Duplicates values in Map: Kotlin - kotlin

I have a scenario whereby I need a map containing duplicate keys and values. I have created a list first and then I used associatedBy to convert them to a map, however the duplicates issue is not taken into account. Here is my implementation:
class State(private val startStatus: Status, private val expectedStatus: Status) {
companion object StatusList {
val listStatuses = listOf(
State(Status.A, Status.B),
State(Status.B, Status.A),
State(Status.B, Status.C),
State(Status.C, Status.B),
State(Status.C, Status.E),
State(Status.C, Status.D),
State(Status.D, Status.B),
State(Status.E, Status.C),
State(Status.E, Status.B)
)
open fun mapStatuses(): Map<Status, Collection<Status>> {
return listStatuses.associateBy(
keySelector = { key -> key.expectedStatus },
valueTransform = State::startStatus)
}
}
}
I am struggling to find a Multimap in Kotlin that would allow me to deal with duplicates. Can you help?
Thanks

In short, there is no multimap in Kotlin.
A multimap would allow multiple, equivalent keys with different values - this can be implemented with unique keys and a collection of values associated with given key, instead of having a view over collection of key-value pairs with equivalent keys.
Thus, you can use groupBy():
data class Record(val id: Int, val name: String)
fun main() {
val records = listOf(
Record(1, "hello"),
Record(1, "there"),
Record(2, "general"),
Record(2, "kenobi")
)
val mapped = records.groupBy({ it.id }, { it.name })
for (entry in mapped) {
println("${entry.key} -> ${entry.value.joinToString()}")
}
}
Here I am using groupBy with a projection of key (which is Record's id) and a projection of value (which is Record's name). Quite similar to your States and Statuses.

Related

How to map list of Triple into data class with nested list in kotlin

Sample
val listTriple = listOf<Triple<Int, Int, String>>()
data class Sample(val parentId: Int, val listItem : List<Item>)
data class Item(val id: Int, val name: String)
how to map listTriple into listOf Sample in kotlin in the best way
You can express that really concise by specifying groupBys valueTransform lambda:
val samples = listTriple.groupBy({ it.first }, { Item(it.second, it.third) }).map {
Sample(it.key, it.value)
}
But as EpicPandaForce mentioned in the comments it would be better to create a dedidacted class instead of using Triple. Having to refer to properties by first, second, third makes it hard to read.
Of course I could've just destructuring syntax here as well, but that doesn't solve the problem of not having a dedicated class.
Try this:
val samples = list
.groupBy { it.first }
.map {
val items = it.value.map { Item(it.second, it.third) }
Sample(it.key, items)
}
Assuming that the first Int of your triple is the parentId and the second one is the id, you would do this:
val listOfSample = listTriple.map {(parentId, id, name) ->
Sample(parentId, listOf(Item(id, name)))
}
If they are in the other order, just change the order in the '.map{}'
You can do groupBy for the secondary question of how to concatenate all lists based on the same parent id
val listOfSample = listTriple.map {(parentId, id, name) ->
Sample(parentId, listOf(Item(id, name)))
}.groupBy { it.parentId }

Convert String referential datatype to real referential datatypes

