How to make efficient list from particular object in kotlin - kotlin

Hey I have list of object in which I want to combine title property value into list of object. I did this successfully without any problem. My main questions is there any better approach to do in kotlin which helps in efficient, better memory management and everything when eventList is bigger in size.
CombineList.kt
fun main() {
val newList = mutableListOf<String>()
val eventList = createData()
eventList.forEachIndexed { _, event ->
val title = event.title
if (!title.isNullOrEmpty()) {
newList.add(title)
}
}
println(newList)
}
data class Event(val title: String? = null, val status: String? = null)
fun createData() = listOf(
Event("text 1", "abc"),
Event("text 2", "abc"),
Event("text 3", "abc"),
Event("text 4", "abc"),
Event("", "abc"),
Event(null, "abc")
)

Instead of creating a new list and iteratively add each element in a forEach, you can use map which yields a new list with all the results of mapping the given function to each element:
val newList = eventList.map { it.title }.filterNot { it.isNullOrEmpty() }
The filterNot works in a similar way, giving you a list of all elements in a list, that do not fulfill a given predicate.
An overview of common operations for collections can be found here.

Instead of creating a list of all titles with map and then filter out null and empty values it might be better to create the list with mapNotNull (and to convert empty strings to null in its lambda):
val newList = createData().mapNotNull { it.title?.ifEmpty { null } }

Related

How to use destructuring declarations to get multiple value from single map iteration?

data class Person(val name: String, val age: String, val color: String)
val persons = listOf(Person("john", "20", "green"), Person("Mary", "20", "blue"))
val names = persons.map {it.name}
val ages = persons.map {it.age}
val colors = persons.map {it.color}
// I will need 3 iteration to get my names and ages and colors
val personsMap = persons.map { it.name to it.age }
val (names2, ages) = personsMap.unzip()
// I am using 1 iteration but with Map and uzip(), I can get names2,
// ages within a single iteration
// Now if I wish to get color
// By using map, or alternatives call helper.. can I retrieve names,
// ages and colors at one go
// I do know that we can use iteration forEachloop and add into a
// set/list, but
// are there better way of doing it
// val (names, ages, colors)
What are the alternative way to write to get multiple value in a single iteration to get its list from destructing.
In order to collect lists of different properties, at least one iteration is required. this can be done by defining an extension function as
fun List<Person>.collectProperties(): Triple<List<String>, List<String>, List<String>>{
val (names, ages, colors) = listOf(mutableListOf<String>(), mutableListOf(), mutableListOf())
this.forEach { person ->
names.add(person.name)
ages.add(person.age)
colors.add(person.color)
}
return Triple(names, ages, colors)
}
now you can use this as
val persons = listOf(Person("john", "20", "green"), Person("Mary", "20", "blue"))
val(names, ages, colors) = persons.collectProperties()
Your toList() call is redundant, so you're iterating 3 times instead of twice.
I can't think of a way to do this with a single iteration using standard library higher-order functions. You could write a function like this that maps and unzips in one step so it only iterates once:
inline fun <T, U, V> Iterable<T>.doubleMap(transform: (T) -> Pair<U, V>): Pair<List<U>, List<V>> {
val size = if (this is Collection<*>) size else 10
val listU = ArrayList<U>(size)
val listV = ArrayList<V>(size)
for (t in this) {
with (transform(t)) {
listU += first
listV += second
}
}
return listU to listV
}

Duplicates values in Map: 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.

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

Mapping an iterable 1-to-n in kotlin

What I am trying to do is use the Iterable.map but instead of transforming every one value to one new value, I want to transform one value to multiple new values.
For example:
val myList = listOf("test", "123", "another.test", "test2")
val result = myList.map {
if(it.contains(".")) {
return#map it.split(".")
} else {
return#map it
}
}
//desired output: ["test", "123", "another", "test", "test2"]
This code would result in a List which contains both strings and lists of strings (type Any).
How could I most elegantly implement this?
One quick way to do this is using flatMap.
val output = myList.flatMap { if(it.contains(".")) it.split(".") else listOf(it) }
The flatMap method transforms the each element using given function and then flatten the result to a single list.
https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/flat-map.html