I have an array of customers, each customer has properties id
, uuid
and subCustomer
and other properties that I am not interested in. I would like to do one iteration, where I would create 3 arrays where one would hold ids
, other uuids
and third subcustomers
only.
I have tried to achieve this by using fold
function like this:
customers.fold(
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
))
{ acc, customer ->
acc["ids"]?.plus(customer["id"])
acc["uuids"]?.plus(customer["uuid"])
acc["subCustomers"]?.plus(customer["subCustomer"])
}
With this code I get an error in editor:
Type mismatch.
Required:
Map<String, List<{Comparable{String & UUID}> & java.io.Serializable}>>
Found:
List<Any?>?
I have tried this as well:
customers.fold(
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
))
{ acc, customer ->
mapOf(
"ids" to acc["ids"]?.plus(customer["id"]),
"uuids" to acc["uuids"]?.plus(customer["uuid"]),
"subCustomers" to acc["subCustomers"]?.plus(customer["subCustomer"])
)
}
But, I get this errors:
Type mismatch.
Required:
List<{Comparable{String & UUID}> & java.io.Serializable}>
Found:
List<Any?>?
Type mismatch.
Required:
Map<String, List<{Comparable{String & UUID}> & java.io.Serializable}>>
Found:
Map<String, List<Any?>?>
CodePudding user response:
Write two data classes for your data. One for your customers, and one for the three lists that you want:
data class Customer(
val id: String,
val uuid: UUID,
val subCustomer: String,
)
data class CustomerDataLists(
val ids: MutableList<String> = mutableListOf(),
val uuids: MutableList<UUID> = mutableListOf(),
val subCustomers: MutableList<String> = mutableListOf(),
)
Then, just use a simple for loop to add the data in:
val dataLists = CustomerDataLists()
for (customer in customers) {
dataLists.ids.add(customer.id)
dataLists.uuids.add(customer.uuid)
dataLists.subCustomers.add(customer.subCustomer)
}
// now dataLists is filled with customers' data
CodePudding user response:
@Sweeper's answer is nice. I believe in any case it's worth using data classes instead of maps for this kind of use case.
Since you don't really have any interactions between the 3 lists in the fold
, you could also build those lists independently (but it's 3 iterations of course here):
data class Customer(
val id: String,
val uuid: UUID,
val subCustomer: String,
)
data class AggregatedCustomers(
val ids: List<String>,
val uuids: List<UUID>,
val subCustomers: List<String>,
)
val customers: List<Customer> = TODO("get that list from somewhere")
val aggregated = AggregatedCustomers(
ids = customers.map { it.id }
uuids = customers.map { it.uuid }
subCustomers = customers.map { it.subCustomer }
)
CodePudding user response:
This answer contiains 3 parts:
- A better way to solve such problem;
- Why the original code doesn't work;
- Other problems need to pay attention.
1. A better way to solve such problem
Let's assume that the Consumer
mentioned looks like this:
data class Customer(
val id: String,
val uuid: UUID,
val subCustomer: String,
)
It's really not necessary to use function fold
in such occasion. For
loop or extension function forEach
is merely enough:
val customers: List<Customer> = listOf(
Customer("1", UUID.randomUUID(), "sub-1"),
Customer("2", UUID.randomUUID(), "sub-2"),
Customer("3", UUID.randomUUID(), "sub-3"),
)
val ids = mutableListOf<String>() // pay attention. use `mutableListOf` instead of `listOf()`
val uuids = mutableListOf<UUID>()
val subConsumers = mutableListOf<String>()
customers.forEach {
ids = it.id
uuids = it.uuid
subConsumers = it.subCustomer
}
2. Why the original code doesn't work
The proposed two pieces of code are in the same pattern:
customers.fold(
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
)
) { acc, customer ->
// ... do something with acc and customer
}
We should first make it clear that the last statement in the fold
scope is the expression to be accumulated. It's like an acc_n <combine> customer -> acc_(n 1)
, for each customer in customers
each time, where <combine>
is where we write our logic. So the first proposed piece of code doesn't work because you might not be aware that something should be returned while writing:
customers.fold(...){ acc, customer ->
acc["ids"]?.plus(customer.id)
acc["uuids"]?.plus(customer.uuid)
acc["subCustomers"]?.plus(customer.subCustomer)
}
In fact, the last statement
acc["subCustomers"]?.plus(...)
is an expression with typeList<Any>?
, kotlin regard it as your "acc_(n 1)", but you proposemapOf("ids" to ...)
asacc_0
, which has typeMap<String, ...>
: not the same type asList<Any>?
. And that's why you got the first error:Type mismatch. Required: Map<String, List<{Comparable{String & UUID}> & java.io.Serializable}>> Found: List<{Comparable{String & UUID}> & java.io.Serializable}>?
We'll talk about generic types later.
Let's move on the second piece of code. A map is proposed as the last expression in the scope of fold
, which is also a map:
customers.fold(...) { acc, customer ->
mapOf(
"ids" to acc["ids"]?.plus(customer.id),
"uuids" to acc["uuids"]?.plus(customer.uuid),
"subCustomers" to acc["subCustomers"]?.plus(customer.subCustomer)
)
}
The simpliest way to eliminate error is using !!
expression (not suggested!):
customers.fold(...) { acc, customer ->
mapOf(
"ids" to acc["ids"]?.plus(customer.id)!!,
"uuids" to acc["uuids"]?.plus(customer.uuid)!!,
"subCustomers" to acc["subCustomers"]?.plus(customer.subCustomer)!!
)
}
The reason is that kotlin cannot assert acc["ids"] is not null, that's why you use ?.
for a null-safe method invoke. However such invoke make the return type nullable:
val cus: Customer? = Customer("1", UUID.randomUUID(), "sub-1") // cus has type Customer? : nullable
val id1: String = cus?.id // [compile error] Type mismatch. [Required: String] [Found: String?]
val id2: String? = cus?.id // OK
val id3: String = cus?.id!! // If `cus?.id` is null, throw NPE.
You've declare acc_0
(in bracket after fold
) in type Map<String, List<T>>
implicitly (we will talk about T later). Just know that T is not a nullable type), but a map with type Map<String, List<T>?>
was found as acc_(n 1)
. Types mismatch and the error was shown:
Type mismatch.
Required:
List<{Comparable{String & UUID}> & java.io.Serializable}>
Found:
List<{Comparable{String & UUID}> & java.io.Serializable}>?
3. Other problem need to pay attention
An important problem is: What's the type of acc_0
?
// acc_0:
mapOf(
"ids" to listOf<String>(),
"uuids" to listOf<UUID>(),
"subCustomers" to listOf<String>()
)
Of course type of each expression on the left of to
is String, and List<T>
is the type of each expression on the right of it. so it must be Map<String, List<T>>
. What about T
? Kotlin try to find the nearest ancessor of String and UUID, and find them both implements Comparable<?>
and Serializable
, so that's what you see in the error. That's the type of T:
Required:
List<{Comparable{String & UUID}> & java.io.Serializable}>
This may lead to some unwanted experience:
val map = mapOf(
"listA" to mutableListOf("233"),
"listB" to mutableListOf(UUID.randomUUID())
)
val listA = map["A"]!! // MutableList<out {Comparable{String & UUID}> & java.io.Serializable}!>
// generic type "collapse" into `Nothing` for no type can implement both Comparable<String> and Comparable<UUID>
listA.add(Any()) // Type mismatch. [Required: Nothing] [Found: Any]
So try not to put lists with different generic type into one map.
Another problem is, when you try to invoke acc["ids"]?.plus(customer.id)
, you are actually invoking such method (from kotlin _Collections.kt
)
public operator fun <T> Collection<T>.plus(element: T): List<T> {
val result = ArrayList<T>(size 1)
result.addAll(this)
result.add(element)
return result
}
A new list is created each time you invoke the method! Try use mutableListOf()
in replace of listOf()
for collections that you want to make changes, and use " =" (or ?.plusAsign()
as null-safe version) operator instead. This may leads to some other problem with the original code (which is too complex to explain why), but for the code in part 1: A better way to solve such problem, the =
is actually invoking:
public inline operator fun <T> MutableCollection<in T>.plusAssign(element: T) {
this.add(element)
}
which just add value to list without create new ones.