Kotlin sorting Mutable map of strings - kotlin

Why cannot I sort a mutable map of string. My map is declared as follows.
val schedule: MutableMap<String, ArrayList<String>>
It gives me schedule object as follows.
{1=[1], 0=[0], 3=[3], 2=[2], 5=[5], 4=[4, 14.07, 16.07, 01.08, 10.08], 6=[6]}
Now for day 4, I would to sort the elements in ascending order, ideally ignoring first element. I want my output to look like below.
{1=[1], 0=[0], 3=[3], 2=[2], 5=[5], 4=[4, 1.08, 10.08, 14.07, 16.07], 6=[6]}
I can access the required day with schedule.schedule["4"]?.sorted()
but this doesn't do anything. I tired converting Strings to Ints but still no luck.

Use sort() instead of sorted().
sort() sorts "in place": it mutates the ArrayList
sorted() returns a new sorted ArrayList
Try it: https://repl.it/repls/BitterRapidQuark
val map = mutableMapOf("4" to arrayListOf<String>("4", "14.07", "16.07", "01.08", "10.08"))
println("Original: " + map) // {4=[4, 14.07, 16.07, 01.08, 10.08]}
map["4"]?.sorted()
println("Not mutated: " + map) // {4=[4, 14.07, 16.07, 01.08, 10.08]}
map["4"]?.sort()
println("Mutated: " + map) // {4=[01.08, 10.08, 14.07, 16.07, 4]}

Related

Extracting items from a Map using a step

In Kotlin you can extract list items using a step like this:
val numbers = listOf("one", "two", "three", "four", "five", "six")
println(numbers.slice(0..4 step 2))
Is there a similar way to do this for a map? So instead of listOf, I'm using mapOf.
First you need to think about whether it makes sense to use slice on a Map. The Map interface does not guarantee an order, so it does not have the concept of indices that you can select from, which is what slice does.
If your Map is backed by a specific implementation that guarantees an iteration order, such as LinkedHashMap, then it can make sense to convert the Map's entries into a List so you can slice them, and then you can convert the result back to a Map.
val result = someLinkedHashMap
.entries
.toList()
.slice(someRange)
.associate { it.key to it.value } // change the filtered entries list into a new map
The associate function's documentation guarantees a Map implementation that has consistent execution order.
slice() is an extension function for Arrays and Lists.
To convert a Map to a List, first get the entries Set, and then convert the Set into a List.
Run in Kotlin Playground
fun main() {
val numbers = mapOf(
"one" to 1,
"two" to 2,
"three" to 3,
"four" to 4,
"five" to 5,
"six" to 6,
)
// get the Map entries, and convert them to a List - then slice
val sliced = numbers.entries.toList().slice(0..4 step 2)
println(sliced)
}
Output:
[one=1, three=3, five=5]

Building string from list of list of strings

I rather have this ugly way of building a string from a list as:
val input = listOf("[A,B]", "[C,D]")
val builder = StringBuilder()
builder.append("Serialized('IDs((")
for (pt in input) {
builder.append(pt[0] + " " + pt[1])
builder.append(", ")
}
builder.append("))')")
The problem is that it adds a comma after the last element and if I want to avoid that I need to add another if check in the loop for the last element.
I wonder if there is a more concise way of doing this in kotlin?
EDIT
End result should be something like:
Serialized('IDs((A B,C D))')
In Kotlin you can use joinToString for this kind of use case (it deals with inserting the separator only between elements).
It is very versatile because it allows to specify a transform function for each element (in addition to the more classic separator, prefix, postfix). This makes it equivalent to mapping all elements to strings and then joining them together, but in one single call.
If input really is a List<List<String>> like you mention in the title and you assume in your loop, you can use:
input.joinToString(
prefix = "Serialized('IDs((",
postfix = "))')",
separator = ", ",
) { (x, y) -> "$x $y" }
Note that the syntax with (x, y) is a destructuring syntax that automatically gets the first and second element of the lists inside your list (parentheses are important).
If your input is in fact a List<String> as in listOf("[A,B]", "[C,D]") that you wrote at the top of your code, you can instead use:
input.joinToString(
prefix = "Serialized('IDs((",
postfix = "))')",
separator = ", ",
) { it.removeSurrounding("[", "]").replace(",", " ") }
val input = listOf("[A,B]", "[C,D]")
val result =
"Serialized('IDs((" +
input.joinToString(",") { it.removeSurrounding("[", "]").replace(",", " ") } +
"))')"
println(result) // Output: Serialized('IDs((A B,C D))')
Kotlin provides an extension function [joinToString][1] (in Iterable) for this type of purpose.
input.joinToString(",", "Serialized('IDs((", "))')")
This will correctly add the separator.

How to get size of specfic value inside array Kotlin

