How to sum multiple elements after grouping in Kotlin - kotlin

I have a list of objects A (alist).
A {
val b : Int
val c1 : Int
val c2 : Int
val d1 : Int
val d2 : Int
}
and I want to group them by b and calculate sum of c1+c2 and d1+d2 on each group and put the results in list of E objects elist.
E {
val sum_of_c_types : Int
val sum_of_d_types : Int
}
How do I achieve in kotlin using any collection inbuilt function?
note:
I know I can do it with reduce function and create temporary A objects, but this is important to dont use temporary A object in code.

I've solved it by using a sequence of groupBy, map and sumBy. It's probably not the cleanest solution I guess.
data class A(val b: Int,
val c1: Int,
val c2: Int,
val d1: Int,
val d2: Int)
data class E(val sumC: Int, val sumD: Int)
fun main(args: Array<String>) {
val alist = listOf(A(1, 2, 1, 4, 5), A(1, 3, 4, 6, 3), A(2, 2, 2, 2, 2), A(3, 1, 2, 1, 2))
val grouped: Map<Int, E> = alist.groupBy(A::b).mapValues {
E(it.value.sumBy { it.c1 + it.c2 }, it.value.sumBy { it.d1 + it.d2 })
}
grouped.forEach {
println("Group b=${it.key}: ${it.value}")
}
}
Results in:
Group b=1: E(sumC=10, sumD=18)
Group b=2: E(sumC=4, sumD=4)
Group b=3: E(sumC=3, sumD=3)
Edit:
With Grouping (using groupingBy instead of groupBy), it looks even better because you don't have to handle map entities:
val grouped = alist.groupingBy(A::b).aggregate { _, acc: E?, e, _ ->
E((acc?.sumC ?: 0) + e.c1 + e.c2, (acc?.sumD ?: 0) + e.d1 + e.d2)
}

I think it's just grouping with folding
fun group(a: List<A>) = a.groupingBy(A::b).fold(E(0, 0),
{ acc, elem ->
E(acc.sum_of_c_types + elem.c1 + elem.c2,
acc.sum_of_d_types + elem.d1 + elem.d2)
})

I solved it by following code:
alist.groupby { b }. mapValues {
it.value.map {
E(it.c1+it.c2, it.d1+it.d2)
}.reduce {
acc, e -> E(acc.sum_of_c_types + e.sum_of_c_types, acc.sum_of_d_types + e.sum_of_d_types)
}.values

Related

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

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
}

Kotlin: groupBy and convert to other object

I have a list of objects
data class OldFormat(ShiftId: Int, NozzleValue: Int, NozzleId: Int , UserId: Int)
which I want to group by two of fields "shiftId and userId" and then subtract the maximum value of each group from the minimum value of the same group and then
sum the result and then convert it to a new object with this class:
data class NewFormat(ShiftId: Int, NozzleValue: Int, UserId: Int)
The process would be like this:
listOfOldFormat -> groupby(shiftId, userId) -> sum(maximumValue-minimumValue) -> listOfNewFormat
We do have groupBy function, so we have everything we need to.
I am not sure what do you mean by subtract the maximum value of each group from the minimum value of the same group and then sum the result (what should I sum?), so I did it as group.value.max - group.value.min and it's nozzeValue for NewFormat.
Code snippet:
data class OldFormat(val shiftId: Int, val nozzleValue: Int, val nozzleId: Int, val userId: Int)
data class NewFormat(val shiftId: Int, val nozzleValue: Int, val userId: Int)
fun main() {
val old = listOf(
OldFormat(0, 10, 10, 0),
OldFormat(0, 120, 10, 1),
OldFormat(1, 11, 8, 10),
OldFormat(0, 10, 1, 1),
OldFormat(1, 50, 10, 10)
) // Example data
old.groupBy {
it.shiftId to it.userId // After it we have Map<Key, List<OldFormat>>, where key is pair of shiftId and userId
}.map { entry ->
val max = entry.value.maxBy { it.nozzleValue }?.nozzleValue ?: 0
val min = entry.value.minBy { it.nozzleValue }?.nozzleValue ?: 0
entry.key to (max - min) // Just do whatever you want with that data
}.map {
NewFormat(
shiftId = it.first.first,
userId = it.first.second,
nozzleValue = it.second
) // And now map it into type you want
}.let {
println(it) // Just for seeing result
}
}

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

Sum a subset of of numbers in a list

Is there a way in Kotlin for doing the sum() operation on a filtered list of numbers, without actually filtering out the elements first?
I'm looking for something like this:
val nums = listOf<Long>(-2, -1, 1, 2, 3, 4)
val sum = nums.sum(it > 0)
You can make use of Iterable<T>.sumBy:
/**
* Returns the sum of all values produced by [selector] function applied to each element in the collection.
*/
public inline fun <T> Iterable<T>.sumBy(selector: (T) -> Int): Int {
var sum: Int = 0
for (element in this) {
sum += selector(element)
}
return sum
}
You can pass a function to it where the function transforms negative value to 0. So, it sums up all values in the list which is greater than 0 since adding 0 makes no effect to the result.
val nums = listOf<Long>(-2, -1, 1, 2, 3, 4)
val sum = nums.sumBy { if (it > 0) it.toInt() else 0 }
println(sum) //10
If you require a Long value back, you have to write an extension for Long just like Iterable<T>.sumByDouble.
inline fun <T> Iterable<T>.sumByLong(selector: (T) -> Long): Long {
var sum: Long = 0
for (element in this) {
sum += selector(element)
}
return sum
}
Then, the toInt() conversion can be taken away.
nums.sumByLong { if (it > 0) it else 0 }
As suggested by #Ruckus T-Boom, if (it > 0) it else 0 can be simplified using Long.coerceAtLeast() which returns the value itself or the given minimum value:
nums.sumByLong { it.coerceAtLeast(0) }
sumBy and sumByDouble are Deprecated from kotlin 1.5 . You can check those link.
Use sumOf to get sum on a List or Array
sumOf
Returns the sum of all values produced by selector function applied to each element in the collection or Array.
Example:
data class Order(
val id : String,
val price : Double
)
val orderList = ......
val sum = orderList.sumOf { it.price }
data class Product(val name: String, val quantity: Int) {
}
fun main(args: Array<String>) {
val productList = listOf(
Product("A", 100),
Product("B", 200),
Product("C", 300)
)
val totalPriceInList1: Int = productList.map { it.quantity }.sum()
println("sum(): " + totalPriceInList1)
val totalPriceInList2: Int = productList.sumBy { it.quantity }
println("sumBy(): " + totalPriceInList2)
}
this is the result of our code
sum(): 600
sumBy(): 600