fold/reduce with complex accumulator - kotlin

I have a list that looks like this:
val myList = listOf(
Message(
id= 1,
info = listOf(1, 2)
),
Message(
id= 1,
info = listOf(3, 4)
),
Message(
id= 2,
info = listOf(5, 6)
)
)
How can I convert it so the elements with the same id are combined?
listOf(
Message
id= 1
info = listOf(1, 2, 3, 4)
),
Message
id= 2
info = listOf(5, 6)
)
)
I've tried the following, and it works
myList
.groupBy { it.id }
.map { entry ->
val infos = entry.value.fold(listOf<Int>()) { acc, e -> acc + e.info }
Message(
id = entry.key,
info = infos
)
}
But I was wondering if there was an easier/cleaner/more idiomatic way to merge these objects. It seems like I would be able to do this with a single fold, but I can't wrap my brain around it.
Thanks

Would also go for groupingBy but do it a bit differently via fold (compare also Grouping):
myList.groupingBy { it.id }
.fold({ _, _ -> mutableListOf<Int>() }) { _, acc, el ->
acc.also { it += el.info }
}
.map { (id, infos) -> Message(id, infos) }
This way you have only 1 intermediate map and only 1 intermediate list per key, which accumulates your values. At the end you transform it in the form you require (e.g. into a Message). Maybe you do not even need that? Maybe the map is already what you are after?
In that case you may want to use something as follows (i.e. narrowing the mutable list type of the values):
val groupedMessages : Map<Int, List<Int>> = myList.groupingBy { it.id }
.fold({ _, _ -> mutableListOf() }) { _, acc, el ->
acc.also { it += el.info }
}

You can groupingBy the ids, then reduce, which would perform a reduction on each of the groups.
myList.groupingBy { it.id }.reduce { id, acc, msg ->
Message(id, acc.info + msg.info)
}.values
This will of course create lots of Message and List objects, but that's the way it is, since both are immutable. But there is also a chance that this doesn't matter in the grand scheme of things.
If you had a MutableMessage like this:
data class MutableMessage(
val id: Int,
val info: MutableList<Int>
)
You could do:
myList.groupingBy { it.id }.reduce { _, acc, msg ->
acc.also { it.info.addAll(msg.info) }
}.values

A solution without using reduce or fold:
data class Message(val id: Int, val info: List<Int>)
val list = listOf(
Message(id = 1, info = listOf(1, 2)),
Message(id = 1, info = listOf(3, 4)),
Message(id = 2, info = listOf(5, 6))
)
val result = list
.groupBy { message -> message.id }
.map { (_, message) -> message.first().copy(info = message.map { it.info }.flatten() ) }
result.forEach(::println)

By extracting out a few functions which have a meaning of their own, You can make it readable to a great extent.
data class Message(val id: Int, val info: List<Int>) {
fun merge(that: Message): Message = this.copy(info = this.info + that.info)
}
fun List<Message>.mergeAll() =
this.reduce { first, second -> first.merge(second) }
fun main() {
val myList = listOf(
Message(
id = 1,
info = listOf(1, 2)
),
Message(
id = 1,
info = listOf(3, 4)
),
Message(
id = 2,
info = listOf(5, 6)
)
)
val output = myList
.groupBy { it.id }
.values
.map { it.mergeAll() }
println(output)
}

Related

Find the list item of Pairs with the minimum value

val index = listOf("abc", "def", "ghi", "jkl", "mno")
.mapIndexed { index, v ->
var t = 0
var p = 0
for (s in v) {
t += ("deh".get(p++).toInt() - s.toInt()).absoluteValue
}
Pair(index, v)
}
.minOf {
val iterator = iterator<Pair<Int, String>>(it)
if (!iterator.hasNext()) throw NoSuchElementException()
var minValue = iterator.next().second
while (iterator.hasNext()) {
val v = selector(iterator.next())
minValue = minOf(minValue, v)
}
return minValue
}
This is an alternative solution and works, but I am wondering if the solution can be done using mapOf as shown above?
val index = listOf("abc", "def", "ghi", "jkl", "jad", "jaa", "mno")
.mapIndexed { index, v ->
var t = 0
var p = 0
for (s in v) {
t += ("jac".get(p++).toInt() - s.toInt()).absoluteValue
}
Pair(index, t)
}.toSortedSet(compareBy { it.second })
.first()
I create a map of Pairs and I want to find the index of the map item where the Pair with the value (the second item in the pair) is the lowest value (minimum) of all the pairs. If possible, I would like to use the minOf function. The first example above will not compile because of bugs in the minOf function. Not sure how to iterate over the map of Pairs.
You can use minBy {} to get the minimum value from a collection, although often it's safer to use minByOrNull {} in case no minimal value can be computed (which could happen if the list is empty).
import kotlin.math.absoluteValue
fun main() {
val minElement = listOf("abc", "def", "ghi", "jkl", "jad", "jaa", "mno")
.minByOrNull { v ->
var t = 0
var p = 0
for (s in v) {
t += ("jac".get(p++).toInt() - s.toInt()).absoluteValue
}
t
}
println(minElement)
}
jad
Run in Kotlin Playground
If you also want to find the index of the minimal value, then you can use withIndex(), which will pair each list element with its index.
import kotlin.math.absoluteValue
fun main() {
val minIndexedElement = listOf("abc", "def", "ghi", "jkl", "jad", "jaa", "mno")
.withIndex() // adds the index to each element
.minByOrNull { (_, v) ->
var t = 0
var p = 0
for (s in v) {
t += ("jac".get(p++).toInt() - s.toInt()).absoluteValue
}
t
}
println(minIndexedElement)
}
IndexedValue(index=4, value=jad)
Run in Kotlin Playground
Another solution would be to extract the character codes from "jar" and from each item, and then to zip the two code lists. zip allows for a transform closure in which the calculation with the two codes can be made. After that sum() gives the wanted value.
data class Result(val index: Int, val string: String, val computedValue: Int)
val list = listOf("abc", "def", "ghi", "jkl", "jad", "jaa", "mno")
val result = list
.mapIndexed { idx, str ->
val codes1 = "jac".toCharArray().map { it.code }
val codes2 = str.toCharArray().map { it.code }
val computed = codes1.zip(codes2) { code1, code2 -> (code1 - code2).absoluteValue }.sum()
Result(idx, str, computed)
}
.minByOrNull { it.computedValue }
println(result) // Output: Result(index=4, string=jad, computedValue=1)
Instead of the helper data class Result a Triple instance could be used:
...
Triple(idx, str, computed)
}
.minByOrNull { it.third }
// Output: (4, jad, 1)
Or if the calculated value is not needed, it could be dropped like that:
...
?.let { it.first to it.second }
// Output: (4, jad)

