Sort a map of objects indescending order - kotlin

In Kotlin, I have a map of objects. The key is a string and the object stored with the key is an object. I want to sort the map in descending order by a property in the object. In this example, I want to sort by timestamp:
data class Image(val timestamp: Long)
val map = mutableMapOf<String, Image>()
map.put("a") = 999
map.put("b") = 555
map.put("c") = 333
map.toSortedMap.... Not sure of the rest
When sorted, the items should be arranged as:
c, b, a
Not sure how to implement the sorting.

This works. It won't return a map but a list of mapped items instead. For my sample, it returns:
List<MutableMap.MutableEntry<String, Int>
and to get this, you call:
map.entries.sortedWith(compareByDescending({ it.value }))
If the values refer to objects instead of basic types (Int, Boolean, etc), the objects are not copied but only a reference to them are placed in the returned list. That means that the object will exist in both the returned list and the original map. To destroy the object, it has to be removed from both the list and the map.

Related

Kotlin modifying dataclass object key from map changes the reference after modifying variable

I have a MutableMap that its keys are objects from a DataClass (User dataclass), and the values are arrays from other Dataclass (Dog dataclass). If i have a variable with a User object, and i put it in the MutableMap and i test if the map contains the User, it says that is true. But after putting the user in the MutableMap if i change one of the attributes of the User object using the variable that holds the User object, the Map says that it doesnt contains the user object.
This is an example
data class User(
var name: String,
var project: String,
)
data class Dog(
var kind: String
)
fun main(args: Array<String>) {
var mapUserDogs: MutableMap<User, MutableList<Dog>> = mutableMapOf()
var userSelected = User("name2", "P2")
mapUserDogs.put(
User("name1", "P1"),
mutableListOf(Dog("R1"), Dog("R2"))
)
mapUserDogs.put(
userSelected,
mutableListOf(Dog("R21"), Dog("R31"))
)
println(userSelected)
println(mapUserDogs.keys.toString())
println(mapUserDogs.contains(userSelected))
println(mapUserDogs.values.toString())
println("\n")
userSelected.name = "Name3"
println(userSelected)
println(mapUserDogs.keys.toString())
println(mapUserDogs.contains(userSelected))
println(mapUserDogs.values.toString())
}
The prints statements show this:
User(name=name2, project=P2)
[User(name=name1, project=P1), User(name=name2, project=P2)]
true
[[Dog(kind=R1), Dog(kind=R2)], [Dog(kind=R21), Dog(kind=R31)]]
User(name=Name3, project=P2)
[User(name=name1, project=P1), User(name=Name3, project=P2)]
false
[[Dog(kind=R1), Dog(kind=R2)], [Dog(kind=R21), Dog(kind=R31)]]
Process finished with exit code 0
But it doesn't make sense. Why the map says that it doesn't contains the user object if its clear that it still holds the reference to it after being modified?
User(name=Name3, project=P2)
[User(name=name1, project=P1), User(name=Name3, project=P2)]
The user in the keys collection was also changed when i modified the userSelected variable, so now the object has it attribute name as "Name3" in both the variable and in the Map keys, but it still says that it doesnt contains it.
What can i do so that i can change the attributes in the userSelected object and the Map still return true when using the "contains" method?. And doing the same process in reverse shows the same. If i get from the map the user and i modify it, the userVariable is also modified but if i later test if the map contains the userVariable, it says false.
What can i do so that i can change the attributes in the userSelected object and the Map still return true when using the "contains" method?
There is nothing you can do that preserves both your ability to look up the entry in the map and your ability to modify the key.
Make your data class immutable (val instead of var, etc.), and when you need to change a mapping, remove the old key and put in the new key. That's really the only useful thing you can do.
To add to Louis Wasserman's correct answer:
This is simply the way that maps work in Kotlin: their contract requires that keys don't change significantly once stored. The docs for java.util.Map* spell this out:
Note: great care must be exercised if mutable objects are used as map keys. The behavior of a map is not specified if the value of an object is changed in a manner that affects equals comparisons while the object is a key in the map.
The safest approach is to use only immutable objects as keys. (Note that not just the object itself, but any objects it references, and so on, must all be immutable for it to be completely safe.)
You can get away with mutable keys as long as, once the key is stored in the map, you're careful never to change anything that would affect the results of calling equals() on it. (This may be appropriate if the object needs some initial set-up that can't all be done in its constructor, or to avoid having both mutable and immutable variants of a class.) But it's not easy to guarantee, and leaves potential problems for future maintenance, so full immutability is preferable.
The effects of mutating keys can be obvious or subtle. As OP noticed, mappings may appear to vanish, and maybe later reappear. But depending on the exact map implementation, it may cause further problems such as errors when fetching/adding/removing unrelated mappings, memory leaks, or even infinite loops. (“The behaviour… is not specified” means that anything can happen!)
What can i do so that i can change the attributes in the userSelected object and the Map still return true when using the "contains" method?
What you're trying to do there is to change the mapping. If you store a map from key K1 to value V, and you mutate the key to hold K2, then you're effectively saying “K1 no longer maps to V; instead, K2 now maps to V.”
So the correct way to do that is to remove the old mapping, and then add the new one. If the key is immutable, that's what you have to do — but even if the key is mutable, you must remove the old mapping before changing it, and then add a new mapping after changing it, so that it never changes while it's stored in the map.
(* The Kotlin library docs don't address this, unfortunately — IMHO this is one of many areas in which they're lacking, as compared to the exemplary Java docs…)
That happens because data classes in Kotlin are compared by value, unlike regular classes which are compared by reference. When you use a data class as a key, the map gets searched for a User with the same string values for the name and project fields, not for the object itself in memory.
For example:
data class User(
var name: String,
var project: String,
)
val user1 = User("Daniel", "Something Cool")
val user2 = User("Daniel", "Something Cool")
println(user1 == user2) // true
works because, even though they are different objects (and thus different references), they have the same name and project values.
However, if I were to do this:
user1.name = "Christian"
println(user1 == user2) // false
the answer would be false because they don't share the same value for all of their fields.
If I made User a standard class:
class User(
var name: String,
var project: String,
)
val user1 = User("Daniel", "Something Cool")
val user2 = User("Daniel", "Something Cool")
println(user1 == user2) // false
the answer would be false because they are different references, even though they share the same values.
For your code to work the way you want, make User a regular class instead of a data class.
That's the key difference between regular classes and data classes: a class is passed by reference, a data class is passed by value. Data classes are nothing more than collections of values with (optionally) some methods attached to them, classes are individual objects.