I have the following dataclasses:
data class JsonNpc(
val name: String,
val neighbours: JsonPreferences
)
data class JsonPreferences(
val loves: List<String>,
val hates: List<String>
)
I have a list of these, and they reference each other through strings like:
[
JsonNpc(
"first",
JsonPreferences(
listOf("second"),
listOf()
)
),
JsonNpc(
"second",
JsonPreferences(
listOf(),
listOf("first")
)
)
]
note that a likes b does not mean b likes a
I also have the Dataclasses
data class Npc(
val name: String,
val neighbours: NeighbourPreferences,
)
data class NeighbourPreferences(
val loves: List<Npc>,
val hates: List<Npc>
)
And I want to convert the String reference types to the normal reference types.
What I have tried:
recursively creating the npcs (and excluding any that are already in the chain, as that would lead to infinite recursion):
Does not work, as the Npc can not be fully created and the List is immutable (I dont want it to be mutable)
I have managed to find a way to do this. It did not work with Npc as a data class, as I needed a real constructor
fun parseNpcs(map: Map<String, JsonNpc>): Map<String, Npc> {
val resultMap: MutableMap<String, Npc> = mutableMapOf()
for (value in map.values) {
if(resultMap.containsKey(value.name))
continue
Npc(value, map, resultMap)
}
return resultMap
}
class Npc(jsonNpc: JsonNpc, infoList: Map<String, JsonNpc>, resultMap: MutableMap<String, Npc>) {
val name: String
val neighbourPreferences: NeighbourPreferences
init {
this.name = jsonNpc.name
resultMap[name] = this
val lovesNpc = jsonNpc.neighbours.loves.map {
resultMap[it] ?: Npc(infoList[it] ?: error("Missing an Npc"), infoList, resultMap)
}
val hatesNpc = jsonNpc.neighbours.hates.map {
resultMap[it] ?: Npc(infoList[it] ?: error("Missing an Npc"), infoList, resultMap)
}
this.neighbourPreferences = NeighbourPreferences(
lovesNpc, hatesNpc
)
}
}
data class NeighbourPreferences(
val loves: List<Npc>,
val hates: List<Npc>
)
checking in the debugger, the people carry the same references for each Neighbour, so the Guide is always one Npc instance.

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

Removing elements from a list which are not in another list - Kotlin

I have two mutableLists, listOfA has so many objects including duplicates while listOfB has fewer. So I want to use listOfB to filter similar objects in listOfA so all list will have equal number of objects with equivalent keys at the end. Code below could explain more.
fun main() {
test()
}
data class ObjA(val key: String, val value: String)
data class ObjB(val key: String, val value: String, val ref: Int)
fun test() {
val listOfA = mutableListOf(
ObjA("one", ""),
ObjA("one", "o"),
ObjA("one", "on"),
ObjA("one", "one"),
ObjA("two", ""),
ObjA("two", "2"),
ObjA("two", "two"),
ObjA("three", "3"),
ObjA("four", "4"),
ObjA("five", "five")
)
//Use this list's object keys to get object with similar keys in above array.
val listOfB = mutableListOf(
ObjB("one", "i", 2),
ObjB("two", "ii", 5)
)
val distinctListOfA = listOfA.distinctBy { it.key } //Remove duplicates in listOfA
/*
val desiredList = doSomething to compare keys in distinctListOfA and listOfB
for (o in desiredList) {
println("key: ${o.key}, value: ${o.value}")
}
*/
/* I was hoping to get this kind of output with duplicates removed and comparison made.
key: one, value: one
key: two, value: two
*/
}
If you want to operate directly on that distinctListOfA you may want to use removeAll to remove all the matching entries from it. Just be sure that you initialize the keys of B only once so that it doesn't get evaluated every time the predicate is applied:
val keysOfB = listOfB.map { it.key } // or listOfB.map { it.key }.also { keysOfB ->
distinctListOfA.removeAll {
it.key !in keysOfB
}
//} // if "also" was used you need it
If you have a MutableMap<String, ObjA> in place after evaluating your unique values (and I think it may make more sense to operate on a Map here), the following might be what you are after:
val map : MutableMap<String, ObjA> = ...
map.keys.retainAll(listOfB.map { it.key })
retainAll just keeps those values that are matching the given collection entries and after applying it the map now contains only the keys one and two.
In case you want to keep your previous lists/maps and rather want a new list/map instead, you may just call something like the following before operating on it:
val newList = distinctListOfA.toList() // creates a new list with the same entries
val newMap = yourPreviousMap.toMutableMap() // create a new map with the same entries
I tried this
primaryList.removeAll { primaryItem ->
secondaryList.any { it.equals(primary.id, true) }
}
PrimaryList here is a list of objects
SecondaryList here is a list of strings

