Define collection of data class - kotlin

If I've the below data class
data class User(val name: String = "", val age: Int = 0)
How can I define a collection of it, like:
var user = User [] // this is not working
I need to be able to call the users by:
user[0].name // something like this!

Defining a List collection in Kotlin in different ways:
Immutable variable with immutable (read only) list:
val users: List<User> = listOf( User("Tom", 32), User("John", 64) )
Immutable variable with mutable list:
val users: MutableList<User> = mutableListOf( User("Tom", 32), User("John", 64) )
or without initial value - empty list and without explicit variable type:
val users = mutableListOf<User>()
//or
val users = ArrayList<User>()
you can add items to list:
users.add(anohterUser) or
users += anotherUser (under the hood it's users.add(anohterUser))
Mutable variable with immutable list:
var users: List<User> = listOf( User("Tom", 32), User("John", 64) )
or without initial value - empty list and without explicit variable type:
var users = emptyList<User>()
NOTE: you can add* items to list:
users += anotherUser - *it creates new ArrayList and assigns it to users
Mutable variable with mutable list:
var users: MutableList<User> = mutableListOf( User("Tom", 32), User("John", 64) )
or without initial value - empty list and without explicit variable type:
var users = emptyList<User>().toMutableList()
//or
var users = ArrayList<User>()
NOTE: you can add items to list:
users.add(anohterUser)
but not using users += anotherUser
Error: Kotlin: Assignment operators ambiguity:
public operator fun Collection.plus(element: String): List defined in kotlin.collections
#InlineOnly public inline operator fun MutableCollection.plusAssign(element: String): Unit defined in kotlin.collections
see also:
https://kotlinlang.org/docs/reference/collections.html

You do it like this in Kotlin:
val array = arrayOf(User("name1"), User("name2"))
If you want to create an Array without adding elements right away, use
val arrayList = ArrayList<User>()
In this case you have to specify the element type explicitely because there is nothing to infer it from.
From the ArrayList docu:
Povides a MutableList implementation, which uses a
resizable array as its backing storage

Related

Kotlin Generic problem, UNCHECKED_CAST , required:Nothing

#file:Suppress("UNCHECKED_CAST")
data class Element<T>(
val key: String,
val valueOne: T,
val valueTwo: T,
val comparator: Comparator<T>,
val comparatorValue: CompareResult
)
enum class CompareResult(
val value: Int
) {
LESS(-1),
EQUAL(0),
GREATER_THAN(1)
}
fun <T> matchesComparison(list:Collection<Element<T>>): Pair<Boolean, List<String>> {
val failedComparisons = mutableListOf<String>()
for (element in list) {
val compareValue = element.comparator.compare(element.valueOne, element.valueTwo)
if (element.comparatorValue.value != compareValue) {
failedComparisons.add(element.key)
}
}
return Pair(failedComparisons.isEmpty(), failedComparisons)
}
val stringComparator = Comparator.comparing(String::toString)
val intComparator = Comparator.comparing(Int::toInt)
val elementsToCompare = listOf(
Element("number", 1, 2, intComparator, CompareResult.LESS),
Element("first name", "a", "a", stringComparator, CompareResult.EQUAL),
Element("last name", "a", "b", stringComparator, CompareResult.EQUAL)
)
matchesComparison(elementsToCompare).second.joinToString(", ","Failed elements: \"","\"")
I often get faced with comparing two different object properties with the same values.
As an example object A has props number,firstname,lastname. What i want to do is create a list have and have a function which goes over these Elements and returns which props have failed the comparison. I've managed to use generics for both the object and the matchesComparison function which returns the failed comparisons. The problem begins when i want to pass this list which is of type Collection<Element<out Any>> to this function is i get a type missmatch. instead of using unchecked casts to force the Comparator to be of type Any i would like to do this
val stringComparator = Comparator.comparing(String::toString)
val intComparator = Comparator.comparing(Int::toInt)
The result value that of the script above should be Failed elements: "last name"
I tried changing the signature of the function to out any but then the comparator.compare method has both params as of type Nothing. I really want to avoid unsing unchecked casts.
matchesComparison() doesn't need to be generic in this case. It doesn't really care what is the type of the whole input collection, so we can simply use * here.
Then we have another problem. The compiler isn't smart enough to notice that while we perform operations on a single element, all its properties are of matching types. As a result, it doesn't allow to use element.comparator on element.valueOne and element.valueTwo. To fix this problem, we simply need to create a separate function which works on a single Element, so it understand the type for all properties is the same:
fun matchesComparison(list:Collection<Element<*>>): Pair<Boolean, List<String>> {
fun <T> Element<T>.matches() = comparatorValue.value == comparator.compare(valueOne, valueTwo)
val failedComparisons = mutableListOf<String>()
for (element in list) {
if (!element.matches()) {
failedComparisons.add(element.key)
}
}
return Pair(failedComparisons.isEmpty(), failedComparisons)
}
Also, I believe such matches() function should be actually a member function of Element. It seems strange that while Element is pretty independent and it contains everything that is needed to perform a comparison, it still requires to use external code for this. If it would have a matches() function then we wouldn't need to care about its T. matches() would work with any Element.

Kotlin - creating map with 3 arrays using fold not working

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?>?>
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
#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 }
)
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 type List<Any>?, kotlin regard it as your "acc_(n+1)", but you propose mapOf("ids" to ...) as acc_0, which has type Map<String, ...>: not the same type as List<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.

