How to use destructuring declarations to get multiple value from single map iteration? - kotlin

data class Person(val name: String, val age: String, val color: String)
val persons = listOf(Person("john", "20", "green"), Person("Mary", "20", "blue"))
val names = persons.map {it.name}
val ages = persons.map {it.age}
val colors = persons.map {it.color}
// I will need 3 iteration to get my names and ages and colors
val personsMap = persons.map { it.name to it.age }
val (names2, ages) = personsMap.unzip()
// I am using 1 iteration but with Map and uzip(), I can get names2,
// ages within a single iteration
// Now if I wish to get color
// By using map, or alternatives call helper.. can I retrieve names,
// ages and colors at one go
// I do know that we can use iteration forEachloop and add into a
// set/list, but
// are there better way of doing it
// val (names, ages, colors)
What are the alternative way to write to get multiple value in a single iteration to get its list from destructing.

In order to collect lists of different properties, at least one iteration is required. this can be done by defining an extension function as
fun List<Person>.collectProperties(): Triple<List<String>, List<String>, List<String>>{
val (names, ages, colors) = listOf(mutableListOf<String>(), mutableListOf(), mutableListOf())
this.forEach { person ->
names.add(person.name)
ages.add(person.age)
colors.add(person.color)
}
return Triple(names, ages, colors)
}
now you can use this as
val persons = listOf(Person("john", "20", "green"), Person("Mary", "20", "blue"))
val(names, ages, colors) = persons.collectProperties()

Your toList() call is redundant, so you're iterating 3 times instead of twice.
I can't think of a way to do this with a single iteration using standard library higher-order functions. You could write a function like this that maps and unzips in one step so it only iterates once:
inline fun <T, U, V> Iterable<T>.doubleMap(transform: (T) -> Pair<U, V>): Pair<List<U>, List<V>> {
val size = if (this is Collection<*>) size else 10
val listU = ArrayList<U>(size)
val listV = ArrayList<V>(size)
for (t in this) {
with (transform(t)) {
listU += first
listV += second
}
}
return listU to listV
}

Related

Kotlin: How to flatten list of Hashmaps

How can I flatten a list of HashMaps in Kotlin?
var listOfMaps: List<Map<String, String>> = listOf(mapOf("test" to "test1"), mapOf("test2" to "test3"), mapOf("test4" to "test5"))
I would like to get:Map<String,String> with all key value paires
val map:Map<String, String> = listOfMaps
.flatMap { it.entries }
.associate { it.key to it.value }
you can do something like this if you don't know if the list can be empty.
val map = listOfMaps.fold(mapOf<String, String>()) {acc, value -> acc + value }
If the list never will be empty you can use reduce instead.
Thank you Demigod for the comments
You could use fold:
listOfMaps.fold(
mutableMapOf<String, String>(),
{ acc, item -> acc.also { it.putAll(item) } }
)
The first parameter mutableMapOf<String, String>() creates an empty map to put the values into. This is called the accumulator
The second parameter is a function which takes two arguments
The accumulator
An item from the original list
This function is run sequentially against all items in the list. In our case it adds all the items from each map to the accumulator.
Note: This function does not account for duplicate keys. If a later map has the same key as an earlier one then the value just gets overridden.
Also note (pun intended): We use acc.also {} as we want to return the actual map, not the return value from the addAll method
Well... seeing lots of solutions, I will add my two cents here:
If you don't mind losing the values of duplicated keys you can use something as follows:
listOfMaps.flatMap { it.entries }.associate{ it.key to it.value } // or: it.toPair() if you will
// depending on how large those lists can become, you may want to consider also using asSequence
If you instead want to collect all entries including duplicate keys (i.e. saving all the values), use the following instead (which then gives you a Map<String, List<String>>):
listOfMaps.flatMap { it.entries }.groupBy({ it.key }) { it.value }
Also here the comment regarding asSequence holds...
Finally if you can omit those maps within the list and just use a Pair instead, that will spare you the flatMap { it.entries }-call and make things even easier, e.g. you could just call .toMap() then for the first case and groupBy directly for the second and the question regarding asSequence no longer arises.
An extra addition to this, if you have single value maps, maybe you want to switch to a List<Prair<String, String>>. In that case, the solution is straight forward:
You would have something like:
var listOfMaps: List<Pair<String, String>> = listOf("test" to "test1", "test2" to "test3", "test4" to "test5")
and toMap() would dsolve it all:
listOfMaps.toMap()
If you don't have duplicate keys or don't care for them, you can do it like this:
val flattenedMap = listOfMaps.flatMap { it.toList() }.toMap()
If you have duplicate keys and care for them, you can do it like this:
val flattenedMap = mutableMapOf<String, MutableList<String>>().apply {
listOfMaps.flatMap { it.toList() }.forEach {
getOrPut(it.first) {
mutableListOf()
}.add(it.second)
}
}
The result will be Map<String, List<String>> then of course.
This solution works with same keys (and different collection as values) by merging them together instead of overwriting them
/**
* Merge two maps with lists
*/
fun <K,V>Map<K,Collection<V>>.mergeLists(other: Map<K,Collection<V>>) =
(this.keys + other.keys).associateWith {key ->
setOf(this[key], other[key]).filterNotNull()
}.mapValues { (_,b) -> b.flatten().distinct() }
/**
* Merge two maps with sets
*/
fun <K,V>Map<K,Collection<V>>.mergeSets(other: Map<K,Collection<V>>) =
(this.keys + other.keys).associateWith {key ->
setOf(this[key], other[key]).filterNotNull()
}.mapValues { (_,b) -> b.flatten().toSet() }
Then use like e.g. listOfMaps.reduce { a, b -> a.mergeSets(b) }
Test:
#Test
fun `should merge two maps with as lists or sets`() {
// GIVEN
val map1 = mapOf(
"a" to listOf(1, 2, 3),
"b" to listOf(4, 5, 6),
"c" to listOf(10),
"e" to emptyList()
)
val map2 = mapOf(
"a" to listOf(1, 9),
"b" to listOf(7),
"d" to listOf(null)
)
// WHEN
val mergedAsLists = map1.mergeLists(map2)
val mergedAsSets = map1.mergeSets(map2)
// THEN
listOf(mergedAsLists, mergedAsSets).forEach { merged ->
assertThat(merged.keys).containsOnly("a", "b", "c", "d", "e")
assertThat(merged["a"]).containsOnly(1,2,3,9)
assertThat(merged["b"]).containsOnly(4,5,6,7)
assertThat(merged["c"]).containsOnly(10)
assertThat(merged["d"]).containsOnly(null)
assertThat(merged["e"]).isEmpty()
}
}