How to get last Key or Value in Kotlin Map collection

How can I get the last key or value in a Kotlin Map collection? It seems like it cannot be done by using an index value.
There's a couple ways it can be done. While you can't elegantly print a map directly, you may print it's entry set.
The first way, and the way that I DO NOT recommend, is by calling the .last() function on the entry set. This can be accomplished with testMap.entries.last(). The reason I don't recommend this method is because in real data this method is non-deterministic -- meaning there's no way to guarantee the characteristics of the value returned.
While I don't recommend this method, I don't know your application and this may be sufficient.
I DO recommend using the .sortedBy() function on your entry set, and then calling .last() on it. This allows you to make some sort of assumption about the results returned, something that is typically necessary, otherwise why do you want the last?
See this example comparing the two methods and then comparing the method against the order you would get if you iterate with the .forEach function:
fun main(args: Array<String>) {
val testMap = mutableMapOf<Long, String>()
testMap[1] = "Hello"
testMap[5] = "World"
testMap[3] = "Foobar"
println(testMap.entries.last())
println(testMap.entries.sortedBy { it.key }.last())
println("\norder via loop:")
testMap
.entries
.forEach {
println("\t$it")
}
}
Take a look at the output:
3=Foobar
5=World
order via loop:
1=Hello
5=World
3=Foobar
Here we see that the value returned from .last(), is the last value that was inserted into the map - the same happens with .forEach. This is okay, but usually we want our map to have some sort of order. In this example, i've called for the entry set to be sorted by the key value, so that our call to .last() on the entry set returns the key/value pair with the largest key.

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!

Kotlin: How convert from Set to Map?

I have Set<FlagFilter> and I need to convert it to Map<Class<out FlagFilter>, FlagFilter>.
I tried doing it like this:
val result: Map<Class<out FlagFilter>, FlagFilter> =
target
.takeIf { !it.isEmpty() }
?.map { mapOf(it.javaClass to it) }
?: emptyMap<>()
but instead of a Map it turns out to be a List and I get a compilation error:
Type mismatch.
Required: Map<Class<out FlagFilter>, FlagFilter>
Found: List<Map<Class<FlagFilter>, FlagFilter>>
What am I doing wrong? As if there is not enough operation, but I do not understand yet which one
map isn't anything to do with the Map type - it's a functional programming term (coming from a broader mathematical concept) that basically means a function that maps each input value to an output value.
So it's a transformation or conversion that takes a collection of items, transforms each one, and results in another collection with the same number of items. In Kotlin, you get a List of items (unless you're working with a Sequence, in which case you get another Sequence that yields the same number of items).
It's worth getting familiar with the kotlin.collections package - there's lots of useful stuff in there! But each function has a specific purpose, in terms of how they process the collection and what they return:
map - returns a new value for each item
onEach - returns the original items (allows you to do something with each, then continue processing the collection)
forEach - returns nothing (allows you to do something with each, but as a final operation - you can't chain another operation, it's terminal)
filter - returns a subset of the original items, matching a predicate
first - returns a single item, matching a predicate
reduce - returns a single item, transforming the values to produce a single result
count - returns a single item, based on an attribute of the collection (not the values themselves)
There's more but you get the idea - some things transform, some things pass-through, some things give you an identically sized collection, or a potentially smaller one, or a single value. map is just the one that takes a collection, and gives you a collection of the same size where every item has been (potentially) altered.
Like Vadik says, use one of the associate* functions to turn values into a Map object, effectively transforming each item in the collection into a Map.Entry. Which is technically mapping it to a mapping, so I wasn't totally accurate earlier when I said it's nothing to do with Maps, but I figured I'd save this thought til the end ;)
Just use associateBy extension function:
val result: Map<Class<out FlagFilter>, FlagFilter> =
target.associateBy { it.javaClass }
Or if you want to fix your code, remove the excessive call of mapOf(). Just convert your Set to List of Pairs, and then call toMap() to create a map:
val result: Map<Class<out FlagFilter>, FlagFilter> =
target.map { it.javaClass to it }.toMap()