Create list of lists in kotlin?

How do I create a list of lists?
I have a mutable list of ints:
val rsrpList = mutableListOf<Int>()
Now I am trying to create a list of lists, like this:
val rsrpList = mutableListOf<Int>()
val rsrqList = mutableListOf<Int>()
val levelList = mutableListOf<Int>()
val listoflists = List<List<Int>>
listoflists.add(rsrpList)
listoflists.add(rsrqList)
listoflists.add(levelList)
but I know this is wrong, because I'm adding a list one at a time, instead of a list of lists. How would I do this?
You can do this with the Kotlin Standard Library. Both List and MutableList can be created to a specific size (3 in this case) and specify a lambda that will initialize each value.
val listOfList = MutableList(3) { mutableListOf<Int>() }
Or:
val listOfList = List(3) { mutableListOf<Int>() }
Update: To initialize a List with precreated lists:
val listOfList = listOf(list1, list2, list3)
Or in your specific case:
val listOfList = listOf(rsrpList, rsrqList, levelList)
And in both cases you can replace listOf with mutableListOf if you want a mutable list as the main type.
Your example is fine (you have an empty list, you're adding lists to that list, you end up with a list of lists!) but if you're trying to avoid mutability by declaring everything at once:
val listOfLists = listOf(rsrpList, rsrqList, levelList)
and you can declare those lists at the same time if you want
val listOfLists = listOf(
listOf(1, 2, 3),
listOf(9, 8, 7),
listOf(7, 7, 7)
)
or you can use mutableListOf if you need any of them to be mutable! The formatting there isn't necessary, I just think it looks clearer how they're nested

Kotlin ArrayList won't append object using 'add' method

When trying to 'add' a custom object to an ArrayList, the ArrayList remains null
I've tried modifying the data class initialization by using MutableLists, among other things
Here is the data class that I'm using:
data class WYRStatistics(val team: String, val league: String, val gp: String, val g: String, val a: String, val pt: String, val pim: String, val pm: String)
This is where I'm using the data class to construct on object and try to add the object to an ArrayList
var n = 0
var statsArr: ArrayList<WYRStatistics>? = null
while (n < tempStats.length()) {
val statObject = tempStats.getJSONObject(n)
val singleStat = WYRStatistics(statObject.getString("Team"),
statObject.getString("League"),
statObject.getString("GamesPlayed"),
statObject.getString("Goals"),
statObject.getString("Assists"),
statObject.getString("Points"),
statObject.getString("PenaltyMinutes"),
statObject.getString("PlusMinus")
)
println(singleStat)
statsArr?.add(singleStat)
println(statsArr)
tempPlayer.stats?.add(singleStat)
println(tempPlayer)
n++
}
The utilization is inside of a function that handles the asynchronous task of decoding JSON. This is all working fine.
tempStats is a JSONArray - when printed it is formatted properly
When I println singleStat, the object is printed properly
However, when I 'add' singleStat to statsArr, the statsArr returns null
You are initializing arrayList to nullas what JB mentioned.
The correct way should be
val statsArr = arrayListOf<WYRStatistics>()

How do I create a map from 2 arrays?

I have a string array and an integer array. How do I create a map using the first as keys and the second as values?
val keys = arrayOf("butter", "milk", "apples")
val values = arrayOf(5, 10, 42)
val map: Map<String, Int> = ???
How to convert List to Map in Kotlin? doesn't solve this problem; I have 2 arrays and want a single map.
You can zip together the arrays to get a list of pairs (List<Pair<String, Int>>), and then use toMap to get your map.
Like this:
val keys = arrayOf("butter", "milk", "apples")
val values = arrayOf(5, 10, 42)
val map: Map<String, Int> =
keys.zip(values) // Gives you [("butter", 5), ("milk", 10), ("apples", 42)]
.toMap() // This is an extension function on Iterable<Pair<K, V>>
According to kotlin
Constructing Collections
-> creating a short-living Pair object, is not recommended only if performance isn't critical and to quote: "To avoid excessive memory usage, use alternative ways. For example, you can create a mutable map and populate it using the write operations. The apply() function can help to keep the initialization fluent here."
since I'm not much of an expert I run on to these code and maybe this should work better?:
val numbersMap = mutableMapOf<String,Int>()
.apply{ for (i in 1.. 5) this["key$i"] = i }
println(numbersMap)
//result = {key1=1, key2=2, key3=3, key4=4}
or to adjust it to question above - something like this:
val keys = arrayOf("butter", "milk", "apples")
val values = arrayOf(5, 10, 42)
val mapNumber = mutableMapOf<String, Int>()
.apply { for (i in keys.indices) this[keys[i]] = values[i] }
println(mapNumber)