Traverse a list of filter to appy them

I have some filter on a list(10..1000).
I would like to have a list of function objects, traverse this list and apply that filter to (10..1000).
Maybe later choose some filters.
fun main(args: Array<String>) {
var sol = (10..1000).toList().filter(lastDigitIsLength ).filter(no7andNo1 ).filter(isEvenAndGreater1).filter(first2DigitsOddCrossSumLess10 ).filter(isPrime )
println("The number is $sol")
}
/* The functions exist
There are few ways to do that.
I've listed them in my example, along with some general comments.
// No need for varargs since Kotlin 1.3
fun main() {
// Prefer vals over vars
val sol = (10..1000).
asSequence().// Use asSequence instead of toList
filter { lastDigitIsLength(it) }. // Block
filter(::no7andNo1). // Method reference
toList()
println("The number is $sol")
}
// Don't know what it means, so lets just always return true
fun lastDigitIsLength(i: Int) = true // Short notation
fun no7andNo1(i: Int): Boolean {
return i % 10 == 7 || i % 10 == 1
}
Sorry, maybe I misunderstand your question. Do you want to apply a list of filters to list of ints? If so, it can be done like this:
fun filter(list: List<Int>, filters: List<(Int) -> Boolean>): List<Int> {
var result = list
for (filter in filters) {
result = result.filter(filter)
}
return result
}
so you can rewrite your function as:
fun main() {
val sol = filter((10..1000).toList(), listOf(::lastDigitIsLength, ::no7andNo1, ::isEvenAndGreater1))
println("The number is $sol")
}

Removing elements from a list which are not in another list - Kotlin

I have two mutableLists, listOfA has so many objects including duplicates while listOfB has fewer. So I want to use listOfB to filter similar objects in listOfA so all list will have equal number of objects with equivalent keys at the end. Code below could explain more.
fun main() {
test()
}
data class ObjA(val key: String, val value: String)
data class ObjB(val key: String, val value: String, val ref: Int)
fun test() {
val listOfA = mutableListOf(
ObjA("one", ""),
ObjA("one", "o"),
ObjA("one", "on"),
ObjA("one", "one"),
ObjA("two", ""),
ObjA("two", "2"),
ObjA("two", "two"),
ObjA("three", "3"),
ObjA("four", "4"),
ObjA("five", "five")
)
//Use this list's object keys to get object with similar keys in above array.
val listOfB = mutableListOf(
ObjB("one", "i", 2),
ObjB("two", "ii", 5)
)
val distinctListOfA = listOfA.distinctBy { it.key } //Remove duplicates in listOfA
/*
val desiredList = doSomething to compare keys in distinctListOfA and listOfB
for (o in desiredList) {
println("key: ${o.key}, value: ${o.value}")
}
*/
/* I was hoping to get this kind of output with duplicates removed and comparison made.
key: one, value: one
key: two, value: two
*/
}
If you want to operate directly on that distinctListOfA you may want to use removeAll to remove all the matching entries from it. Just be sure that you initialize the keys of B only once so that it doesn't get evaluated every time the predicate is applied:
val keysOfB = listOfB.map { it.key } // or listOfB.map { it.key }.also { keysOfB ->
distinctListOfA.removeAll {
it.key !in keysOfB
}
//} // if "also" was used you need it
If you have a MutableMap<String, ObjA> in place after evaluating your unique values (and I think it may make more sense to operate on a Map here), the following might be what you are after:
val map : MutableMap<String, ObjA> = ...
map.keys.retainAll(listOfB.map { it.key })
retainAll just keeps those values that are matching the given collection entries and after applying it the map now contains only the keys one and two.
In case you want to keep your previous lists/maps and rather want a new list/map instead, you may just call something like the following before operating on it:
val newList = distinctListOfA.toList() // creates a new list with the same entries
val newMap = yourPreviousMap.toMutableMap() // create a new map with the same entries
I tried this
primaryList.removeAll { primaryItem ->
secondaryList.any { it.equals(primary.id, true) }
}
PrimaryList here is a list of objects
SecondaryList here is a list of strings