Kotlin: mutable map of mutable list won't update the list

(Kotlin newbie here) I have a text file with rows that look like these:
1-1-1
1-1-2
1-1-3
2-1-1
2-1-2
etc.
I have to transform these data to a map where the key is the first 2 elements and the value is a list of the third elements that that match the key. For example, the above records will transform into this JSON:
1-1: [1, 2, 3]
2-1: [1, 2]
etc.
I'm unable to increment the list. Here's a simplified version, I get stuck on the "else":
fun main () {
val l1 = mutableListOf("1-1-1", "1-1-2", "1-1-3", "2-1-1", "2-1-2")
val m = mutableMapOf<String, List<Int>>()
for (e in l1) {
val c = e.split("-")
val key = "${c[0]}-${c[1]}"
if (m[key] == null) m[key] = listOf(c[2].toInt())
else println("How do I append to the list?")
}
println(m)
}
Output:
{1-1=[1], 2-1=[1]}
But I want:
{1-1=[1, 2, 3], 2-1=[1, 2]}
Thank you (comments about idiomatic form are welcome!)
If we continue to follow your strategy, what you need is for the value type to be a MutableList. Then you can add to the existing MutableList when there's already an existing list for that key:
fun main() {
val l1 = mutableListOf("1-1-1", "1-1-2", "1-1-3", "2-1-1", "2-1-2")
val m = mutableMapOf<String, MutableList<Int>>()
for (e in l1) {
val c = e.split("-")
val key = "${c[0]}-${c[1]}"
if (m[key] == null) m[key] = mutableListOf(c[2].toInt())
else m[key]!!.add(c[2].toInt())
}
println(m)
}
This can be more natural using getOrPut(). It returns the existing MutableList or creates one and puts it in the map if it's missing. Then we don't have to deal with if/else, and can simply add the new item to the list.
fun main() {
val l1 = mutableListOf("1-1-1", "1-1-2", "1-1-3", "2-1-1", "2-1-2")
val m = mutableMapOf<String, MutableList<Int>>()
for (e in l1) {
val c = e.split("-")
val key = "${c[0]}-${c[1]}"
m.getOrPut(key, ::mutableListOf).add(c[2].toInt())
}
println(m)
}
But we can use the map and groupBy functions to create it more simply:
val m = l1.map { it.split("-") }
.groupBy(
{ "${it[0]}-${it[1]}" }, // keys
{ it[2].toInt() } // values
)
You can achieve your desired output with a single call to groupBy of the Kotlin standard library.
val input = listOf("1-1-1", "1-1-2", "1-1-3", "2-1-1", "2-1-2")
val result = input.groupBy(
{ it.substringBeforeLast("-") }, // extract key from item
{ it.substringAfterLast("-").toInt() } // extract value from item
)
The first lambda function extracts the key to group by of every list item. The second lambda function provides the value to use for each list item.
You can also do it by first mapping your values to Pairs and then group them as follows:
fun main(args: Array<String>) {
val input = listOf("1-1-1", "1-1-2", "1-1-3", "2-1-1", "2-1-2")
val result = input.map {
val values = it.split("-")
"${values[0]}-${values[1]}" to values[2]
}.groupBy ({ it.first }) { it.second }
println(result)
}

kotlin reduce list of map to a single map [closed]

