How can I convert a Map<String, ArrayList<Int>> to a Map<String, Int>?
My objects are as follows:
val mapOfItems = mapOf("x" to listOf(1, 2), "y" to listOf(1, 3), "z" to listOf(2, 3))
I want to get an object like this:
val mapOfSummedItems = mapOf("x" to 3, "y" to 4, "z" to 5)
I tried:
mapOfItems.map { (key, value) -> SummedItems(key, value.sum()) }
.groupBy { it.key }
data class SummedItems(val key: String, val sum: Int)
That does not give me what I want that gives me an Map<String, ArrayList<SummedItems>>
.map() on a Map generates a List. While you can make this a List<Pair> and then use associate* operators to get back into a Map, you can also just map the values directly:
mapOfItems.mapValues { it.value.sum() }
Related
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.")
}
}
}
I'm looking for an idiomatic way of converting a list of pairs where Pair.first is a key and Pair.second is a list of values. This procedural approach works but I was hoping to find a more idiomatic way that doesn't require creating the mutable lists directly.
val pairs: Pair<String, List<Int>>
val res = mutableMapOf<String, List<Int>>()
pairs.forEach {
res.getOrPut(it.first, ::mutableListOf).addAll(it.second)
}
This code can get wrapped in an extension function like follows but it doesn't seem very generic:
fun <K, V> List<Pair<K, Collection<V>>>.toMultimap(): Map<K, List<V>> {
var res = mutableMapOf<K, MutableList<V>>()
forEach {
res.getOrPut(it.first, ::mutableListOf).addAll(it.second)
}
return res
}
Using pairs.toMap doesn't work because it overwrites map keys with a last in wins approach. groupBy works comes close, it creates keys to values in a list of lists structure.
val pairs2 = listOf(
Pair("a", listOf(1, 2, 3)),
Pair("b", listOf(6, 7)),
Pair("a", listOf(4, 5)),
Pair("b", listOf(8, 9)),
)
val res = pairs2.groupBy({ it.first }, { it.second })
println(res)
{a=[[1, 2, 3], [4, 5]], b=[[6, 7], [8, 9]]}
It is possible to then flatten the map but the downside here is that its pretty inefficient as this creates double the required hashmaps and lists per key (one for groupby and another for flatten). If there
val res = pairs2.groupBy({ it.first }, { it.second }).mapValues { it.value.flatten() }
println(res)
{a=[1, 2, 3, 4, 5], b=[6, 7, 8, 9]}
Looking to see if there are any better approaches to accomplishing this transform.
Rather than groupBy, use groupingBy, which produces a Grouping. This is an intermediate object on which you can do all kinds of fold/reduce operations. In your case:
fun <K, V> List<Pair<K, Collection<V>>>.toMultimap() =
groupingBy { it.first }
.fold(emptyList<V>()) { acc, (_, new) -> acc + new }
If you don't like the fact that + creates too many new lists, you can do something like this:
groupingBy { it.first }
.fold({ _, _ -> mutableListOf<V>() }) { _, acc, (_, new) ->
acc.addAll(new)
acc
}
val pairs: List<Pair<String, List<Int>>> = listOf(
Pair("a", listOf(1, 2, 3)),
Pair("b", listOf(6, 7)),
Pair("a", listOf(4, 5)),
Pair("b", listOf(8, 9)),
)
val result = pairs
.map { it.first }.distinct()
.associateWith { first -> pairs.filter { it.first == first }.flatMap { it.second } }
println(result) // {a=[1, 2, 3, 4, 5], b=[6, 7, 8, 9]}
Here's one way you could do it.
val pairs2 = listOf(
Pair("a", listOf(1, 2, 3)),
Pair("b", listOf(6, 7)),
Pair("a", listOf(4, 5)),
Pair("b", listOf(8, 9)),
)
val res = pairs2.flatMap { pair ->
pair.second.map { value ->
pair.first to value
}
}.groupBy({ it.first }, { it.second })
println(res) // {a=[1, 2, 3, 4, 5], b=[6, 7, 8, 9]}
Essentially, you flatten the list in each pair, preserving the original key, and then you convert the whole thing into your Map.
If you're worried about the lists taking up too much space, you could instead work with sequences, though maybe that's overkill.
val res = pairs2.asSequence().flatMap { pair ->
pair.second.asSequence().map { value ->
pair.first to value
}
}.groupBy({ it.first }, { it.second })
The core idea here is that flatMap is excellent at allowing you to add/remove elements from your original list on the fly.
I have a List like [1,2,3,4,5] and I am trying to convert to List ["1","2","3","4","5"]
I tried doing it like this
val numbers = listOf(1, 2, 3, 4, 5)
val numbersStr = mutableListOf<String>()
val itr = numbers.listIterator()
while(itr.hasNext())
{
numbersStr.add(itr.next().toString())
}
but I feel it is little verbose and doesnt make use of Kotlin's built in functions.
What is the best alternative?
Check out kotlin's map function
val numberStr = listOf(1, 2, 3, 4, 5).map { it.toString() }
fun main() {
val list = listOf(1, 2, 3, 4, 5)
if (list.isNotEmpty()) {
Nel(list[0], list.subList(1, list.lastIndex))
}
}
According to arrow documents, it seems be able to do it through Semigroup or Monad binding.
However, there is no code to replace List with NonEmptyList.
Is there a better way to replace List withNonEmptyList without using subList?
There is a companion function fromList that return an Option<NonEmptyList> or if you are sure use fromListUnsafe:
val list = listOf(1, 2, 3, 4, 5)
val nelistOption = NonEmptyList.fromList(list)
val nelist = NonEmptyList.fromListUnsafe(list)
Arrow 1.1.3 has introduced a more Koltin idiomatic way to achieve this with the introduction of Iterable.toNonEmptyListOrNull(): NonEmptyList?
Example:
val list: List<Int> = listOf(1, 2, 3)
val nonEmptyList: NonEmptyList<Int>? = list.toNonEmptyListOrNull()
To get the old Option type one can use the toOption() extension function
val list: List<Int> = listOf(1, 2, 3)
val nonEmptyList: Option<NonEmptyList<Int>> = list.toNonEmptyListOrNull().toOption()
or use Option.fromNullable()
val list: List<Int> = listOf(1, 2, 3)
val nonEmptyList: Option<NonEmptyList<Int>> = Option.fromNullable(list.toNonEmptyListOrNull())
The NonEmptyList.fromList() function is now deprecated in favor of toNonEmptyListOrNull.
How to collect to Map from List where null values are excluded/skipped?
This code doesn't skip null values:
val map = listOf(Pair("a", 1), Pair("b", null), Pair("c", 3), Pair("d", null))
.associateBy({ it.first }, { it.second })
println(map)
Workaround solution. But collects into mutable map:
val map2 = listOf(Pair("a", 1), Pair("b", null), Pair("c", 3), Pair("d", null))
.mapNotNull {
if (it.second != null) it else null
}.toMap()
println(map2)
So is there more convenient way to do this? Also I want to get Map<String, Int> type, not Map<String, Int?>
Actually, a slight change to pwolaq's answer guarantees that the second item is non-nullable:
val map = listOf(Pair("a", 1), Pair("b", null), Pair("c", 3), Pair("d", null))
.mapNotNull { p -> p.second?.let { Pair(p.first, it) } }
.toMap()
println(map)
This will give you a Map<String, Int>, since mapNotNull ignores anything that maps to null, and using let with the safe call operator ?. returns null if its receiver (p.second) is null.
This is basically what you stated in your question, made shorter with let.
You want to filter out null values, then you should use filter method:
val map = listOf(Pair("a", 1), Pair("b", null), Pair("c", 3), Pair("d", null))
.filter { it.second != null }
.toMap()
println(map)
A more readable solution might be using the associateBy function with the double bang expression (!!):
val map: Map<String, Int> = listOf(
Pair("a", 1),
Pair("b", null),
Pair("c", 3),
Pair("d", null)
)
.filter { it.first != null && it.second != null }
.associateBy({ it.first!! }, { it.second!! })
println(map)
Since Kotlin 1.6, there is also a stable buildMap function that can be used to write custom helper functions that are performant without sacrificing readability:
fun <T, K : Any, V : Any> Iterable<T>.associateByNotNull(
keySelector: (T) -> K?,
valueTransform: (T) -> V?,
): Map<K, V> = buildMap {
for (item in this#associateByNotNull) {
val key = keySelector(item) ?: continue
val value = valueTransform(item) ?: continue
this[key] = value
}
}
Note that writing this as a "low-level" for loop eliminates the need for the creation of intermediate collections.