Create a Map of Map of Object from a given List of Object - kotlin

The structure of data class
data class ProductDetail(
val name: String,
val price: Int,
val status: Status,
val images: Images
...
)
I have a List<ProductDetail> and I want to convert it to Map<String, Map<Int, ProductDetail>>.
I tried looking into associate and associatedTo but I am only able to create Map<String, Pair<Int, ProductDetail>>. Is there a way to do this without the use to a loop in Kotlin?

val mapOfMaps = list.groupBy(ProductDetail::name) // or { it.name }
.mapValues { (name, productDetailsList) ->
productDetailsList.groupBy(ProductDetail::price) // or { it.price }
}
Use groupBy to easily create a map that groups items into matching lists. Then you can map those lists into inner maps with another groupBy call.
I am guessing associate is not the behavior you wanted here, because then every inner map would only contain one item, instead of all items with the matching name, and all repeated names would be dropped from the data.

Related

How to correctly cast list in Kotlin?

I have a list for example of type People. My list can contain only elements of type Student or only elements of type Worker:
interface People {
val name: String
val age: Int
}
data class Student(
override val name: String,
override val age: Int,
val course: Int
) : People
data class Worker(
override val name: String,
override val age: Int,
val position: String
) : People
At some point I need to know the exact type of the list (student or worker).
Can I safely find out the exact type? So far I've written this code, but it doesn't look very good:
fun someLogic(items: List<People>): List<People> {
return (items as? List<Student>) ?: (items as? List<Worker>)
?.filter {}
....
}
Also, I get a warning:
Unchecked cast
Can you please tell me how to perform such transformations correctly?
At runtime, the type parameter you used to create the list is not available. e.g. it is impossible to distinguish between the following two situations:
val students: List<People> = listOf<Student>(student1, student2)
val people: List<People> = listOf<People>(student1, student2)
This is because of type erasure.
The only information you have at runtime that can help determine a list's element type is the type of its elements.
So if a list has no elements, there is no way of knowing what type of list it is. Though in most situations, you don't need to anyway.
So assuming the list can only be a list of all students, or a list of all workers, but not a list containing a mixture of students and workers, you can determine the type of the list by checking the first element.
when (items.firstOrNull()) {
null -> { /* cannot determine the type */ }
is Student -> { /* is a list of students */ }
is Worker -> { /* is a list of worker */ }
// you can remove this branch by making the interface sealed
else -> { /* someone made another class implementing People! */ }
}
If you want to get a List<Student> or List<Worker> out of this on the other hand, you can just use filterIsInstance:
val students = items.filterIsInstance<Student>()
val worker = items.filterIsInstance<Worker>()
whichever list is not empty, then the type of items is the type of that list.
If you want to check that List<People> is List<Student> you can use this extension function:
fun List<People>.isStudentList(): Boolean {
// returns true if no element is not Student, so all elements are Student
return all { it is Student }
}
And if you want to cast List<People> to List<Student>, you can use map, and this cast is safe so let's say that there is some People that the are not Student so the cast is going to return null instead of Student because of as? and the mapNotNull is going to exclude null elements so in worst cases where you pass a list that doesn't contain any Student this function is going to return an empty list:
fun List<People>.toStudentList(): List<Student> {
// This is going to loop through the list and cast each People to Student
return mapNotNull { it as? Student }
}
Or you can just use filterIsInstance<Student> this will work the same as toStudentList above:
list.filterIsInstance<Student>()
And the same approach can be used for Worker
I would solve the problem with more specific classes.
You can define:
interface PeopleList<P : People> : List<P>
class StudentList : PeopleList<Student> {
// add implementation
}
class WorkerList : PeopleList<Worker> {
// add implementation
}
You can then easily check the types of these lists. Each of those classes can then provide guarantees that you are not mixing Student and Worker objects in the same List, something you can't do with plain List<People> objects.
Note also you are better off writing your code avoiding checking types if at all possible. Much better to add methods to the PeopleList interface and force the subclasses to implement them, for example:
interface PeopleList<P : People> : List<P> {
fun doSomethingGood()
}
Then you can call these methods at the appropriate time, instead of checking the type. This approach keeps the functionality associated with the subtypes alongside those subtypes and not scattered through the code at the various points where you have to check the type of PeopleList.

Kotlin - How to convert a list of objects into a single one after map operation?

I'm trying to wrap my head around map and reduce operations in Kotlin. At least, I guess it's reduce what I'm trying to do.
Let's say that I have a class called Car that takes any number (varargs constructor) of CarPart. Then, I have a list of CarPart which I'll do a map operation and from the result of the operation I need to build one Car using each subelement, something along these lines:
class CarPart(val description: String)
class Car(vararg val carPart: CarPart)
val carParts = listOf(CarPart("Engine"), CarPart("Steering Wheel")))
carParts.map { it.description.toUpperCase() }
.map { CarPart(it) }
.reduce { acc, carPart -> Car(carPart) } <--- I'm struggling here, is reduce what I should be doing
to construct one car from all the subelement?
PS.1: I know that the class design could be better and not take a varargs, this is just an example of a legacy application I'm refactoring and originally that's a Java class taking varargs which I can't change now.
PS.2: The example of mapping to a String and then creating an object out of that String is just for the sake of the example. The actual code grabs an object within the list.
You can simply use a the spread operator (*) over an array:
val mappedCarParts = carParts
.map { it.description.toUpperCase() }
.map { CarPart(it) }
.toTypedArray()
val car = Car(*mappedCarParts)
// Or even:
val car = carParts
.map { it.description.toUpperCase() }
.map { CarPart(it) }
.toTypedArray()
.let{ Car(*it) }
You could just extract the constructor of the Car outside of the creation of the list. I don't see any reason as to why you'd want it inside.
val car = Car(
*carParts
.map { CarPart(it.description.uppercase(Locale.getDefault())) } //keep the .toUpperCase() if you are using an old version of Kotlin
.toTypedArray()
)
We need the spread operator there in order for the vararg to know that we are passing it the elements of the list and not the list itself.

