Alternatives to array in Kotlin - kotlin

at the moment I have a very simple array:
val name = arrayListOf(
"Steve",
"David",
"Chris,
"Bev")
MyResult.text = name[2]
Giving “Chris”.
But I would like to use the same data but including the age with something like:
val name = array????(
0, "Steve", 20,
1, "David", 28,
2, "Chris, 28,
3, "Bev", 46)
MyResult.text = name[2,0]
Giving the same result, ie using something that allows me to specify the position in the array please to be able to return the item?
Any help appreciated!

Arrays (and lists, which are more generally useful) are for when every element is the same type.
In this case, that's not true.  You have a mix of types, because you have internal structure: some elements relate directly to others, in a regular way.  What you have is effectively a list of people, each with several fields.  So code that!  It's very straightforward:
data class Person(val id: Int, val name: String, val age: Int)
val people = listOf(Person(0, "Steve", 20),
Person(1, "David", 28),
Person(2, "Chris", 28),
Person(3, "Bev", 46))
This way, the whole type system is working for you.  Because you've told the compiler what those fields represent and how they relate to each other, it can prevent you from mixing them up by mistake, and it knows what you can do with each one.  You can then manipulate them in all sorts of ways.  Not just getting the person at given position in the list:
val firstPerson = people[0]
But getting a person by name:
val david = people.find{ it.name == "David" }
Or finding the youngest person:
val youngest = people.minByOrNull{ it.age }
Or getting their names in alphabetical order:
val names = people.map{ it.name }.sorted()
Or many other things.  The Kotlin standard library has a lot of extension functions and other machinery for handling lists, and Kotlin's data classes make it very easy to create value objects like this.
If there's no inherent ordering between the people, then maybe a set would be a better fit.
Or if you often refer to them by ID, then you could store them in a map with their ID as the key.
It all depends what you're going to do with them.
But an array is unlikely to be the best option.  Arrays are a necessary evil, needed for a few specific things (varargs, implementing higher-level structures, storing primitive values efficiently, and interoperating with legacy code), but for most other uses lists are more powerful, more flexible, and better supported.  Most Kotlin code should use lists instead of arrays.

Related

Kotlin. What is the best way to replace element in immutable list?

