Kotlin - How to concatenate two maps? - kotlin

I have two immutable maps, as example:
val a = mapOf("a" to 1, "z" to 1)
val b = mapOf("b" to 1, "a" to 1, "c" to 1)
Is there any elegant way to concatenate these maps and have a single map with all the keys? If it has duplicated keys, I want to replace it with value from B map.
The result should look something like:
mapOf("z" to 1, "b" to 1, "a" to 1, "c" to 1)

simple + operator:
/**
* Creates a new read-only map by replacing or adding entries to this map from another [map].
*
* The returned map preserves the entry iteration order of the original map.
* Those entries of another [map] that are missing in this map are iterated in the end in the order of that [map].
*/
public operator fun <K, V> Map<out K, V>.plus(map: Map<out K, V>): Map<K, V> =
LinkedHashMap(this).apply { putAll(map) }
example:
fun main(args: Array<String>) {
val a = mapOf("a" to 1, "z" to 1)
val b = mapOf("b" to 1, "a" to 2, "c" to 1)
val c = a+b
println(c)
}
output: {a=2, z=1, b=1, c=1}

Related

Kotlin how apply different functions by predictor

I need to apply 2 different functions to my list in Kotlin based on external map, saving initial order of the list.
class Example(){
val mapper = mapOf<String, Int>(
"abc" to 1,
"de" to 0,
"fedd" to 1,
"q" to 1
)
fun function1(x:String)=x.length*2
fun function0(x:String)=x.length*3
fun function(lst: List<String>){
TODO("Need to apply function1 for all values in dictionary, where corresponding value in mapper is 1" +
"And apply function 0 for all values where corresponding value in mapper is 0")
}
}
So, for list ["q", "fedd", "de"] method Example.function() should return [2, 8, 6]
2 -> q value in mapper is 1, applying function1, multiplying by 2, get 1*2=2
8 -> fedd in mapper is 1, applying function1, multiplying by 2, get 4*2=8
6 -> de in mapper is 0, applying function0, multiplying by 3, get 2*3=6
What is the easiest way to realise this logic?
You could use map on the list to achieve this. follow below:
val mapper = mapOf<String, Int>(
"abc" to 1,
"de" to 0,
"fedd" to 1,
"q" to 1
)
fun function1(x: String) = x.length * 2
fun function0(x: String) = x.length * 3
fun function(lst: List<String>) {
val newList = lst.map {
when (mapper[it]) {
0 -> function0(it)
1 -> function1(it)
else -> error("Key doesn't exist in map, throw or return value as is.")
}
}
}

Transform Kotlin List to certain size by padding or concatenating the last n elements

I often need to either shorten or pad a List to a certain amount of entries. For that I use a function like this:
fun List<String>.compactOrPadEnd(size: Int): List<String> {
if (this.size < size)
return this + List(if (this.size < size) size - this.size else 0) { "" }
else
return this.subList(0, size - 1) + this.subList(size - 1, this.size).joinToString("")
}
val list0 = emptyList<String>()
val list1 = listOf("A")
val list2 = listOf("A", "B")
val list3 = listOf("A", "B", "C")
val list4 = listOf("A", "B", "C", "D")
val list5 = listOf("A", "B", "C", "D", "E")
val size = 3
list0. compactOrPadEnd(size).onEach(::println) // [ , , ]
list1. compactOrPadEnd(size).onEach(::println) // [A, , ]
list2. compactOrPadEnd(size).onEach(::println) // [A, B, ]
list3. compactOrPadEnd(size).onEach(::println) // [A, B, C]
list4. compactOrPadEnd(size).onEach(::println) // [A, B, CD]
list5. compactOrPadEnd(size).onEach(::println) // [A, B, CDE]
The above code is more readable with separate functions:
fun List<String>.padEnd(size: Int) =
this + List(if (this.size < size) size - this.size else 0) { "" }
fun List<String>.compact(size: Int) =
this.subList(0, size - 1) + this.subList(size - 1, this.size).joinToString("")
fun List<String>.compactAndPadEnd(size: Int): List<String> =
if (this.size < size) padEnd(size) else compact(size)
I find both solutions too clumsy. I went through all the built-in collection functions to come up with something simpler, but to no avail.
Small side question: is there a better name than compactAndPadEnd?
You can write this as one case (i.e. without if-else) if you take advantage of the fact that joinToString happens to return your pad element "" when the list is empty.
fun List<String>.resizeEnd(size: Int): List<String> =
this.subList(0, min(size - 1, this.size)) +
this.subList(min(size - 1, this.size), this.size).joinToString("") +
List(max(0, size - this.size - 1)) { "" }
Notice that I'm creating a list of size size - this.size - 1 at the end. -1 because one of the empty strings would have been the one returned by joinToString("").
If you don't mind drop and take creating extra lists, you can make it shorter:
fun List<String>.resizeEnd(size: Int): List<String> =
this.take(size - 1) +
this.drop(size - 1).joinToString("") +
List(max(0, size - this.size - 1)) { "" }
You can also generalise this to:
fun <T> List<T>.resizeEnd(size: Int, padElement: T, foldFunction: (T, T) -> T): List<T> =
this.subList(0, min(size - 1, this.size)) +
this.subList(min(size - 1, this.size), this.size).fold(padElement, foldFunction) +
List(max(0, size - this.size)) { padElement }
But the catch is that padElement must be the identity for foldFunction.
As an alternative, you could create a List of fixed size using an initializer function combined with a when to initialize each item:
fun List<String>.resize(size: Int) = List(size) {
when {
it == size - 1 && this.size > size -> this.subList(size - 1, this.size).joinToString("")
this.size > it -> this[it]
else -> ""
}
}
I'm not entirely sure if this is any less clumsy, I suppose that depends on personal preference.
I see some slight clean-up you can do on your function. You're redundantly checking this.size < size, you could lift return out of the condition branches, and you could use take/drop for brevity.
fun List<String>.compactOrPadEnd(size: Int): List<String> {
return if (this.size < size)
this + List(size - this.size) { "" }
else
take(size - 1) + drop(size - 1).joinToString("")
}
Personally, I'd call it concatEndOrPad.

