How to group objects by values - kotlin

I have an object that looks like this:
data class Product(val name: String,
val maker: List<String>)
Currently, the response that I receive from the backend (and it can't be changed) is as follows:
[{"name":"Car", "maker":["Audi"]},
{"name":"Car", "maker":["BMW"]},
{"name":"Motorcycle", "maker":["Yamaha"]},
{"name":"Motorcycle", "maker":["Kawasaki"]}
]
The actual list consists of a lot of data, but the name field can be trusted to be grouped by.
What would be a way for me to map this data so that the end result is something like this:
[{"name":"Car", "maker":["Audi", "BMW"]},
{"name":"Motorcycle", "maker":["Yamaha","Kawasaki"]}
]

Just use groupBy { ... } and then process the groups map entries, replacing them with a single Product:
val result = products.groupBy { it.name }.entries.map { (name, group) ->
Product(name, group.flatMap { it.maker })
}

Related

Remove values with some similarities in a map using kotlin streams

I have a map<String, MyObject> where the values in the map could have same values for some variables (e.g., name parameter in my example). I would appreciate any solution using streams to remove entries with same name parameter on the value and keep only one of them with minimum id.
data class MyObject(val id: Int, val name: String)
For instance my map could be:
[
"first" to MyObject(1, "Alice"),
"second" to MyObject(2, "Bob"),
"third" to MyObject(3, "Alice")
]
and the expected output is:
[
"first" to MyObject(1, "Alice"),
"second" to MyObject(2, "Bob")
]
where the entry with key third is removed because the value has the same name as the first entry.
First, we need to identify all of the duplicate candidates. We can do that with groupBy, which works on any iterable (and Map is iterable with iteratee type Entry<K, V>).
myMap.entries
.groupBy({ entry => entry.value.name })
This produces a value of type Map<String, List<Entry<String, MyObject>>>.
Now, for each value in the map, we want to choose the element in the list with the smallest ID. We can select the minimum element by some condition using minBy and can do that to each element of a map with mapValues.
myMap.entries
.groupBy({ entry => entry.value.name })
.mapValues({ entry => entry.value.minBy({ it.value.id })!! })
(Note: groupBy always produces nonempty lists, since it's partitioning a set, so we can confidently !! assert that a minimum exists)
Finally, this returns a Map<String, Entry<String, MyObject>>, and you probably want to eliminate the excess Map layer.
myMap.entries
.groupBy({ entry -> entry.value.name })
.mapValues({ entry -> entry.value.minBy({ it.value.id })!! })
.values
.associate({ it.key to it.value })
Try it online!
There are multiple ways to do this using pure Kotlin, here is one relying on the fact that hash-maps do not allow duplicates. I am sure there are better solutions out there:
values.toList()
// If you care about the smaller id number value
// then sort by descending so they replace larger values.
.sortedByDescending { it.second.id }
// Will replace duplicates by hashing technique
.associateBy { it.second.name }
// Back to the same data structure
.map { it.value.first to it.value.second }.toMap()
Try it online!

Kotlin - Filter list with values from another list

This is probably super simple but I just cannot figure out how to google for that.
What I have is:
data class Post(val id: String)
val ids = listOf("1", "5", "19")
val posts = listOf<Post>(post1, post2, post3 etc)
now I want to filter posts list with the ids list.
This is how I filter one id:
val output = posts.filter{ it.id == ids[0]}
but how do I filter for all the items in "ids" list?
You can use a small modification of the code you wrote to filter out a single Post by checking if ids contains the id of a Post instead of comparing it only to the first value in ids:
fun main() {
// list of ids to be filtered
val ids = listOf("1", "5", "19")
// list of posts to filter from
val posts = listOf(
Post("1"), Post("2"),
Post("3"), Post("5"),
Post("9"), Post("10"),
Post("15"), Post("19"),
Post("20")
)
// filter a single post that matches the first id from ids
val singleOutput = posts.filter { it.id == ids[0] }
// filter all posts that have an id contained in ids
val multiOutput = posts.filter { ids.contains(it.id) }
// print the single post with the matching id
println(singleOutput)
// print the list of posts with matching ids
println(multiOutput)
}
The output of this is
[Post(id=1)]
[Post(id=1), Post(id=5), Post(id=19)]
You just have to use 'any' in your filter function to compare all your list elements.
val output = posts.filter { post -> ids.any { id -> id == post.id } }

Filter incoming event by Map of <String, Set<String>>

I am looking for a way to neatly filter the values ​​from the event and create a map based on them.
What we need to know:
Event:
The event contains the dictionary data structure (its a map of <String, String> where key is a product parameter name.
For example:
a)
Key: Brand,
Value: Nike
b)
Key: Producer,
Value: Audi
Filter:
Filter is a Map<String, Set<String>> and its configured in yml.
Example of values:
Key: Manufacturer,
Value: [Brand, Producer, Creator, Author]
Entity:
data class Product(
val id: String,
val parameters: Map<String, Parameter> = emptyMap()
)
data class Parameter(
val parameterId: String?,
val parameterValue: String)
How flow looks like:
1)An event comes with a list of parameters, e.g.
mapOf(
"brand" to "Nike",
"size" to "42",
"color" to "black
)
Values ​​are filtered. Lets pretend we have got only one filter - its key = Manufacturer, its values = [Brand, Producer, Creator, Author].
So from this event we have got only one position - its mapOf("brand" to "Nike")
During filtering, a map is created in the domain object [Product] with filled field- parameters: Map <String, Parameter>
e.g.
mapOf("Manufacturer" to Parameter(null, "Nike"))
As you can see - Manufacturer, not Brand, was selected as the key in the map in the Product object (i.e. the key of this parameter, not the value).
GOAL:
What should the method of filtering the event look like?How to do it in clever, Kotlin way (filter the data using the Map <String, Set > for the incoming map and create the target object based on it.)
Thanks in advance for help/advice!
EDIT:
More information:
Sample input:
Incoming event that appears on the domain side:
ProductCreatedEvent( id = "1234", mapOf("brand" to "Nike", "color" to "black, "size" to "46"))
Sample output:
Product(id = "1234", mapOf("Manufacturer" to "Nike"))
The incoming event was filtered by a filter with the structure Map <String, Set <String>>
The structure of the filter is dictated by the fact that the data comes from various sources and thus, for example, the "manufacturer" key on the supplier's side may be called "brand" or "producer". On the side of my service it is to be standardized and therefore the key value per key is selected in the parameter map in the domain object.
This should work
val event = ProductCreatedEvent( id = "1234", mapOf("Brand" to "Nike", "Color" to "Black", "Size" to "46"))
val filters = mapOf("Manufacturer" to setOf("Brand", "Producer", "Creator", "Author"))
val parameters = event.params.mapNotNull { param ->
filters.entries.find { it.value.contains(param.key) }
?.let { it.key to param.value }
}.toMap()
val product = Product(event.id, parameters)

Using RxJava to generate a map where keys are values of a Kotlin enum and map's values come from another RxJava stream

Introduction
Let's say I have a Kotlin enum class:
enum class Type {
ONE,
TWO,
THREE,
FOUR
}
and a following data class:
data class Item(
val name: String,
val type: Type
)
Then I have a Single that emits a list of Items – can by anything but for example purposes, let's say it looks like that:
val itemsSingle = Single.just(listOf(
Item("A", Type.ONE),
Item("B", Type.ONE),
Item("C", Type.TWO),
Item("D", Type.THREE),
))
Problem
What I'd like to achieve is to have an RxJava stream that will output a map where keys come from Type and values are lists of Items matching a given Type value (where an undetermined, unsorted list of Items is provided by some other Single stream). The signature would be:
Single<Map<Type, List<Item>> // or Observable<Map<Type, List<Item>>
One additional requirement is that the map's keys should always exhaust all values from Type enum even if the itemsSingle stream contains no items for some Type values (or no items at all). So, for the provided itemsSingle example stream the returned map should look like this:
{
ONE: [ Item(name: "A", type: ONE), Item(name: "B", type: ONE) ],
TWO: [ Item(name: "C", type: TWO) ],
THREE: [ Item(name: "D", type: THREE) ],
FOUR: []
}
Attempt
With all the above, I've kinda achieved the desired result with following steps:
To satisfy the requirement of exhausting all Type enum values I first create a map that has an empty list for all possible Type values:
val typeValuesMap = Type.values().associate { it to emptyList<Item>() }
val typeValuesMapSingle = Single.just(typeValuesMap)
// result: {ONE=[], TWO=[], THREE=[], FOUR=[]}
I can get a map that contains items from itemsSingle grouped under respective Type value keys:
val groupedItemsMapSingle = itemsSingle.flattenAsObservable { it }
.groupBy { it.type }
.flatMapSingle { it.toList() }
.toMap { list -> list[0].type } // the list is guaranteed to have at least one item
// result: {ONE=[Item(name=A, type=ONE), Item(name=B, type=ONE)], THREE=[Item(name=D, type=THREE)], TWO=[Item(name=C, type=TWO)]}
finally I can combine both lists using the combineLatest operator and overwriting initial empty list of items for a given Type value if itemsSingle contained any Items for this Type value:
Observable.combineLatest(
typeValuesMapSingle.flattenAsObservable { it.entries },
groupedItemsMapSingle.flattenAsObservable { it.entries }
) { a, b -> listOf(a, b) }
.defaultIfEmpty(typeValuesMap.entries.toList()) // in case itemsSingle is empty
.flatMapIterable { it }
.collect({mutableMapOf<Type, List<Item>>()}, { a, b -> a[b.key] = b.value})
// result: {FOUR=[], ONE=[Item(name=A, type=ONE), Item(name=B, type=ONE)], THREE=[Item(name=D, type=THREE)], TWO=[Item(name=C, type=TWO)]}
Summary
As you can see, it's quite a lot of code for a seemingly simple operation. So my question is – is there a simpler way to achieve the result I'm after?
Just merge a map of empty lists with a map of filled lists
val result = itemsSingle.map { items->
Type.values().associateWith { listOf<Item>() } + items.groupBy { it.type }
}

How to sort objects list in case insensitive order?

Let's say I have a list of Strings in Kotlin: stringList: MutableList<String>
Then it is is easy to sort such list in case insensitive order by doing this:
stringList.sortWith(String.CASE_INSENSITIVE_ORDER)
But how would I sort a list of objects in case insensitive order? For example: places: MutableList<Place>
Where Place is a simple class with 2 fields - name: String and id: Int, and I would like to sort these Places by the name field.
I tried to do something like this: places.sortedWith(compareBy { it.name }) but this solution doesn't take letter case into account.
It looks like compareBy might be able to take a Comparator as an argument, see the documentation here: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.comparisons/compare-by.html
Try:
places.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it.name }))
to sort the array in place, or you can assign it to a new variable using
val newArray = places.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it.name }))
Sort Ascending - case insensitive:
myList.sortedBy { it.name?.toLowerCase() }
Sort Descending - case insensitive:
myList.sortedByDescending { it.name?.toLowerCase() }