How to pass the current instance into the constructor of another class withon class declaration?

I have a service that returns me a list of entities, for example:
data class TypeDto(
val type: Type,
val stages: List<StageDto>)
After applying several filter and flatMap operations, I get the desired data that has the following structure:
data class CustomerDto(
val id: String,
val name: String)
In order to extract the logic of filtering and mapping, I made a CustomerDtoWrapper class that takes List<TypeDto> as a constructor argument and does all the collection manipulation. So, in the end it looks as follows:
val types = service.getTypes()
val customers = CustomerDtoWrapper(types).filteredCustomers()
But I would like to make it more fluent and easy to read. Is it possible to call a certain function after getTypes(), so that types will be of the CustomerDtoWrapper type and look as follows:
val types = service.getTypes().someFun { ... }
val customers = types.filteredCustomers()
You can write for example extension function for this:
fun List<TypeDto>.toFilteredCustomers() = CustomerDtoWrapper(this).filteredCustomers()
and use it like this:
val customers = service.getTypes().toFilteredCustomers()

Kotlin, How to use the collection function to simplify this code

I am using Kotlin for a project, I write this code can complete the requirement:
val rewards = ArrayList<Map<String, Int>>()
rangeExams
.forEach { examAnswer ->
var reward = hashMapOf("Score" to examAnswer.answerScore)
var questionIds = examAnswer
.answers
.map { it.id }
reward.put("NewQuestion", questionIds.size)
rewards.add(reward)
}
"rangeExams" is a list of collection.
I would like to combinate Kotlin Functions of Collection,
to put elements of rangeExams into a map
and put this map to a new list,
how can I simplify this code by Kotlin ?
ExamAnswer is a pojo:
class ExamAnswer (val id: String, val answerScore: Int, val answers:List<Answer>)
Thank you for your reply
Since you add an item to the rewards for each element of rangeExams, the .forEach { ... } call can be transformed to .map { ... }.
Also, you only use the result of examAnswer.answers.map { it.id } to get its size, so you can remove .map { it.id } and use the size of the original collection.
If you don't need to mutate the maps afterwards, you can replace hashMapOf(...) with mapOf(...).
val rewards = rangeExams.map {
mapOf(
"Score" to it.answerScore,
"NewQuestion" to it.answers.size)
}
If you need to mutate the rewards list after it's created, add .toMutableList() in the end.
There is a little potential to simplify this.
Firstly, I would suggest a more functional approach, which could turn the mutable list rewards into an immutable one.
Secondly, infer the creation of the hash-map reward with the put into one line. You than can use the also the immutable version of the map, instead of the mutable one created by hashMapOf (if you need mutability, than you can just keep hashMapOf).
thirdly, you just use the questionIds to the the size. For that, you don't have to map anything, just call examAnswer.ansers.size. This short call can be inferred as well
fourthly, you can use it instead of explicitly name the param examAnswer because this block is now quite short anyway
This would lead to this code:
val rewards = rangeExams.map {
mapOf("Score" to it.answerScore,
"NewQuestion" to it.answers.size)
}

How can I remove duplicate objects with distinctBy from a list in Kotlin?

How can I use distinctBy on a list of custom objects to strip out the duplicates? I want to determine "uniqueness" by multiple properties of the object, but not all of them.
I was hoping something like this would work, but no luck:
val uniqueObjects = myObjectList.distinctBy { it.myField, it.myOtherField }
Edit: I'm curious how to use distinctBy with any number of properties, not just two like in my example above.
You can create a pair:
myObjectList.distinctBy { Pair(it.myField, it.myOtherField) }
The distinctBy will use equality of Pair to determine uniqueness.
If you look at the implementation of the distinctBy, it just adds the value you pass in the lambda to a Set. And if the Set did not already contain the specified element, it adds the respective item of the original List to the new List and that new List is being returned as the result of distinctBy.
public inline fun <T, K> Iterable<T>.distinctBy(selector: (T) -> K): List<T> {
val set = HashSet<K>()
val list = ArrayList<T>()
for (e in this) {
val key = selector(e)
if (set.add(key))
list.add(e)
}
return list
}
So you can pass a composite object that holds the properties that you require to find the uniqueness.
data class Selector(val property1: String, val property2: String, ...)
And pass that Selector object inside the lambda:
myObjectList.distinctBy { Selector(it.property1, it.property2, ...) }
You can create a triple:
myObjectList.distinctBy { Triple(it.firstField, it.secondField, it.thirdField) }
The distinctBy will use equality of Triple to determine uniqueness.
*I have implemented in this way, it provides most Unique list 👍