Sorted map with custom comparator - kotlin

I want to create a sorted map with a composite key. If it didn't need to be sorted, I would use
val myMap = mapOf(
Pair(1,"a") to "A",
Pair(2,"a") to "AA",
Pair(1,"b") to "B"
)
But it has to be a sorted map and this doesn't work:
val myMap = sortedMapOf(
Pair(1,"a") to "A",
Pair(2,"a") to "AA",
Pair(1,"b") to "B"
)
What's the most idiomatic way to create a sorted map with a custom comparator? I want it to compare by the first element of the pair and then by the second.

sortedMapOf has an overload that takes a comparator as its first argument.
val myMap = sortedMapOf(
compareBy<Pair<Int, String>> { it.first } then compareBy { it.second },
Pair(1,"a") to "A",
Pair(2,"a") to "AA",
Pair(1,"b") to "B",
)

Related

How get Map<K, V> from groupBy instead of Map<K, List<V>>?

groupBy transforms a list to a map:
Map<K, List<V>>
in a such manner:
val words = listOf("a", "abc", "ab", "def", "abcd")
val byLength = words.groupBy { it.length }
println(byLength.keys) // [1, 3, 2, 4]
println(byLength.values) // [[a], [abc, def], [ab], [abcd]]
I wanna get a Map<K, V> - the same, but values are reduced to a single value, let's say get the first element from the values list:
println(byLength.values) // [a, abc, ab, abcd]
what is the easiest way to get it? Can groupBy provide it by using 2nd parameter? Or need transform Map<K, List> further to Map<K, V>?
You don't need groupBy, you can simply write :
words.map { it.length to it }.toMap()
you are creating map entries from list and then creating map from these entries
If the last element that matches your keySselector can be the value
words.associateBy { it.length } //[a, def, ab, abcd]
If you want the first element that matches your selector you can still use groupBy
words.groupBy { it.length }.mapValues { it.value.first() } //[a, abc, ab, abcd]

Kotlin: How to flatten list of Hashmaps

How can I flatten a list of HashMaps in Kotlin?
var listOfMaps: List<Map<String, String>> = listOf(mapOf("test" to "test1"), mapOf("test2" to "test3"), mapOf("test4" to "test5"))
I would like to get:Map<String,String> with all key value paires
val map:Map<String, String> = listOfMaps
.flatMap { it.entries }
.associate { it.key to it.value }
you can do something like this if you don't know if the list can be empty.
val map = listOfMaps.fold(mapOf<String, String>()) {acc, value -> acc + value }
If the list never will be empty you can use reduce instead.
Thank you Demigod for the comments
You could use fold:
listOfMaps.fold(
mutableMapOf<String, String>(),
{ acc, item -> acc.also { it.putAll(item) } }
)
The first parameter mutableMapOf<String, String>() creates an empty map to put the values into. This is called the accumulator
The second parameter is a function which takes two arguments
The accumulator
An item from the original list
This function is run sequentially against all items in the list. In our case it adds all the items from each map to the accumulator.
Note: This function does not account for duplicate keys. If a later map has the same key as an earlier one then the value just gets overridden.
Also note (pun intended): We use acc.also {} as we want to return the actual map, not the return value from the addAll method
Well... seeing lots of solutions, I will add my two cents here:
If you don't mind losing the values of duplicated keys you can use something as follows:
listOfMaps.flatMap { it.entries }.associate{ it.key to it.value } // or: it.toPair() if you will
// depending on how large those lists can become, you may want to consider also using asSequence
If you instead want to collect all entries including duplicate keys (i.e. saving all the values), use the following instead (which then gives you a Map<String, List<String>>):
listOfMaps.flatMap { it.entries }.groupBy({ it.key }) { it.value }
Also here the comment regarding asSequence holds...
Finally if you can omit those maps within the list and just use a Pair instead, that will spare you the flatMap { it.entries }-call and make things even easier, e.g. you could just call .toMap() then for the first case and groupBy directly for the second and the question regarding asSequence no longer arises.
An extra addition to this, if you have single value maps, maybe you want to switch to a List<Prair<String, String>>. In that case, the solution is straight forward:
You would have something like:
var listOfMaps: List<Pair<String, String>> = listOf("test" to "test1", "test2" to "test3", "test4" to "test5")
and toMap() would dsolve it all:
listOfMaps.toMap()
If you don't have duplicate keys or don't care for them, you can do it like this:
val flattenedMap = listOfMaps.flatMap { it.toList() }.toMap()
If you have duplicate keys and care for them, you can do it like this:
val flattenedMap = mutableMapOf<String, MutableList<String>>().apply {
listOfMaps.flatMap { it.toList() }.forEach {
getOrPut(it.first) {
mutableListOf()
}.add(it.second)
}
}
The result will be Map<String, List<String>> then of course.
This solution works with same keys (and different collection as values) by merging them together instead of overwriting them
/**
* Merge two maps with lists
*/
fun <K,V>Map<K,Collection<V>>.mergeLists(other: Map<K,Collection<V>>) =
(this.keys + other.keys).associateWith {key ->
setOf(this[key], other[key]).filterNotNull()
}.mapValues { (_,b) -> b.flatten().distinct() }
/**
* Merge two maps with sets
*/
fun <K,V>Map<K,Collection<V>>.mergeSets(other: Map<K,Collection<V>>) =
(this.keys + other.keys).associateWith {key ->
setOf(this[key], other[key]).filterNotNull()
}.mapValues { (_,b) -> b.flatten().toSet() }
Then use like e.g. listOfMaps.reduce { a, b -> a.mergeSets(b) }
Test:
#Test
fun `should merge two maps with as lists or sets`() {
// GIVEN
val map1 = mapOf(
"a" to listOf(1, 2, 3),
"b" to listOf(4, 5, 6),
"c" to listOf(10),
"e" to emptyList()
)
val map2 = mapOf(
"a" to listOf(1, 9),
"b" to listOf(7),
"d" to listOf(null)
)
// WHEN
val mergedAsLists = map1.mergeLists(map2)
val mergedAsSets = map1.mergeSets(map2)
// THEN
listOf(mergedAsLists, mergedAsSets).forEach { merged ->
assertThat(merged.keys).containsOnly("a", "b", "c", "d", "e")
assertThat(merged["a"]).containsOnly(1,2,3,9)
assertThat(merged["b"]).containsOnly(4,5,6,7)
assertThat(merged["c"]).containsOnly(10)
assertThat(merged["d"]).containsOnly(null)
assertThat(merged["e"]).isEmpty()
}
}