Kotlin - from a list of Maps, to a map grouped by key

I have a List<Map<Branch,Pair<String, Any>>> that I would like to convert in a single Map<Branch,List<Pair<String, Any>>> .
So if I have an initial list with simply 2 elements :
List
1. branch1 -> Pair(key1,value1)
branch2 -> Pair(key2,value2)
2. branch1 -> Pair(key1a,value1a)
I want to end up with :
Map
branch1 -> Pair(key1,value1)
Pair(key1a,value1a)
branch2 -> Pair(key2,value2)
so a kind of groupBy, using all the values of the keys in the initially nested maps..
I have tried with
list.groupBy{it-> it.keys.first()}
but obviously it doesn't work, as it uses only the first key. I want the same, but using all keys as individual values.
What is the most idiomatic way of doing this in Kotlin ? I have an ugly looking working version in Java, but I am quite sure Kotlin has a nice way of doing it.. it's just that I am not finding it so far !
Any idea ?
Thanks
The following:
val result =
listOfMaps.asSequence()
.flatMap {
it.asSequence()
}.groupBy({ it.key }, { it.value })
will give you the result of type Map<Branch,List<Pair<String, Any>>> with the contents you requested.
val list: List<Map<Branch, Pair<String, Any>>> = listOf()
val map = list
.flatMap { it.entries }
.groupBy { it.key }
.mapValues { entry -> entry.value.map { it.value } }
I've managed to write this.
data class Branch(val name: String)
data class Key(val name: String)
data class Value(val name: String)
val sharedBranch = Branch("1")
val listOfMaps: List<Map<Branch, Pair<Key, Value>>> = listOf(
mapOf(sharedBranch to Pair(Key("1"), Value("1")),
Branch("2") to Pair(Key("2"), Value("2"))),
mapOf(sharedBranch to Pair(Key("1a"), Value("1a")))
)
val mapValues: Map<Branch, List<Pair<Key, Value>>> = listOfMaps.asSequence()
.flatMap { map -> map.entries.asSequence() }
.groupBy(Map.Entry<Branch, Pair<Key, Value>>::key)
.mapValues { it.value.map(Map.Entry<Branch, Pair<Key, Value>>::value) }
println(mapValues)
Is it appliable for your needs?
Everyone else is using flatMap, but you can also consider using fold, which is a common operation for reducing a larger collection into a smaller one. (For example, you can fold a list of integers into a single sum; here, a list of maps into a single map).
Perhaps others will find this easier to read than the flatMap versions above:
val listOfMaps: List<Map<Key, Value>> = ...
val mergedMaps: Map<Key, List<Value>> =
listOfMaps
.fold(mutableMapOf()) { acc, curr ->
curr.forEach { entry -> acc.merge(entry.key, listOf(entry.value)) { new, old -> new + old } }
acc
}
What the above code is doing:
Create a new, empty map. This will be acc (that is, the accumulator).
Iterate through our list of maps.
Work on one map (curr) at a time.
For the current map, run over each of its key/value pairs.
For each key/value, call merge on acc, passing in a list of size one (wrapping the value). If nothing is associated with the key yet, that list is added; otherwise, it is appended to the list already there.
Return the accumulating map, so it's used again in the next step.
Surprised nobody has mentioned the associate function.
val listy: List<Map<String, Int>> =
listOf(mapOf("A" to 1, "B" to 2), mapOf("C" to 3, "D" to 4))
val flattened = listy
.flatMap { it.asSequence() }
.associate { it.key to it.value }
println(flattened)
will print out {A=1, B=2, C=3, D=4}
Extract it to an extension function
private fun <K, V> List<Map<K, V>>.group(): Map<K, List<V>> =
asSequence().flatMap { it.asSequence() }.groupBy({ it.key }, { it.value })
Use it like so:
val list = yourListOfMaps
val grouped = list.group()