here is example of the list. I want to make dynamic where maybe the the value will become more.
val list = arrayListOf("A", "B", "C", "A", "A", "B") //Maybe they will be more
I want the output like:-
val result = list[i] + " size: " + list[i].size
So the output will display every String with the size.
A size: 3
B size: 2
C size: 1
If I add more value, so the result will increase also.
You can use groupBy in this way:
val result = list.groupBy { it }.map { it.key to it.value.size }.toMap()
Jeoffrey's way is better actually, since he is using .mapValues() directly, instead of an extra call to .toMap(). I'm just leaving this answer her since
I believe that the other info I put is relevant.
This will give a Map<String, Int>, where the Int is the count of the occurences.
This result will not change when you change the original list. That is not how the language works. If you want something like that, you'd need quite a bit of work, like overwriting the add function from your collection to refresh the result map.
Also, I see no reason for you to use an ArrayList, especially since you are expecting to increase the size of that collection, I'd stick with MutableList if I were you.
I think the terminology you're looking for is "frequency" here: the number of times an element appears in a list.
You can usually count elements in a list using the count method like this:
val numberOfAs = list.count { it == "A" }
This approach is pretty inefficient if you need to count all elements though, in which case you can create a map of frequencies the following way:
val freqs = list.groupBy { it }.mapValues { (_, g) -> g.size }
freqs here will be a Map where each key is a unique element from the original list, and the value is the corresponding frequency of that element in the list.
This works by first grouping elements that are equal to each other via groupBy, which returns a Map<String, List<String>> where each key is a unique element from the original list, and each value is the group of all elements in the list that were equal to the key.
Then mapValues will transform that map so that the values are the sizes of the groups instead of the groups themselves.
An improved approach, as suggested by #broot is to make use of Kotlin's Grouping class which has a built-in eachCount method:
val freqs = list.groupingBy { it }.eachCount()

How to sort a string alphabetically in Kotlin

I want to reorder the string "hearty" to be in alphabetical order: "aehrty"
I've tried:
val str = "hearty"
val arr = str.toCharArray()
println(arr.sort())
This throws an error. I've also tried the .split("") method with the .sort(). That also throws an error.
You need to use sorted() and after that joinToString, to turn the array back into a String:
val str = "hearty"
val arr = str.toCharArray()
println(arr.sorted().joinToString("")) // aehrty
Note: sort() will mutate the array it is invoked on, sorted() will return a new sorted array leaving the original untouched.
So your issue is that CharArray.sort() returns Unit (as it does an in-place sort of the array). Instead, you can use sorted() which returns a List<Char>, or you could do something like:
str.toCharArray().apply { sort() }
Or if you just want the string back:
fun String.alphabetized() = String(toCharArray().apply { sort() })
Then you can do:
println("hearty".alphabetized())

In Kotlin, how can I take the first n elements of an array

In Kotlin, how can I take the first n elements of this array:
val allColours = arrayOf(
Pair(Color.RED, Color.WHITE),
Pair(Color.RED, Color.BLACK),
Pair(Color.YELLOW, Color.BLACK),
Pair(Color.GREEN, Color.WHITE),
Pair(Color.BLUE, Color.WHITE),
Pair(Color.BLUE, Color.WHITE),
Pair(Color.CYAN, Color.BLACK),
Pair(Color.WHITE, Color.BLACK))
So how can I fill pegColours with the first say 3 Pairs?
var pegColours: Array<Pair<Color,Color>> = //???
I tried allColours.take but it gave an error:
Expecting an element
You need to specify the number of items you want to take.
allColours.take(3)
For a random number of random indices, you can use the following:
val indexes = arrayOf(2, 4, 6)
allColours.filterIndexed { index, s -> indexes.contains(index) }
Note that you can write an extension method for this:
fun <T> Array<T>.filterByIndices(vararg indices: Int) = filterIndexed { index, _ -> indices.contains(index) }
Alternatively, if the indices are consecutive, you can use slice:
allColours.slice(1..3)
The problem with your code that you create pairs with color constants which are Ints (allColours has type Array<Pair<Int, Int>>), but you expect Array<Pair<Color, Color>>. What you have to do is change type pegColours type and use take:
var pegColours: Array<Pair<Int, Int>> = allColours.take(3).toTypedArray()
Also you have to call toTypedArray() cause Array.take returns List rather than Array. Or you can change pegColours type as following:
var pegColours: List<Pair<Int, Int>> = allColours.take(3)
I know you already proposed the usage of take, but alternatively ranges and a simple map also help to write idiomatic code as shown next:
var pegColours = (0 until 3)
.map { allColours[it] }
.toTypedArray()
You are very close :)
val allColours = arrayOf("red", "blue", "green")
kotlin.io.println(allColours.take(2))
Will give you first two elements ["red", "blue"]
You have to specify the number of elements you want to take from the array