Kotlin FP : Convert List<String> to Map<String,Int>

I have a List of String, I want to transform into a Map of occurrences. ( ~ The Map values are the count of many times the String was repeated in the List)
The imperative way, I'd write like the following
fun transformMap(list: List<String>): Map<String, Int> {
val map = mutableMapOf<String,Int>()
for(n in list){
map.put(n,map.getOrDefault(n,0) + 1)
}
return map.toMap()
}
How to write this in Functional Programming way ?
In Java 8+, this will be written like this
String[] note;
Map<String, Integer> noteMap = Arrays.stream(note)
.collect(groupingBy(Function.identity(),
collectingAndThen(counting(), Long::intValue)));
You can use Kotlin's Grouping to do this in one line via the Iterable<T>.groupingBy extension:
val myList = listOf("a", "b", "c", "a", "b", "a")
val myMap = myList.groupingBy { it }.eachCount()
println(myMap)
// Prints {a=3, b=2, c=1}
You can use streams in Kotlin too. But if you want to avoid streams, you can use fold():
val list = listOf("a", "b", "c", "a")
val histogram = list.fold(mutableMapOf<String, Int>()) { map, s ->
map[s] = map.getOrDefault(s, 0) + 1
map
}.toMap()
println(histogram)

merge every two equal elements into another one

How to merge every two equal elements in the list into one element and the merge process is specified by lambda function.
For example if I have a list like this
["a","a","b","b"]
And the merge process is {it.repeat(2)}
it should be like this:
["aa","bb"]
but if the list is like this:
["a","a","a"]
It should be converted to
["aa","a"]
Another example is :
["a","b","a","b"]
will be
["a", "b","a","b"]
I tried zipWithNext() but I can't figure out what to do next especially when there is more than two repeat of the same letter.
How to implement like this in Kotlin using functional programming way of Kotlin.
Here comes the very inefficient but, desired solution. As requested, I've used functional programming as much as possible. Hope you'll like it. But, it would be very easy if you use loop or regular expression to solve this problem.
In this question functional style is not needed.
fun main(args: Array<String>) {
val list = listOf("a", "a", "a", "a", "b", "b", "b", "b")
//val list = listOf("a", "a", "a", "b", "a")
var temp = false
val result = list.zipWithNext().map {
if (temp) {
temp = false
null
} else {
if (it.first == it.second) {
temp = true
it.first + it.second
} else {
it.first
}
}
}.filter { !it.isNullOrBlank() }.toMutableList()
if (!temp) {
result.add(list.last())
}
print(result)
}

How to initiate String array in Kotlin?

I would like to know how to start an array similar to this String [] errorSoon = {"Hello", "World"}; in Kotlin. How is done?
You can use arrayOf() fuction as it described in Kotlin Basic Type article.
Your code will be the next:
val errorSoon = arrayOf("Hello", "World")
Declare array at global
names = arrayOf(String())
Now in onCreate method Initialize you array with value
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
//Init array here
names = arrayOf("Friends List", "Notification")
}
val array= arrayOf("Hello", "World")
And there is one more way to create String Array.
// Creates an Array<String> with values ["0", "1", "4", "9", "16"]
val asc = Array(5, { i -> (i * i).toString() })
Try this arrayOf() to create array in Kotlin
val errorSoon = arrayOf("a", "b", "c")
to get values from array use below code
for (i in errorSoon.indices) {
print(errorSoon[i]+" ")
}
You can read more here about it
val errorSoon = arrayOf("Hello", "World")
array of strings can be initialized with square brackets too.
values = [ "a", "b" ]