How to sort objects list in case insensitive order? - kotlin

Let's say I have a list of Strings in Kotlin: stringList: MutableList<String>
Then it is is easy to sort such list in case insensitive order by doing this:
stringList.sortWith(String.CASE_INSENSITIVE_ORDER)
But how would I sort a list of objects in case insensitive order? For example: places: MutableList<Place>
Where Place is a simple class with 2 fields - name: String and id: Int, and I would like to sort these Places by the name field.
I tried to do something like this: places.sortedWith(compareBy { it.name }) but this solution doesn't take letter case into account.

It looks like compareBy might be able to take a Comparator as an argument, see the documentation here: https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.comparisons/compare-by.html
Try:
places.sortWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it.name }))
to sort the array in place, or you can assign it to a new variable using
val newArray = places.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it.name }))

Sort Ascending - case insensitive:
myList.sortedBy { it.name?.toLowerCase() }
Sort Descending - case insensitive:
myList.sortedByDescending { it.name?.toLowerCase() }

Related

Remove values with some similarities in a map using kotlin streams

I have a map<String, MyObject> where the values in the map could have same values for some variables (e.g., name parameter in my example). I would appreciate any solution using streams to remove entries with same name parameter on the value and keep only one of them with minimum id.
data class MyObject(val id: Int, val name: String)
For instance my map could be:
[
"first" to MyObject(1, "Alice"),
"second" to MyObject(2, "Bob"),
"third" to MyObject(3, "Alice")
]
and the expected output is:
[
"first" to MyObject(1, "Alice"),
"second" to MyObject(2, "Bob")
]
where the entry with key third is removed because the value has the same name as the first entry.
First, we need to identify all of the duplicate candidates. We can do that with groupBy, which works on any iterable (and Map is iterable with iteratee type Entry<K, V>).
myMap.entries
.groupBy({ entry => entry.value.name })
This produces a value of type Map<String, List<Entry<String, MyObject>>>.
Now, for each value in the map, we want to choose the element in the list with the smallest ID. We can select the minimum element by some condition using minBy and can do that to each element of a map with mapValues.
myMap.entries
.groupBy({ entry => entry.value.name })
.mapValues({ entry => entry.value.minBy({ it.value.id })!! })
(Note: groupBy always produces nonempty lists, since it's partitioning a set, so we can confidently !! assert that a minimum exists)
Finally, this returns a Map<String, Entry<String, MyObject>>, and you probably want to eliminate the excess Map layer.
myMap.entries
.groupBy({ entry -> entry.value.name })
.mapValues({ entry -> entry.value.minBy({ it.value.id })!! })
.values
.associate({ it.key to it.value })
Try it online!
There are multiple ways to do this using pure Kotlin, here is one relying on the fact that hash-maps do not allow duplicates. I am sure there are better solutions out there:
values.toList()
// If you care about the smaller id number value
// then sort by descending so they replace larger values.
.sortedByDescending { it.second.id }
// Will replace duplicates by hashing technique
.associateBy { it.second.name }
// Back to the same data structure
.map { it.value.first to it.value.second }.toMap()
Try it online!

Sort a list alphabetically while ignoring case?

I have a list of artist names that I want to sort alphabetically, for example, a list like:
val artists = listOf("Artist", "an Artist", "Cool Artist")
Would become:
["an Artist", "Artist", "Cool Artist"]
To sort them, I use:
artists.sortBy { it }.
The problem is that kotlin seems to treat uppercase A and lowercase a as seperate characters [Possibly because of their ASCII order], leading to a sort order like this:
[Artist, Cool Artist, an Artist]
I could write something like:
artists.sortBy { it.toUpperCase() }
To have all the characters treated the same, but this is likely to cause problems in other languages.
Is there a way to make kotlin sort this alphabetically while ignoring whether the characters are lowercase or uppercase?
I've found a better method for sorting in a case-insensitive order. You can use list.sortWith {} with a compareBy() call that uses String.CASE_INSENSITIVE_ORDER. Like this:
artists.sortWith(
compareBy(String.CASE_INSENSITIVE_ORDER, { it.name })
)
If you just have a bunch of strings, you could do:
artists.sortWith(
compareBy(String.CASE_INSENSITIVE_ORDER, { it })
)
This seems to sort properly, unlike list.sortBy {}.
You should use .sort()
example:
val artists = listOf("Artist", "an Artist", "Cool Artist")
artists.sort()

How to sort by multiple fields using case sensitive

As simple sorting by two values in Kotlin can be like this:
.sortedWith(compareBy({ it.lastName }, { it.firstName }))
How to sort by those 2 field and adding case insensitive order?
I know that this can be only applied to sorting by one field:
.sortedWith(compareBy(String.CASE_INSENSITIVE_ORDER, { it.lastName })
How to do this for both fields?
Not like this?
.sortedWith(compareBy({ it.lastName.toLowerCase() }, { it.firstName.toLowerCase() }))

How to group objects by values

I have an object that looks like this:
data class Product(val name: String,
val maker: List<String>)
Currently, the response that I receive from the backend (and it can't be changed) is as follows:
[{"name":"Car", "maker":["Audi"]},
{"name":"Car", "maker":["BMW"]},
{"name":"Motorcycle", "maker":["Yamaha"]},
{"name":"Motorcycle", "maker":["Kawasaki"]}
]
The actual list consists of a lot of data, but the name field can be trusted to be grouped by.
What would be a way for me to map this data so that the end result is something like this:
[{"name":"Car", "maker":["Audi", "BMW"]},
{"name":"Motorcycle", "maker":["Yamaha","Kawasaki"]}
]
Just use groupBy { ... } and then process the groups map entries, replacing them with a single Product:
val result = products.groupBy { it.name }.entries.map { (name, group) ->
Product(name, group.flatMap { it.maker })
}

MongoDB like statement with multiple fields

With SQL we can do the following :
select * from x where concat(x.y ," ",x.z) like "%find m%"
when x.y = "find" and x.z = "me".
How do I do the same thing with MongoDB, When I use a JSON structure similar to this:
{
data:
[
{
id:1,
value : "find"
},
{
id:2,
value : "me"
}
]
}
The comparison to SQL here is not valid since no relational database has the same concept of embedded arrays that MongoDB has, and is provided in your example. You can only "concat" between "fields in a row" of a table. Basically not the same thing.
You can do this with the JavaScript evaluation of $where, which is not optimal, but it's a start. And you can add some extra "smarts" to the match as well with caution:
db.collection.find({
"$or": [
{ "data.value": /^f/ },
{ "data.value": /^m/ }
],
"$where": function() {
var items = [];
this.data.forEach(function(item) {
items.push(item.value);
});
var myString = items.join(" ");
if ( myString.match(/find m/) != null )
return 1;
}
})
So there you go. We optimized this a bit by taking the first characters from your "test string" in each word and compared the tokens to each element of the array in the document.
The next part "concatenates" the array elements into a string and then does a "regex" comparison ( same as "like" ) on the concatenated result to see if it matches. Where it does then the document is considered a match and returned.
Not optimal, but these are the options available to MongoDB on a structure like this. Perhaps the structure should be different. But you don't specify why you want this so we can't advise a better solution to what you want to achieve.