What is the best way to update specific item in immutable list. For example I have list of Item. And I have several ways to update list:
1.
fun List<Item>.getList(newItem: Item): List<Item> {
val items = this.toMutableList()
val index = items.indexOf(newItem)
if (index != -1) {
items[index ] = newItem
}
return items
}
fun List<Item>.getList(newItem: Card): List<Item> {
return this.map { item ->
if (item.id == newItem.id) newItem else item
}
}
The second option looks more concise and I like it more. However, in the second option, we will go through each element in the list, which is bad for me, because the list can contain many elements.
Please, is there a better way to fulfill my requirement?
You have a few options - you're already doing the "make a mutable copy and update it" approach, and the "make a copy by mapping each item and changing what you need" one.
Another typical approach is to kinda go half-and-half, copying the parts you need, and inserting the bits you want to change. You could do this by, for example, slicing the list around the element you want to change, and building your final list from those parts:
fun List<Item>.update(item: Item): List<Item> {
val itemIndex = indexOf(item)
return if (itemIndex == -1) this.toList()
else slice(0 until itemIndex) + item + slice(itemIndex+1 until size)
}
This way you get to take advantage of any efficiency from the underlying list copy methods, versus map which has to "transform" each item even if it ends up passing through the original.
But as always, it's best to benchmark to see how well these approaches actually perform! Here's a playground example - definitely not the best place to do benchmarking, but it can be instructive as a general ballpark if you run things a few times:
Mapping all elements: 2500 ms
Slicing: 1491 ms
Copy and update index: 611 ms
Broadly speaking, mapping takes 60-100% more time than the slice-and-combine approach. And slicing takes 2-3x longer than just a straight mutable copy and update.
Considering what you actually need to do here (get a copy of the list and change (up to) one thing) the last approach seems like the best fit! The others have their benefits depending on how you want to manipulate the list to produce the end result, but since you're barely doing anything here, they just add unnecessary overhead. And of course it depends on your use-case - the slicing approach for example uses more intermediate lists than the mapping approach, and that might be a concern in addition to raw speed.
If the verbosity in your first example bothers you, you could always write it like:
fun List<Item>.getList(newItem: Item): List<Item> =
this.toMutableList().apply {
val index = indexOf(newItem)
if (index != -1) set(index, newItem)
}
The second one looks ever so slightly better for performance, but they are both O(n), so it's not a big difference, and hardly worth worrying about. I would go for the second one because it's easier to read.
The first one iterates the list up to 2 times, but the second iteration breaks early once it finds the item. (The first iteration is to copy the list, but it is possibly optimized by the JVM to do a fast array copy under the hood.)
The second one iterates the list a single time, but it does have to do the ID comparison for each item in the list.
Side note: "immutable" is not really the right term for a List. They are called "read-only" Lists because the interface does not guarantee immutability. For example:
private val mutableList = mutableListOf<Int>()
val readOnlyList: List<Int> get() = mutableList
To an outside class, this List is read-only, but not immutable. Its contents might be getting changed internally in the class that owns the list. That would be kind of a fragile design, but it's possible. There are situations where you might want to use a MutableList for performance reasons and pass it to other functions that only expect a read-only List. As long as you don't mutate it while it is in use by that other class, it would be OK.
Another thing you could try is, as apparently each item has an id field that you are using to identify the item, to create a map from it, perform all your replacements on that map, and convert it back into a list. This is only useful if you can batch all the replacements you need to do, though. It will probably also change the order of the items in the list.
fun List<Item>.getList(newItem: Item) =
associateBy(Item::id)
.also { map ->
map[newItem.id] = newItem
}
.values
And then there’s also the possibility to convert your list into a Sequence: this way it will be lazily evaluated; every replacement you add with .map will create a new Sequence that refers to the old one plus your new mapping, and none of it will be evaluated until you run an operation that actually has to read the whole thing, like toList().
Another solution: if the list is truly immutable and not only read-only; or if its contents could change and you would like to see these changes in the resulting list, then you can also wrap the original list into another one. This is fairly easy to do in Kotlin:
fun main() {
val list = listOf(
Item(1, "1-orig"),
Item(2, "2-orig"),
Item(3, "3-orig"),
)
val list2 = list.getList(Item(2, "2-new"))
println(list2)
}
fun List<Item>.getList(newItem: Item): List<Item> {
val found = indexOfFirst { it.id == newItem.id }
if (found == -1) return this
return object : AbstractList<Item>() {
override val size = this#getList.size
override fun get(index: Int) = if (index == found) newItem else this#getList[index]
}
}
data class Item(val id: Int, val name: String)
This is very good for the performance if you don't plan to repeatedly modify resulting lists with further changes. It is O(1) to replace an item and it almost doesn't use any additional memory. However, if you plan to invoke getList() repeatedly on a resulting list, each time creating a new one, that would create a chain of lists, slowing down access to the data and preventing garbage collector to clean up replaced items (if you don't use the original list anymore). You can partially optimize this by detecting you invoke getItem() on your specific implementation, but even better, you can use already existing libraries that does this.
This pattern is called a persistent data structure and it is provided by the library kotlinx.collections.immutable. You can use it like this:
fun main() {
val list = persistentListOf(
Item(1, "1-orig"),
Item(2, "2-orig"),
Item(3, "3-orig"),
)
val list2 = list.set(1, Item(2, "2-new"))
println(list2)
}
By the way, it seems strange to keep a list of items where we identify them by their ids. Did you consider using a map instead?

How to group objects in a list by two fields?

I would like to group a list of objects basing on two fields in those objects.
Let's say I have an object like this:
data class ItemDataDTO(
val itemId: BigInteger?,
val sequence: BigInteger?,
val serialNumber: String?,
val pickingId: String?,
val runId: String? = null,
val warehouse: String?,
)
Now I have a list of ItemDataDTO containing a lot of items. I would like to group them by runId and pickingId (because I need those items that have the same pickingId and runId grouped somehow.)
val items: List<ItemDataDTO> = someItemRepository.getItemsForWarehouse("someWarehouseId")
val groupedItems = items.groupBy({ it.runId }, { it.pickingId })
This doesn't work. I found out that I could use groupingBy() function along with a Triple, but I want just them to be grouped by two values...
val groupedItems = items.groupingBy { Triple(it.runId, it.pickingId, null) }
But this doesn't work as well. I tried to add a third parameter instead of null, using it.warehouse:
val groupedItems = items.groupingBy { Triple(it.runId, it.pickingId, it.warehouse) }
It returns an instance of Grouping<ItemDataDTO, Triple<String?, String?, String?>> and I'm not sure what to do with this object.
What could I do to properly group those objects?
In a perfect world, I would like to transform this list to a list of something like:
data class PickingList(
val runId: String,
val pickingId: String,
val items: List<ItemDataDTO>,
)
So the output would be a List<PickingList>.
There's nothing special about it really! groupBy takes a keySelector function which returns some value to be used as a key for that item. So if you want to match on two properties, that key item needs to be composed of those two values.
A Triple with two items is a Pair, so you can just do this:
// just FYI, "it.runId to it.pickingId" is shorthand for a Pair - you see it more
// when defining key/value pairs for maps though. "Pair(x, y)" might read better here
// since you're really just combining values, not describing how one relates to the other
items.groupBy { Pair(it.runId, it.pickingId) }
So each item will produce a Pair with those two values. Any other items with a Pair that matches (as far as the equals function goes) will be put into the same group. It's a bit like adding to a Map, except that if a key already exists, the value is added to a list instead of overwriting the previous value.
You can do that with any key really. Pair and Triple are just quick, general convenience classes for bundling a few items together - but a lot of the time it's better to define your own data structure, e.g. using a data class. So long as two instances with the same data are equal, they count as the same key for grouping.
As for the output you want, with the PickingList... you could use something like that for your grouping operation - but in that case you'd have to pretty much reimplement groupBy yourself. You'd have to take an item, and work out its composite key from the properties you want to consider. Then you'd need to find a match for that key in some store you've created for your groups
If it's a list of PickingLists, you'd need to go through each one, comparing its IDs to the ones you want, adding to its list if you find a match and creating the object if you can't find it.
If you're storing a map of Pair(id1, id2) -> PickingList then that's close to how groupBy works anyway, in terms of generating a key for lookups. In that case, you might want to just use groupBy to group all your items, and then transform the final map:
items.groupBy { Pair(it.runId, it.pickingId) }
.map { (ids, list) ->
PairingList(runId = ids.first, pickingId = ids.second, items = list)
}
This takes every map entry (a Pair of IDs and the list of all things grouped by those IDs) and uses it to create a PairingList from that key/value data. Basically, once you've grouped all your data, you transform it into the data structures you want to work with.
This is also a good example of why your own data class might be better than just using a Pair - it.first doesn't really tell you what that value is in the Pair, just that it's the first of the two values. Whereas
data class IdCombo(val runId: String, val pickingId: String)
works the same as a Pair, but the properties have useful names and make your code much more readable and less prone to bugs:
map { (ids, list) ->
// didn't even bother with the named arguments, since the names are in
// the ids object now!
PairingList(ids.runId, ids.pickingId, items = list)
}

how can i sort 2D mutableListof<Any> by the first element in Kotlin

how can i sort 2D mutable list of array by the first element of array?
val books = mutableListOf<Any>(
listof("abc","b",1),
listof("abb","y",2),
listof("abcl"."i",3)
)
i want to get sort this mutablelist by alphabetical order of the first element of each list.
output should be
[listof("abb","y",2), listof("abc","b",1), listof("abcl"."i",3) ]
You can do
books.sortBy { (it as List<*>).first() as String }
This is difficult, because you have very limited type information.
If you the elements of the inner lists always have three values of type String, String, Integer, you should probably use a triple:
val books = mutableListOf<Triple<String, String, Int>>(
Triple("abc","b",1),
Triple("abb","y",2),
Triple("abcl","i",3)
)
books.sortBy { t -> t.first }
If the inner lists are always lists, but with different lengths and types, but it is known that the are always strings you can do something like
val books = mutableListOf<List<Any>>(
listOf("abc","b",1),
listOf("abb","y",2),
listOf("abcl","i",3)
)
books.sortBy { t -> t.first() as String }
If you don't know any type information, and books is truly a MutableList<Any>, then you cannot compare: you don't what you are comparing.
You've got two problems here:
your list contains elements of Any, which doesn't imply any kind of "first element"
you can only compare things which implement Comparable (unless you pass in your own comparator, or do your own comparison logic in the sorting function)
First, if this list really is supposed to hold "lists of elements", then you should make that part of the type. We can use the general Collection type (or Iterable which it extends):
val books = mutableListOf<Collection<Any>>(
listof("abc","b",1),
...
Unfortunately that doesn't work for arrays, which are their own thing. If you want to be able to mix and match, you need to keep the MutableList<Any> type, and do some type checking in the sort function:
// all kinds of things going in here
val books = mutableListOf<Any>(
listOf("abc","b",1),
arrayOf("abb","y",2),
setOf("abcl","i",3)
)
books.sortedBy {
when(it) {
is Collection<*> -> it.first().toString()
is Array<*> -> it.first().toString()
// or your fallback could just be it.toString()
else -> throw Exception("can't compare this thing")
}
}
That example demonstrates your second problem too - how to sort a bunch of Anys. Since you said you want them alphabetically sorted (and without knowing what you're going to put in there besides strings and numbers) one approach is to just call toString() on everything.
That's one of the few methods Any has, so if you can't be more specific about the types in your "lists", you can at least sort on that. Whether it'll be any use out-of-the-box depends on the objects you put in there!

How to compare two lists and insert values based on one into the other by a property in kotlin?

I am new to kotlin and I am trying to loop through two list and insert email in people.
data class User(val id:String,val name: String,val email: String)
data class Person(val id:String,val name: String, val email: String="")
//Input
val users = listOf(User("1","John","john#a.com"),User("2","Doe","Doe#a.com"))
val people = listOf(Person("1","John"),Person("2","Doe"))
//expected
val userToPerson = listOf(Person("1","John","john#a.com"),Person("2","Doe","Doe#a.com"))
I am trying with this.
val map = people.map { it ->
{
val foundUser = users.find { user -> user.id == it.id }
if (foundUser != null) {
it.email = foundUser.email
}
}
}
map.forEach(System.out::print)
I am getting error for foundUser.isNotNull() here Unresolved reference: isNotNull
Updated with suggested:
() -> kotlin.Unit() -> kotlin.Unit
I am trying to convert a list of users to a list of people. They both have their ids as common.
I want to update people Person class corresponding to their user email.
All people do not have email. But all users have the email.
So, the final result would have people with email. If there is a person with no matching id, we can skip that data.
First, calling find for every person is not only a bit awkward to write, it's also (which is far worse) inefficient. (It takes time proportional to the square of the number of people, which means it will perform really badly as the number of people gets large.)
To fix that, I'd create a map from an ID to its User:
val usersById = users.associateBy{ it.id }
We can then look up users by their ID quickly, in a way which scales well.
Armed with that, the solution can be fairly straightforward. Here's one which creates new* Person objects:
val userToPerson = people.map{ person ->
val user = usersById[person.id]
if (user != null && user.email != person.email)
Person(person.id, person.name, user.email)
else
person
}
This solution is a little longer than necessary, but I hope it's easy to read and understand. It also avoids creating Person objects unless necessary, for efficiency. And when there's no corresponding User, it uses the existing Person.
* As the question is currently written, Person's fields are immutable, so the existing Person objects can't be updated with a new email address. That leads naturally into a functional style.
That's not necessarily a bad thing; immutability has many benefits, such as being easier to think about, and thread safety. It can also allow some compiler optimisations. However, if you're not careful, it can generate lots of temporary objects, which can can reduce efficiency (due to cache misses, constructor calls, and then more frequent garbage collections).
The alternative would be to make Person mutable, and do all the updates in-place — which is the traditional imperative style that most of us started from.
Both approaches are valid; which one you choose is a trade-off involving safety, maintainability, and performance — Kotlin supports both.

What is the best practice of iterating record keys and values in Reasonml?

I'm new to ReasonML, but I read through most of the official documents. I could go through the casual trial and errors for this, but since I need to write codes in ReasonML right now, I'd like to know the best practices of iterating keys and values of reason record types.
I fully agree with #Shawn that you should use a more appropriate data structure. A list of tuples, for example, is a nice and easy way to pass in a user-defined set of homogeneous key/value pairs:
fooOnThis([
("test1", ["a", "b", "c"]),
("test2", ["c"]),
])
If you need heterogeneous data I would suggest using a variant to specify the data type:
type data =
| String(string)
| KvPairs(list((string, data)));
fooOnThis([
("test1", [String("a"), String("b"), String("c")]),
("test2", [String("c"), KvPairs([("innerTest", "d")])]),
])
Alternatively you can use objects instead of records, which seems like what you actually want.
For the record, a record requires a pre-defined record type:
type record = {
foo: int,
bar: string,
};
and this is how you construct them:
let value = {
foo: 42,
bar: "baz",
};
Objects on the other hand are structurally typed, meaning they don't require a pre-defined type, and you construct them slightly differently:
let value
: {. "foo": int, "bar": string }
= {"foo": 42, "bar": "baz"};
Notice that the keys are strings.
With objects you can use Js.Obj.keys to get the keys:
let keys = Js.Obj.keys(value); // returns [|"foo", "bar"|]
The problem now is getting the values. There is no Js.Obj API for getting the values or entries because it would either be unsound or very impractical. To demonstrate that, let's try making it ourselves.
We can easily write our own binding to Object.entries:
[#bs.val] external entries: Js.t({..}) => array((string, _)) = "Object.entries";
entries here is a function that takes any object and returns an array of tuples with string keys and values of a type that will be inferred based on how we use them. This is neither safe, because we don't know what the actual value types are, or particularly practical as it will be homogeneously typed. For example:
let fields = entries({"foo": 42, "bar": "baz"});
// This will infer the value's type as an `int`
switch (fields) {
| [|("foo", value), _|] => value + 2
| _ => 0
};
// This will infer the value's type as an `string`, and yield a type error
// because `fields` can't be typed to hold both `int`s and `string`s
switch (fields) {
| [|("foo", value), _|] => value ++ "2"
| _ => ""
};
You can use either of these switch expressions (with unexpected results and possible crashes at runtime), but not both together as there is no unboxed string | int type to be inferred in Reason.
To get around this we can make the value an abstract type and use Js.Types.classify to safely get the actual underlying data type, akin to using typeof in JavaScript:
type value;
[#bs.val] external entries: Js.t({..}) => array((string, value)) = "Object.entries";
let fields = entries({"foo": 42, "bar": "baz"});
switch (fields) {
| [|("foo", value), _|] =>
switch (Js.Types.classify(value)) {
| JSString(str) => str
| JSNumber(number) => Js.Float.toString(number)
| _ => "unknown"
}
| _ => "unknown"
};
This is completely safe but, as you can see, not very practical.
Finally, we can actually modify this slightly to use it safely with records as well, by relying on the fact that records are represented internally as JavaScript objects. All we need to do is not restrict entries to objects:
[#bs.val] external entries: 'a => array((string, value)) = "Object.entries";
let fields = keys({foo: 42, bar: 24}); // returns [|("foo", 42), ("bar", 24)|]
This is still safe because all values are objects in JavaScript and we don't make any assumptions about the type of the values. If we try to use this with a primitive type we'll just get an empty array, and if we try to use it with an array we'll get the indexes as keys.
But because records need to be pre-defined this isn't going to be very useful. So all this said, I still suggest going with the list of tuples.
Note: This uses ReasonML syntax since that's what you asked for, but refers to the ReScript documentation, which uses the slightly different ReScript syntax, since the BuckleScript documentation has been taken down (Yeah it's a mess right now, I know. Hopefully it'll improve eventually.)
Maybe I am not understanding the question or the use case. But as far as I know there is no way to iterate over key/value pairs of a record. You may want to use a different data model:
hash table https://caml.inria.fr/pub/docs/manual-ocaml/libref/Hashtbl.html
Js.Dict (if you're working in bucklescript/ReScript) https://rescript-lang.org/docs/manual/latest/api/js/dict
a list of tuples
With a record all keys and value types are known so you can just write code to handle each one, no iteration needed.