Pair items in a list with one another in Kotlin

I want to pair items in a list with one another
Example
list("A","B","C") to -> list(Pair(A,B),Pair(A,C),Pair(B,C))
list("A","B","C","D") to -> list(Pair(A,B),Pair(A,C),Pair(A,D),Pair(B,C),Pair(B,D),Pair(C,D))
I have tried using zipWithNext, but it does not help my cause. If anyone can show me how I can achieve this?
You can simply nest for loops and use ranges for that:
fun permute(list: List<String>): List<Pair<String, String>> {
var result: MutableList<Pair<String, String>> = mutableListOf()
for (i in 0..(list.size - 1)) {
val s = list.get(i)
for (j in (i + 1)..(list.size - 1)) {
val p = Pair(s, list.get(j))
result.add(p)
}
}
return result
}
There might be ways that are more Kotlin style, but I don't know one at the moment...
Using this method in a fun main() like this
fun main() {
val list = listOf("A", "B", "C", "D")
println(permute(list))
}
will output
[(A, B), (A, C), (A, D), (B, C), (B, D), (C, D)]
kotlin way )
var a = listOf("A", "B", "C", "D")
var pairs = a.mapIndexed { index, s ->
a.slice(index + 1 until a.size).map { Pair(s, it)}
}.flatten()
print(pairs)
If you were looking for a Pair chaining here is how to do it:
fun main() {
val a = listOf("A", "B", "C", "D")
val listPair: MutableList<Pair<String, String>> = mutableListOf()
a.forEachIndexed{ index, _ ->
if (index != a.size - 1) {
val pair = Pair(a.get(index), a.get(index + 1))
listPair.add(pair)
}
}
println(listPair)
}
Result: [(A, B), (B, C), (C, D)]

Zip 3 lists of equal length

Is there a standard operation in Kotlin stdlib which would allow to iterate over a zip of 3 (or more) lists?
Effectively it should do:
list1.zip(list2).zip(list3) { (a, b), c -> listOf(a, b, c)}
Here are functions in the style of the standard library that do this. I'm not saying these are particularly optimized, but I think they're at least easy to understand.
/**
* Returns a list of lists, each built from elements of all lists with the same indexes.
* Output has length of shortest input list.
*/
public inline fun <T> zip(vararg lists: List<T>): List<List<T>> {
return zip(*lists, transform = { it })
}
/**
* Returns a list of values built from elements of all lists with same indexes using provided [transform].
* Output has length of shortest input list.
*/
public inline fun <T, V> zip(vararg lists: List<T>, transform: (List<T>) -> V): List<V> {
val minSize = lists.map(List<T>::size).min() ?: return emptyList()
val list = ArrayList<V>(minSize)
val iterators = lists.map { it.iterator() }
var i = 0
while (i < minSize) {
list.add(transform(iterators.map { it.next() }))
i++
}
return list
}
Usage:
val list1 = listOf(1, 2, 3, 4)
val list2 = listOf(5, 6)
val list3 = listOf(7, 8, 9)
println(zip(list1, list2, list3)) // [[1, 5, 7], [2, 6, 8]]

How to sum multiple elements after grouping in 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