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

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!

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)

fold/reduce with complex accumulator

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)
}

Create map from list of list

I want to create map from list of list and I have write this code
fun getCourses(coursesCount: Int): Map<Course, Int> {
val paidCourses = mutableMapOf<Course, Int>()
for(student in data) {
for(course in student.subscribedCourses) {
if( course.isPaid ) {
paidCourses.putIfAbsent(course, 0)
paidCourses[course] = paidCourses[course]!! + 1
}
}
}
return paidCourses.toList().sortedByDescending { (_, value) -> value }.take(coursesCount).toMap()
}
I wonder how can I concise this more in Kotlin.
You can do a flatMap to flatten the "students with courses" to just a single list of all the courses, filter by isPaid, group by each course, and use eachCount to count the courses.
val paidCourses =
data.flatMap { it.subscribedCourses }
.filter { it.isPaid }
.groupingBy { it }.eachCount()
Note that this will create multiple intermediate lists and loop through them multiple times, which may be undesirable. Here's a way that avoids this, and is still quite concise:
val paidCourses = mutableMapOf<Course, Int>()
for(student in data) {
for(course in student.subscribedCourses) {
if (course.isPaid) {
paidCourses.merge(course, 1, Int::plus)
}
}
}
You can also do:
val paidCourses = mutableMapOf<Course, Int>()
for(student in data) {
student.subscribedCourses.filter { it.isPaid }
.groupingBy { it }
.eachCountTo(paidCourses)
}
You can use merge to increment the frequency.
paidCourses.merge(course, 1, Int::plus)

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)
}

How do i filter and copy values to a list of objects based on another list in Kotlin

I am trying to filter a list of objects based on a certain condition from a second list and then update/copy certain values from the second list to the already filtered list.
I tried this:
val filteredList = firstObjectList.stream()
.filter { first ->
secondObjectList.stream()
.anyMatch { second ->
second.sharedId == first.shareId
}
}.toList()
filteredList.map { filtered ->
secondObjectList.forEach { so ->
if(filtered.shareId == so.shareId){
val asset= Assets()
asset.address = so.address
asset.assetValue = so.assetValue
filtered.asset = asset
}
}
}
return filteredList
here are the objects:
Class firstObject(
val shareId: Int,
var asset : Asset? = null)
Class secondObject(
val shareId: Int,
var asset: Assets)
Class Assets(
val address: String,
val assetValue: Double)
This works but obviously not very efficient and Java based. How can I improve and write this in idiomatic kotlin? as i don’t seem to be able to chain operators correctly. Thanks in Advance.
val map = secondObjectList.associateBy { it.shareId }
val filteredList = firstObjectList
.filter { it.shareId in map }
.onEach { fo ->
fo.asset = map.getValue(fo.shareId).asset.let { Assets(it.address, it.assetValue) }
}