I am trying to return List<List<Map<String, String>>> from a function in kotlin. I'm new to kotlin.
Edit1
Here's how I am attempting to to this
val a = mutableListOf(mutableListOf(mutableMapOf<String, String>()))
The problem with the above variable is, I am unable to figure out how to insert data into this variable. I tried with this:
val a = mutableListOf(mutableListOf(mutableMapOf<String, String>()))
val b = mutableListOf(mutableMapOf<String, String>())
val c = mutableMapOf<String, String>()
c.put("c", "n")
b.add(c)
a.add(b)
This is giving me:
[[{}], [{}, {c=n}]]
What I want is [[{c=n}]]
Can someone tell me how I can insert data into it?
The end goal I am trying to achieve is to store data in the form of List<List<Map<String, String>>>
EDIT 2
The function for which I am trying to write this dat structure:
fun processReport(file: Scanner): MutableList<List<Map<String, String>>> {
val result = mutableListOf<List<Map<String, String>>>()
val columnNames = file.nextLine().split(",")
while (file.hasNext()) {
val record = mutableListOf<Map<String, String>>()
val rowValues = file.nextLine()
.replace(",(?=[^\"]*\"[^\"]*(?:\"[^\"]*\"[^\"]*)*$)".toRegex(), "")
.split(",")
for (i in rowValues.indices) {
record.add(mapOf(columnNames[i] to rowValues[i]))
print(columnNames[i] + " : " + rowValues[i] + " ")
}
result.add(record)
}
return result
}
You don't need to use mutable data structures. You can define it like this:
fun main() {
val a = listOf(listOf(mapOf("c" to "n")))
println(a)
}
Output:
[[{c=n}]]
If you wanted to use mutable data structures and add the data later, you could do it like this:
fun main() {
val map = mutableMapOf<String, String>()
val innerList = mutableListOf<Map<String, String>>()
val outerList = mutableListOf<List<Map<String, String>>>()
map["c"] = "n"
innerList.add(map)
outerList.add(innerList)
println(outerList)
}
The output is the same, although the lists and maps are mutable.
In response to the 2nd edit. Ah, you're parsing a CSV. You shouldn't try to do that yourself, but you should use a library. Here's an example using Apache Commons CSV
fun processReport(file: File): List<List<Map<String, String>>> {
val parser = CSVParser.parse(file, Charset.defaultCharset(), CSVFormat.DEFAULT.withHeader())
return parser.records.map {
it.toMap().entries.map { (k, v) -> mapOf(k to v) }
}
}
For the following CSV:
foo,bar,baz
a,b,c
1,2,3
It produces:
[[{foo=a}, {bar=b}, {baz=c}], [{foo=1}, {bar=2}, {baz=3}]]
Note that you can simplify it further if you're happy returning a list of maps:
fun processReport(file: File): List<Map<String, String>> {
val parser = CSVParser.parse(file, Charset.defaultCharset(), CSVFormat.DEFAULT.withHeader())
return parser.records.map { it.toMap() }
}
Output:
[{foo=a, bar=b, baz=c}, {foo=1, bar=2, baz=3}]
I'm using Charset.defaultCharset() here, but you should change it to whatever character set the CSV is in.
I have never worked before with Kotlin so I have a newbie question. I am working with an existing codebase, so I am wondering about a few things. I see that there is a function getDepartmentById which looks like this:
fun getDepartmentById(ctx: Ctx, params: JsonObject): Either<Failure, FlatResp> =
getOneByIdFlattened(ctx, params.right(), getDepartmentByIdSql(ctx), flattenOne = flattenerToType(MainAccessType.DEPARTMENT))
fun getDepartmentById(ctx: Ctx, id: Long): Either<Failure, FlatResp> =
getDepartmentById(ctx, jsonObject("id" to id))
Calling that function returns either Failure or FlatResp. From what I can see in the code FlatResponse is typealias for Map<MainAccessType, Entities>.
The function getOneByIdFlattened looks like this:
fun getOneByIdFlattened(ctx: Ctx,
params: Either<Long, JsonObject>,
statement: String,
rowConverter: (Row) -> Map<String, Any?> = ::mapFromDbNames,
grouper: (List<Map<String, Any?>>) -> List<Map<String, Any?>> = ::identity,
flattenOne: (List<Map<String, Any?>>) -> FlatResp
): Either<Failure, FlatResp> =
either.eager {
val id = when (params) {
is Either.Left -> Either.Right(params.value)
is Either.Right -> params.value.idL?.right()
?: Failure.JsonError(SErr(GlowErrs.MISSING_ID, "You must provide id")).left()
}.bind()
val dbDataList: List<Map<String, Any?>> = doQuery(ctx, statement, mapOf(
"courierIds" to ctx.user.courierIds,
"id" to id,
"count" to 1,
"offset" to 0,
"departmentIds" to ctx.userDepartments,
"customerIds" to ctx.user.customerIds
),
rowConverter, false
).bind()
val result = flattenOne(grouper(dbDataList))
addUpdatedAtEpoch(result)
}
I wonder how can I get from a FlatResp a property of an object, that looks like this:
So, for example if I want to get just name from this object what would be the best way to do this?
Also, I wonder why is this function returning a collection, and not just a single object when it should get a single row by id from DB?
This is the sql function:
private fun getDepartmentByIdSql(ctx: Ctx) =
"""select ${createSelectFields(departmentKeys)}
from department dept
where dept.id = :id
${
when (ctx.user.role) {
UserRoles.ADMIN -> ""
else -> "and dept.id = any (:departmentIds) "
}
}"""
So there are a lot of things wrong in the snippet provided. Given Snippets:
1
fun getDepartmentById(ctx: Ctx, params: JsonObject): Either<Failure, FlatResp> =
getOneByIdFlattened(ctx, params.right(), getDepartmentByIdSql(ctx), flattenOne = flattenerToType(MainAccessType.DEPARTMENT))
2
fun getDepartmentById(ctx: Ctx, id: Long): Either<Failure, FlatResp> =
getDepartmentById(ctx, jsonObject("id" to id))
3
private fun getDepartmentByIdSql(ctx: Ctx) =
"""select ${createSelectFields(departmentKeys)}
from department dept
where dept.id = :id
${
when (ctx.user.role) {
UserRoles.ADMIN -> ""
else -> "and dept.id = any (:departmentIds) "
}
}"""
4
fun getOneByIdFlattened(ctx: Ctx,
params: Either<Long, JsonObject>,
statement: String,
rowConverter: (Row) -> Map<String, Any?> = ::mapFromDbNames,
grouper: (List<Map<String, Any?>>) -> List<Map<String, Any?>> = ::identity,
flattenOne: (List<Map<String, Any?>>) -> FlatResp
): Either<Failure, FlatResp> =
either.eager {
val id = when (params) {
is Either.Left -> Either.Right(params.value)
is Either.Right -> params.value.idL?.right()
?: Failure.JsonError(SErr(GlowErrs.MISSING_ID, "You must provide id")).left()
}.bind()
val dbDataList: List<Map<String, Any?>> = doQuery(ctx, statement, mapOf(
"courierIds" to ctx.user.courierIds,
"id" to id,
"count" to 1,
"offset" to 0,
"departmentIds" to ctx.userDepartments,
"customerIds" to ctx.user.customerIds
),
rowConverter, false
).bind()
val result = flattenOne(grouper(dbDataList))
addUpdatedAtEpoch(result)
}
Issues:
in snippet 2, jsonObject should be JsonObject(..)
I have no idea what the following lines do :
//snippet1:
flattenOne = flattenerToType(MainAccessType.DEPARTMENT))
//snipper 4
either.eager {...block...}
addUpdatedAtEpoch(result)
doQuery(ctx, statement, mapOf(..)
//snippet3
UserRoles.ADMIN -> ""
"""select ${createSelectFields(departmentKeys)}
They are all probably some extension functions or util files made by your company or from some famous libraries like anko orsplitties . plus these are mixes with function calls of your own class, like createSelectFields or ctx.user.courierIds. also if i have to guess, then this seems like an unusual way of performing some operation on an sql dB
based on just code completion by android studio, i have been able to figure out the classes as following:
class Entities
typealias FlatResp = Map<MainAccessType, Entities>
class Ctx
sealed class Either<A,B>(val a:A?, val b:B?){
val value:A? = null
class Left<A>(val aa:A):Either<A,A>(aa,aa)
class Right<B>(val bv:B):Either<B,B>(bb,bb)
}
class Failure
class Row
class JsonObject(val pair:Pair<String,Long>):JSONObject(){
fun right():Either<Long,JsonObject>{
}
}
class jsonObject()
enum class MainAccessType{DEPARTMENT}
fun getDepartmentById(ctx: Ctx, params: JsonObject): Either<Failure, FlatResp> {
return getOneByIdFlattened(
ctx,
params.right(),
getDepartmentByIdSql(ctx),
flattenOne = flattenerToType(MainAccessType.DEPARTMENT))
}
fun getDepartmentById(ctx: Ctx, id: Long): Either<Failure, FlatResp> {
return getDepartmentById(ctx, JsonObject("id" to id))
}
fun getOneByIdFlattened(ctx: Ctx,
params: Either<Long, JsonObject>,
statement: String,
rowConverter: (Row) -> Map<String, Any?> = ::mapFromDbNames,
grouper: (List<Map<String, Any?>>) -> List<Map<String, Any?>> = ::identity,
flattenOne: (List<Map<String, Any?>>) -> FlatResp
): Either<Failure, FlatResp> {
return either.eager {
val id = when (params) {
is Either.Left -> Either.Right(params.value)
is Either.Right -> params.value.idL?.right()
?: Failure.JsonError(SErr(GlowErrs.MISSING_ID, "You must provide id")).left()
}.bind()
val dbDataList: List<Map<String, Any?>> = doQuery(ctx, statement, mapOf(
"courierIds" to ctx.user.courierIds,
"id" to id,
"count" to 1,
"offset" to 0,
"departmentIds" to ctx.userDepartments,
"customerIds" to ctx.user.customerIds
),
rowConverter, false
).bind()
val result = flattenOne(grouper(dbDataList))
addUpdatedAtEpoch(result)
}
}
private fun getDepartmentByIdSql(ctx: Ctx) =
"""select ${createSelectFields(departmentKeys)}
from department dept
where dept.id = :id
${
when (ctx.user.role) {
UserRoles.ADMIN -> ""
else -> "and dept.id = any (:departmentIds) "
}
}"""
fun mapFromDbNames(row:Row): Map<String,Any?>{
}
fun identity(param : List<Map<String, Any?>>): List<Map<String, Any?>>{
}
This is still not correct and has a lots of red lines in it. but what you can do is keep this as a starter in a separate file, compare and fix the code accordingly and then maybe we can tell what would be a better way:
replace inline functions (fun xyz(...) = someValue ) to block functions. (alt+enter in windows, cmd+n in mac)
instead of typeAlias, use map directly
::something means a function is passed as parameter . its similar to how we pass runnables in java 8, but even more shorthand. you can do ctrl+click( for mac its cmd+click) on that function and goto that function to check what its params are, what its return type are. do the same for various classes/ extension fucntions, variables too. this will help the most
instead of passing something into something which is being passed into another thing (like val bot = Robot(Petrol("5Litres") ) ) , split into different lines to make it understandable ( val amount = "5litres"; val equipment = Petrol(amount) ; val bot = Robot(equipment) )
try to not use 3rd party library/ replace with your own understandable code.
repeat steps 1-5
Hope this gives someplace to start. kotlin is a beautiful language but is also very easy to make unreadable.
Mapping Map values
I wonder how can I get from a FlatResp a property of an object, that looks like this:
So, for example if I want to get just name from this object what would be the best way to do this?
TL;DR
Without data to work with, here's my guess:
val extractedNames: Map<Long, String?> = destinationDepartment
.mapValues { (_, userData: Map<String, Any?>) ->
when (val name = userData["name"]) {
is String -> name
else -> null
}
}
println(extractedNames)
// {1=Bergen, 2=Cindy, 3=Dave}
Intro
Kotlin is great for manipulating collections. For a more general of how to work with collections in Kotlin, I think the docs are really clear Collection transformation operations#Map.
Let's see how that works for this example. You want to extract a specific element, so for that we can use map().
From your screenshot it looks like this is a Map<Long, Map>, where the value is a Map<String, Any?>. I'll assume you want to change the Map<Long, Map> to a Map<Long, String>, where the key is the database ID and the value is user's name.
Test data
So I've got something to test with, I made a new Map:
val destinationDepartment: Map<Long, Map<String, Any?>> =
mapOf(
1L to mapOf(
"id" to 1,
"name" to "Bergen",
"createdAt" to LocalDateTime.now(),
"updatedAt" to LocalDateTime.now(),
),
2L to mapOf(
"id" to 2,
"name" to "Cindy",
"createdAt" to LocalDateTime.now(),
"updatedAt" to LocalDateTime.now(),
),
3L to mapOf(
"id" to 3,
"name" to "Dave",
"createdAt" to LocalDateTime.now(),
"updatedAt" to LocalDateTime.now(),
),
)
Basic noop
First, set up the basics. A Map can be converted to a list of Entries. When we call map(), it will iterates over each Entry, and applies a lambda - which is something we must write. In this instance, the lambda receives the key and value of the Map, and must return a new value.
Aside: the Java equivalent is map.entrySet().stream().map(...)...
Here, the lambda just returns a pair (created with to).
val extractedNames = destinationDepartment
.map { (id: Long, userData: Map<String, Any?>) ->
id to userData
}
println(extractedNames)
// Output: [(1, {id=1, name=Bergen, createdAt=2021-08-19T11:00:07.447660, updatedAt=2021-08-19T11:00:07.449969}),
// (2, {id=2, name=Cindy, createdAt=2021-08-19T11:00:07.463813, updatedAt=2021-08-19T11:00:07.463845}),
// (3, {id=3, name=Dave, createdAt=2021-08-19T11:00:07.463875, updatedAt=2021-08-19T11:00:07.463890})]
Pretty boring! But now we're set up for the next step - extracting name from userData: Map<String, Any?>.
Extracting name
val extractedNames = destinationDepartment
.map { (id: Long, userData: Map<String, Any?>) ->
val name = userData["name"]
id to name
}
println(extractedNames)
// Output: [(1, Bergen), (2, Cindy), (3, Dave)]
Now there's loads of ways to improve this. Making sure that name is a String, not Any?, filtering out blank or null names, mapping to DTOs, sorting. Again, the Kotlin documentation would be a good start. I'll start by listing one really good improvement.
Converting List<Pair<>> to Map<>
If you look at the type of val extractedNames, you'll see that it's a list, not a map.
val extractedNames: List<Pair<Long, Any?>> = ...
That's because the lambda we wrote in the map() function is returning a Pair<Long, String>. Kotlin doesn't know that this is still considered a Map. We can convert any List<Pair<>> back to a map with toMap()
toMap()
val extractedNames: Map<Long, Any?> = destinationDepartment
.map { (id: Long, userData: Map<String, Any?>) ->
val name = userData["name"]
id to name
}
.toMap() // convert List<Pair<>> to a Map<>
println(extractedNames)
// Output: {1=Bergen, 2=Cindy, 3=Dave}
But this is also not great. Why is id: Long in the lambda if we're not using it? Because we're only extracting the name from userData, we're only mapping the values of the Map. We don't need id: Long at all. Fortunately Kotlin has another useful method: mapValues() - and it returns a Map<>, so we can drop the toMap(). Let's use it.
mapValues()
val extractedNames: Map<Long, Any?> = destinationDepartment
.mapValues { (id: Long, userData: Map<String, Any?>) ->
val name = userData["name"]
id to name
}
println(extractedNames)
// {1=(1, Bergen), 2=(2, Cindy), 3=(3, Dave)}
Umm weird. Why are the ids in the values? That's because the mapValues() lambda should return the new value, and in our lambda we're returning both the id and name - oops! Let's only return the name.
Fixing mapValues()
val extractedNames: Map<Long, Any?> = destinationDepartment
.mapValues { (_, userData: Map<String, Any?>) ->
userData["name"]
}
println(extractedNames)
// {1=(1, Bergen), 2=(2, Cindy), 3=(3, Dave)}
Better! Because id is not used, an underscore can be used instead
Aside: Note that the lambda doesn't have a return. Read Returning a value from a lambda expression for an explanation.
I have the following dataclasses:
data class JsonNpc(
val name: String,
val neighbours: JsonPreferences
)
data class JsonPreferences(
val loves: List<String>,
val hates: List<String>
)
I have a list of these, and they reference each other through strings like:
[
JsonNpc(
"first",
JsonPreferences(
listOf("second"),
listOf()
)
),
JsonNpc(
"second",
JsonPreferences(
listOf(),
listOf("first")
)
)
]
note that a likes b does not mean b likes a
I also have the Dataclasses
data class Npc(
val name: String,
val neighbours: NeighbourPreferences,
)
data class NeighbourPreferences(
val loves: List<Npc>,
val hates: List<Npc>
)
And I want to convert the String reference types to the normal reference types.
What I have tried:
recursively creating the npcs (and excluding any that are already in the chain, as that would lead to infinite recursion):
Does not work, as the Npc can not be fully created and the List is immutable (I dont want it to be mutable)
I have managed to find a way to do this. It did not work with Npc as a data class, as I needed a real constructor
fun parseNpcs(map: Map<String, JsonNpc>): Map<String, Npc> {
val resultMap: MutableMap<String, Npc> = mutableMapOf()
for (value in map.values) {
if(resultMap.containsKey(value.name))
continue
Npc(value, map, resultMap)
}
return resultMap
}
class Npc(jsonNpc: JsonNpc, infoList: Map<String, JsonNpc>, resultMap: MutableMap<String, Npc>) {
val name: String
val neighbourPreferences: NeighbourPreferences
init {
this.name = jsonNpc.name
resultMap[name] = this
val lovesNpc = jsonNpc.neighbours.loves.map {
resultMap[it] ?: Npc(infoList[it] ?: error("Missing an Npc"), infoList, resultMap)
}
val hatesNpc = jsonNpc.neighbours.hates.map {
resultMap[it] ?: Npc(infoList[it] ?: error("Missing an Npc"), infoList, resultMap)
}
this.neighbourPreferences = NeighbourPreferences(
lovesNpc, hatesNpc
)
}
}
data class NeighbourPreferences(
val loves: List<Npc>,
val hates: List<Npc>
)
checking in the debugger, the people carry the same references for each Neighbour, so the Guide is always one Npc instance.
need to call a api which take Map<String, String>
fun api.log(map: Map<string, String>
but the key has to be only from the registered ones, so defined a enum for the registered keys:
enum class RegisteredKey {
NONE, ZOOM
}
and first build the EnumMap<> to enforce key type:
var enumParamMap: EnumMap<RegisteredKey, String> = EnumMap<RegisteredKey, String>(RegisteredKey::class.java)
enumParamMap.put(RegisteredKeys.NONE, "0")
enumParamMap.put(RegisteredKeys.ZOOM, "1")
doLog(enumParamMap)
question 1, is there constructor to build the enumMap directly with data?
and then need to transform the EnumMap into a Map<> so that the api.log() will accept it
fun doLog(enumParamMap: EnumMap<RegisteredKey, String>) {
val map: MutableMap<String, String> = mutableMapOf()
for (enum in enumParamMap.entries) {
map.put(enum.key.name, enum.value)
}
api.log(map)
}
question 2: is there simpler way to map the enumMap to regular map?
I'm not sure I'm interpreting your first question correctly, but if you mean you want to initialize an exhaustive EnumMap where every key has an entry, similar to the Array constructor that takes a lambda, you could write one like this:
inline fun <reified K : Enum<K>, V> exhaustiveEnumMap(init: (key: K) -> V): EnumMap<K, V> {
val map = EnumMap<K, V>(K::class.java)
for (key in enumValues<K>())
map[key] = init(key)
return map;
}
Usage:
val map = exhaustiveEnumMap<RegisteredKey, String> { key -> key.ordinal.toString() }
or
val map = exhaustiveEnumMap<RegisteredKey, String> { key ->
when (key){
RegisteredKey.NONE -> "0"
RegisteredKey.ZOOM -> "1"
}
}
Edit based on your comment: You could do that by wrapping a mapOf call with the EnumMap constructor like this, but it would be instantiating an intermediate throwaway LinkedHashMap:
val map = EnumMap(mapOf(RegisteredKey.NONE to "0", RegisteredKey.ZOOM to "1"))
Instead, you could write a helper function like this:
inline fun <reified K: Enum<K>, V> enumMapOf(vararg pairs: Pair<K, V>): EnumMap<K, V> =
pairs.toMap(EnumMap<K, V>(K::class.java))
Usage:
var enumParamMap = enumMapOf(RegisteredKey.NONE to "0", RegisteredKey.ZOOM to "1")
-------
For your next question, I'm not sure if this is really any simpler, but you could do:
fun doLog(enumParamMap: EnumMap<RegisteredKey, String>) =
api.log(enumParamMap.map{ it.key.name to it.value }.toMap())
It's more concise, but you're allocating a list and some pairs that you wouldn't be with your way of doing it.
Something like this should work for initialization:
EnumMap<RegisteredKey, String>(
mapOf(RegisteredKey.NONE to "0", RegisteredKey.ZOOM to "1")
)
To get a normal map just call .toMutableMap():
EnumMap<RegisteredKey, String>(
mapOf(RegisteredKey.NONE to "0", RegisteredKey.ZOOM to "1")
).toMutableMap()
I'm struggling to change it to use a for loop and still do the same thing.
The program is supposed to read a file with some flights and this specific part of the program needs to read the file using two different days that the user inputs then it needs to show how many passengers there are per flight and each day.
And how it's done now works but I'm trying to change it to use a for loop as I said before but doesn't work because I don't know how to do the same thing as map does but only in the fun interval.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
val map = mapReservas(reservas)
for(day in dayInferior..daySuperior) {
map.forEach {
val reservasNum = it.key.first
val reservasDay = it.key.second
val reservasCount = it.value.count()
if (reservasDay == day) {
println("$reservasNum has $reservasCount passengers on day $day")
}
}
}
println()
println("Press Enter")
readLine()
}
fun mapReservas(reservas: List<Reservas>): Map<Pair<String, Int>, List<Reservas>> {
val map = mutableMapOf<Pair<String, Int>, MutableList<Reservas>>()
for (reserva in reservas) {
val key = reserva.numFlight to reserva.day
val list = map[key] ?: mutableListOf()
list.add(reserva)
map[key] = list
}
return map
}
All your code can be replaced only with one function.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
reservas.groupBy { reserva -> reserva.day to reserva.numFlight }
.filter { (key, _) -> key.first in dayInferior..daySuperior }
.forEach { (key, reservas) ->
val (reservasNum, reservasDay) = key
val reservasCount = reservas.count()
println("$reservasNum has $reservasCount passengers on day $reservasDay")
}
println()
println("Press Enter")
readLine()
}
Explaining:
As I undestand, at first you trying to group all your Reservas by day and numFlight. It can be done via one function groupBy where you pass pair of day and numFlight.
Filter all Reservas by day. It can be done by checking if day belongs to range dayInferior..daySuperior (operator in).
Print all reservas by using forEach.
Other things
Destructing declarations
val reservasNum = it.key.first
val reservasDay = it.key.second
same as
val (reservasNum, reservasDa) = it.key
Omitting one unused parameter in lamda:
.filter { (key, _) -> ... }
If you iterate with a for loop over the Map each element is a Pair. If you write (pair, list) you destructure each Pair which itself consists of a Pair and a List.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
val map = mapReservas(reservas)
for(day in dayInferior..daySuperior) {
for((pair, list) in map) {
val reservasNum = pair.first
val reservasDay = pair.second
val reservasCount = list.count()
// ...
}
}
// ...
}
Maybe this makes it more clear:
for(outerPair in map){
val (innerPair, list) = outerPair
val reservasNum = innerPair.first
val reservasDay = innerPair.second
val reservasCount = list.count()
// ...
}
I left this function (mapReservas) untouched intentionally, because maybe you are using it somewhere else. But you can improve it right away by using Type aliases (since Kotlin 1.1).
typealias FlightNum = String
typealias Day = Int
fun mapReservas(reservas: List<Reservas>):
Map<Pair<FlightNum, Day>, List<Reservas>> {
// ...
}
As you can see the code becomes much more readable if you use the destructure syntax and Type aliases.