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

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)

Related

How to find select the elements that are in one arraylist that are not in another arraylist in kotlin

I am attempting to find the difference of 2 arraylists in kotlin i.e the elements in the first arraylist that are not included in the second arraylist. The code below I believe would work for listOf but I need it to work for arraylists of strings structure instead with the same names i.e first, second and difference should all be arraylists of strings.
fun main() {
private var first = ArrayList<String>()
private var second = ArrayList<String>()
private var difference = ArrayList<String>()
first.add(“a”)
first.add(“b”)
first.add(“c”)
first.add(“d”)
first.add(“e”)
second.add(“a”)
second.add(“b”)
second.add(“c”)
val difference = first.minus(second)
println(difference) // [d, e]
}
You can use arrayListOf:
fun main() {
val first = arrayListOf("a", "b", "c", "d", "e")
println("first type: %s".format(first.javaClass.kotlin.qualifiedName))
val second = arrayListOf("a", "b", "c")
println("second type: %s".format(second.javaClass.kotlin.qualifiedName))
val difference = first.minus(second)
println(difference)
println("difference type: %s".format(difference.javaClass.kotlin.qualifiedName))
}
Or instead, add the elements to the ArrayList's one by one if desired:
fun main() {
val first = ArrayList<String>()
first.add("a")
first.add("b")
first.add("c")
first.add("d")
first.add("e")
println("first type: %s".format(first.javaClass.kotlin.qualifiedName))
val second = ArrayList<String>()
second.add("a")
second.add("b")
second.add("c")
println("second type: %s".format(second.javaClass.kotlin.qualifiedName))
val difference = first.minus(second)
println(difference)
println("difference type: %s".format(difference.javaClass.kotlin.qualifiedName))
}
Output:
first type: java.util.ArrayList
second type: java.util.ArrayList
[d, e]
difference type: java.util.ArrayList

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

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

kotlin, how convert a EnumMap into regular Map

need to call a api which take Map<String, String>
fun api.log(map: Map<string, String>
but the key has to be only from the registered ones, so defined a enum for the registered keys:
enum class RegisteredKey {
NONE, ZOOM
}
and first build the EnumMap<> to enforce key type:
var enumParamMap: EnumMap<RegisteredKey, String> = EnumMap<RegisteredKey, String>(RegisteredKey::class.java)
enumParamMap.put(RegisteredKeys.NONE, "0")
enumParamMap.put(RegisteredKeys.ZOOM, "1")
doLog(enumParamMap)
question 1, is there constructor to build the enumMap directly with data?
and then need to transform the EnumMap into a Map<> so that the api.log() will accept it
fun doLog(enumParamMap: EnumMap<RegisteredKey, String>) {
val map: MutableMap<String, String> = mutableMapOf()
for (enum in enumParamMap.entries) {
map.put(enum.key.name, enum.value)
}
api.log(map)
}
question 2: is there simpler way to map the enumMap to regular map?
I'm not sure I'm interpreting your first question correctly, but if you mean you want to initialize an exhaustive EnumMap where every key has an entry, similar to the Array constructor that takes a lambda, you could write one like this:
inline fun <reified K : Enum<K>, V> exhaustiveEnumMap(init: (key: K) -> V): EnumMap<K, V> {
val map = EnumMap<K, V>(K::class.java)
for (key in enumValues<K>())
map[key] = init(key)
return map;
}
Usage:
val map = exhaustiveEnumMap<RegisteredKey, String> { key -> key.ordinal.toString() }
or
val map = exhaustiveEnumMap<RegisteredKey, String> { key ->
when (key){
RegisteredKey.NONE -> "0"
RegisteredKey.ZOOM -> "1"
}
}
Edit based on your comment: You could do that by wrapping a mapOf call with the EnumMap constructor like this, but it would be instantiating an intermediate throwaway LinkedHashMap:
val map = EnumMap(mapOf(RegisteredKey.NONE to "0", RegisteredKey.ZOOM to "1"))
Instead, you could write a helper function like this:
inline fun <reified K: Enum<K>, V> enumMapOf(vararg pairs: Pair<K, V>): EnumMap<K, V> =
pairs.toMap(EnumMap<K, V>(K::class.java))
Usage:
var enumParamMap = enumMapOf(RegisteredKey.NONE to "0", RegisteredKey.ZOOM to "1")
-------
For your next question, I'm not sure if this is really any simpler, but you could do:
fun doLog(enumParamMap: EnumMap<RegisteredKey, String>) =
api.log(enumParamMap.map{ it.key.name to it.value }.toMap())
It's more concise, but you're allocating a list and some pairs that you wouldn't be with your way of doing it.
Something like this should work for initialization:
EnumMap<RegisteredKey, String>(
mapOf(RegisteredKey.NONE to "0", RegisteredKey.ZOOM to "1")
)
To get a normal map just call .toMutableMap():
EnumMap<RegisteredKey, String>(
mapOf(RegisteredKey.NONE to "0", RegisteredKey.ZOOM to "1")
).toMutableMap()

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