Kotlin - from a list of Maps, to a map grouped by key

I have a List<Map<Branch,Pair<String, Any>>> that I would like to convert in a single Map<Branch,List<Pair<String, Any>>> .
So if I have an initial list with simply 2 elements :
List
1. branch1 -> Pair(key1,value1)
branch2 -> Pair(key2,value2)
2. branch1 -> Pair(key1a,value1a)
I want to end up with :
Map
branch1 -> Pair(key1,value1)
Pair(key1a,value1a)
branch2 -> Pair(key2,value2)
so a kind of groupBy, using all the values of the keys in the initially nested maps..
I have tried with
list.groupBy{it-> it.keys.first()}
but obviously it doesn't work, as it uses only the first key. I want the same, but using all keys as individual values.
What is the most idiomatic way of doing this in Kotlin ? I have an ugly looking working version in Java, but I am quite sure Kotlin has a nice way of doing it.. it's just that I am not finding it so far !
Any idea ?
Thanks
The following:
val result =
listOfMaps.asSequence()
.flatMap {
it.asSequence()
}.groupBy({ it.key }, { it.value })
will give you the result of type Map<Branch,List<Pair<String, Any>>> with the contents you requested.
val list: List<Map<Branch, Pair<String, Any>>> = listOf()
val map = list
.flatMap { it.entries }
.groupBy { it.key }
.mapValues { entry -> entry.value.map { it.value } }
I've managed to write this.
data class Branch(val name: String)
data class Key(val name: String)
data class Value(val name: String)
val sharedBranch = Branch("1")
val listOfMaps: List<Map<Branch, Pair<Key, Value>>> = listOf(
mapOf(sharedBranch to Pair(Key("1"), Value("1")),
Branch("2") to Pair(Key("2"), Value("2"))),
mapOf(sharedBranch to Pair(Key("1a"), Value("1a")))
)
val mapValues: Map<Branch, List<Pair<Key, Value>>> = listOfMaps.asSequence()
.flatMap { map -> map.entries.asSequence() }
.groupBy(Map.Entry<Branch, Pair<Key, Value>>::key)
.mapValues { it.value.map(Map.Entry<Branch, Pair<Key, Value>>::value) }
println(mapValues)
Is it appliable for your needs?
Everyone else is using flatMap, but you can also consider using fold, which is a common operation for reducing a larger collection into a smaller one. (For example, you can fold a list of integers into a single sum; here, a list of maps into a single map).
Perhaps others will find this easier to read than the flatMap versions above:
val listOfMaps: List<Map<Key, Value>> = ...
val mergedMaps: Map<Key, List<Value>> =
listOfMaps
.fold(mutableMapOf()) { acc, curr ->
curr.forEach { entry -> acc.merge(entry.key, listOf(entry.value)) { new, old -> new + old } }
acc
}
What the above code is doing:
Create a new, empty map. This will be acc (that is, the accumulator).
Iterate through our list of maps.
Work on one map (curr) at a time.
For the current map, run over each of its key/value pairs.
For each key/value, call merge on acc, passing in a list of size one (wrapping the value). If nothing is associated with the key yet, that list is added; otherwise, it is appended to the list already there.
Return the accumulating map, so it's used again in the next step.
Surprised nobody has mentioned the associate function.
val listy: List<Map<String, Int>> =
listOf(mapOf("A" to 1, "B" to 2), mapOf("C" to 3, "D" to 4))
val flattened = listy
.flatMap { it.asSequence() }
.associate { it.key to it.value }
println(flattened)
will print out {A=1, B=2, C=3, D=4}
Extract it to an extension function
private fun <K, V> List<Map<K, V>>.group(): Map<K, List<V>> =
asSequence().flatMap { it.asSequence() }.groupBy({ it.key }, { it.value })
Use it like so:
val list = yourListOfMaps
val grouped = list.group()

How can I change this to use "for loop" instead of `forEach`

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.