Closed. This question needs details or clarity. It is not currently accepting answers.
Want to improve this question? Add details and clarify the problem by editing this post.
Closed 2 years ago.
Improve this question
How do I reduce reduce
val res = [{a=3}, {a=2, c=1}]
Into
val res = [{a=5,c=1}]` //or
val res = {a=5, c=1}
?
I tried the groupBy function but that doesn't give me the desired result. I want to sump up the values of same keys.
You can use fold operator
For example
val list = listOf(mapOf("a" to 3), mapOf("a" to 2, "c" to 1))
val res = list.fold(mutableMapOf<String, Int>()) { acc, v ->
v.forEach { (key, value) ->
if (acc.contains(key)) {
acc[key] = acc[key]!! + value
} else {
acc[key] = value
}
}
acc
}
Or by reduce operator:
val res = list.reduce { acc, map ->
val res = acc.toMutableMap()
map.forEach { (key, value) ->
res[key] = res.getOrDefault(key, 0) + value
}
res
}
Or groupBy and sumBy operators
val res = list.flatMap { it.entries }
.groupBy { it.key }
.mapValues { it.value.sumBy { v -> v.value } }
To have conflicting values summed, you can use x.asSequence().flatMap { it.asSequence() }.groupBy { it.key }.mapValues { it.value.asSequence().map { it.value }.sum() }, resulting in {a=5, c=1}.
The easiest way to do it will be to iterate over a list. Like this:
val input = listOf(
mapOf(
"a" to 2
),
mapOf(
"a" to 3,
"c" to 1
)
)
val result = mutableMapOf<String, Int>()
input.forEach { map ->
map.forEach { (key, value) ->
result[key] = result.getOrElse(key, { 0 }) + value
}
}
There are already a bunch of answers to the actual problem in the question, but if those maps are counts of items, and you're generating them yourself, you might want to create Groupings instead
val source = listOf(listOf('a', 'a', 'a'), listOf('a', 'a', 'c'))
// creating a grouping for each list, using each item as its own key
val grouped = source.map { it.groupingBy { it } }
And then you can use eachCountTo and a map to build up counts for each key
val counts = grouped.fold(mutableMapOf<Char, Int>()) { countMap, grouping ->
grouping.eachCountTo(countMap)
countMap
}
print(counts)
>> {a=5, c=1}
Good to know it exists, anyway!

Kotlin: how to use reduce/fold with a data structure as the accumulative argument?

I want to use reduce/fold methods to create a new list from an existing List. This is the code I tried to write:
val list: List<Int> = listOf(1, 2, 3)
val newList = mutableListOf<Int>()
val sum: List<Int> = list.fold(newList) { (acc: List<Int>, i: Int) -> {
acc.add(i + 10)
acc
}}
It doesn't compile.
I want that newList will be (11,12,13).
How can I do it in a functional manner?
In Javascript, for example, this code compiles:
list.reduce((acc, item) => {
acc.push(item + 10)
return acc;
}, [])
The most convenient and functional way to do what you want is using map function:
val list: List<Int> = listOf(1, 2, 3)
val newList: MutableList<Int> = list.map { it + 10 }.toMutableList()
But if you really want to use fold for some (strange) reason, you can do it this way:
val newList: MutableList<Int> =
list.fold(ArrayList()) { acc, x -> acc.apply { add(x + 10) } }
Or this way:
val newList: MutableList<Int> =
list.fold(ArrayList()) { acc, x ->
acc += x + 10
acc
}

How to converter list of tuples to tuple of lists?

I have the example to show what I mean:
fun makeRange(i: Int) = Pair(i - 1, i + 1)
val listOfData = listOf(1, 2, 3, 4, 5, 6)
val pairs = listOfData
.map { makeRange(it) }
val leftRange = pairs.map { it.first }
val rightRange = pairs.map { it.second }
I have some list and function which returns a tuple. But the result I need is touple of two lists. I need something like that:
// can I get something like that ?
val (leftRange, rightRange) = listOfData.map { makeRange(it) } ...
Is there a way to do it?
If you really want to destructure it like this, I would also split up your makeRange-function, e.g.:
fun makeLeftRange(i: Int) = i - 1
fun makeRightRange(i: Int) = i + 1
fun makeRange(i: Int) = makeLeftRange(i) to makeRightRange(i) // if you still need it...
Then you can destructure as follows:
val (leftRange, rightRange) = listOfData.map(::makeLeftRange) to listOfData.map(::makeRightRange)
Or if it is really just such an easy function, why not just use the following instead:
val (leftRange, rightRange) = listOfData.map(Int::dec) to listOfData.map(Int::inc)
// or
val (leftRange, rightRange) = listOfData.map { it - 1 } to listOfData.map { it + 1 }
If you want to keep your makeRange as is and want to do it that way, it will get a bit uglier, e.g.:
val (leftRange, rightRange) = listOfData.map(::makeRange).let {
listOfPairs -> listOfPairs.map { it.first } to listOfPairs.map { it.second }
}
Basically reusing what you've shown in an additional let-statement.
Seems like kotlin unzip function is just what you're looking for.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/unzip.html
In your example the usage would look something like
val (leftRange, rightRange) = pairs.unzip()