Split a list into groups based on index of elements - kotlin

I have a collection (a stream), and want to make it a stream of even and odd index elements Ex.
"slow" -> "solw" or "lwso"
fun part2(s: String) = s
.withIndex()
.groupBy { it.index % 2 }.values
.flatMap { it.map { v -> v.value } }
I learned withIndex lately, of course i could use mapIndexed. But no matter what I do, i need last step of v.value sort of. I wonder if there's any other way of writing simple things like this in kotlin.

You can replace n with 2
fun part(s: String, n: Int): String = s
.withIndex()
.groupBy(keySelector = { it.index % n }, valueTransform = { it.value })
.flatMap { it.value }
.joinToString(separator = "")

Related

Find the list item of Pairs with the minimum value

val index = listOf("abc", "def", "ghi", "jkl", "mno")
.mapIndexed { index, v ->
var t = 0
var p = 0
for (s in v) {
t += ("deh".get(p++).toInt() - s.toInt()).absoluteValue
}
Pair(index, v)
}
.minOf {
val iterator = iterator<Pair<Int, String>>(it)
if (!iterator.hasNext()) throw NoSuchElementException()
var minValue = iterator.next().second
while (iterator.hasNext()) {
val v = selector(iterator.next())
minValue = minOf(minValue, v)
}
return minValue
}
This is an alternative solution and works, but I am wondering if the solution can be done using mapOf as shown above?
val index = listOf("abc", "def", "ghi", "jkl", "jad", "jaa", "mno")
.mapIndexed { index, v ->
var t = 0
var p = 0
for (s in v) {
t += ("jac".get(p++).toInt() - s.toInt()).absoluteValue
}
Pair(index, t)
}.toSortedSet(compareBy { it.second })
.first()
I create a map of Pairs and I want to find the index of the map item where the Pair with the value (the second item in the pair) is the lowest value (minimum) of all the pairs. If possible, I would like to use the minOf function. The first example above will not compile because of bugs in the minOf function. Not sure how to iterate over the map of Pairs.
You can use minBy {} to get the minimum value from a collection, although often it's safer to use minByOrNull {} in case no minimal value can be computed (which could happen if the list is empty).
import kotlin.math.absoluteValue
fun main() {
val minElement = listOf("abc", "def", "ghi", "jkl", "jad", "jaa", "mno")
.minByOrNull { v ->
var t = 0
var p = 0
for (s in v) {
t += ("jac".get(p++).toInt() - s.toInt()).absoluteValue
}
t
}
println(minElement)
}
jad
Run in Kotlin Playground
If you also want to find the index of the minimal value, then you can use withIndex(), which will pair each list element with its index.
import kotlin.math.absoluteValue
fun main() {
val minIndexedElement = listOf("abc", "def", "ghi", "jkl", "jad", "jaa", "mno")
.withIndex() // adds the index to each element
.minByOrNull { (_, v) ->
var t = 0
var p = 0
for (s in v) {
t += ("jac".get(p++).toInt() - s.toInt()).absoluteValue
}
t
}
println(minIndexedElement)
}
IndexedValue(index=4, value=jad)
Run in Kotlin Playground
Another solution would be to extract the character codes from "jar" and from each item, and then to zip the two code lists. zip allows for a transform closure in which the calculation with the two codes can be made. After that sum() gives the wanted value.
data class Result(val index: Int, val string: String, val computedValue: Int)
val list = listOf("abc", "def", "ghi", "jkl", "jad", "jaa", "mno")
val result = list
.mapIndexed { idx, str ->
val codes1 = "jac".toCharArray().map { it.code }
val codes2 = str.toCharArray().map { it.code }
val computed = codes1.zip(codes2) { code1, code2 -> (code1 - code2).absoluteValue }.sum()
Result(idx, str, computed)
}
.minByOrNull { it.computedValue }
println(result) // Output: Result(index=4, string=jad, computedValue=1)
Instead of the helper data class Result a Triple instance could be used:
...
Triple(idx, str, computed)
}
.minByOrNull { it.third }
// Output: (4, jad, 1)
Or if the calculated value is not needed, it could be dropped like that:
...
?.let { it.first to it.second }
// Output: (4, jad)

Kotlin distinctBy with condition

I have array with multiple objects with the same key, Other objects have empty values and I was hoping to use distinctBy to remove duplicates and get objects which values has the longest string.
data class ObjA(val key: String, val value: String)
fun test() {
val listOfA = mutableListOf(
ObjA("one", ""), //This will be taken in distinctBy.
ObjA("one", "o"),
ObjA("one", "on"),
ObjA("one", "one"), //This will be ignored in distinctBy. I WANT THIS ONE.
ObjA("two", ""), //This will be returned in distinctBy
ObjA("two", "2"),
ObjA("two", "two"), //But I want this.
ObjA("three", "3"),
ObjA("four", "4"),
ObjA("five", "five")
)
val listOfAWithNoDuplicates = listOfA.distinctBy { it.key }
for (o in listOfAWithNoDuplicates) {
println("key: ${o.key}, value: ${o.value}")
}
}
Output
key: one, value:
key: two, value:
key: three, value: 3
key: four, value: 4
key: five, value: five
How to make this work. Any help will be appreciated.
As distinctBy just returns the distinct keys based on your selector (and in the order of the list), you end up with unique keys, but not yet with the values you want.
For that particular use-case I would probably just sort beforehand, followed by the distinctBy
listOfA.sortedByDescending { it.value.length }
.distinctBy { it.key }
Which creates a new list at sortedByDescending or you just sort the current list beforehand (sortByDescending) and apply distinctBy later on, e.g.:
listOfA.sortByDescending { it.value.length }
listOfA.distinctBy { it.key }
In both cases you get a new List<ObjA> with the expected values.
Several other variants come to my mind as well. All those variants will put the results into a Map<String, ObjA> where the key is actually the unique ObjA.key. You may want to call .values directly if you are not interested in the key/ObjA-mapping.
variant using groupingBy and reduce:
listOfA.groupingBy { it.key }
.reduce { _, acc, e->
maxOf(e, acc, compareBy { it.value.length })
}
variant using a plain forEach/for and filling its own Map:
val map = mutableMapOf<String, ObjA>()
listOfA.forEach {
map.merge(it.key, it) { t, u ->
maxOf(t, u, compareBy { it.value.length })
}
}
variant using fold and merge (very similar to the previous... just using fold instead of for/forEach):
listOfA.fold(mutableMapOf<String, ObjA>()) { acc, objA ->
acc.apply {
merge(objA.key, objA) { t, u ->
maxOf(t, u, compareBy { it.value.length })
}
}
}
variant using groupBy followed by mapValues (but you are then actually creating 1 map which you discard immediately):
listOfA.groupBy { it.key } // the map created at this step is discarded and all the values are remapped in the next step
.mapValues { (_, values) ->
values.maxBy { it.value.length }!!
}
You can use maxBy{} like:
val x = listOfAWithNoDuplicates.maxBy { it.key.length }
println(x)
Output
ObjA(key=three, value=3)

Kotlin - from a list of Maps, to a map grouped by key

I have a List<Map<Branch,Pair<String, Any>>> that I would like to convert in a single Map<Branch,List<Pair<String, Any>>> .
So if I have an initial list with simply 2 elements :
List
1. branch1 -> Pair(key1,value1)
branch2 -> Pair(key2,value2)
2. branch1 -> Pair(key1a,value1a)
I want to end up with :
Map
branch1 -> Pair(key1,value1)
Pair(key1a,value1a)
branch2 -> Pair(key2,value2)
so a kind of groupBy, using all the values of the keys in the initially nested maps..
I have tried with
list.groupBy{it-> it.keys.first()}
but obviously it doesn't work, as it uses only the first key. I want the same, but using all keys as individual values.
What is the most idiomatic way of doing this in Kotlin ? I have an ugly looking working version in Java, but I am quite sure Kotlin has a nice way of doing it.. it's just that I am not finding it so far !
Any idea ?
Thanks
The following:
val result =
listOfMaps.asSequence()
.flatMap {
it.asSequence()
}.groupBy({ it.key }, { it.value })
will give you the result of type Map<Branch,List<Pair<String, Any>>> with the contents you requested.
val list: List<Map<Branch, Pair<String, Any>>> = listOf()
val map = list
.flatMap { it.entries }
.groupBy { it.key }
.mapValues { entry -> entry.value.map { it.value } }
I've managed to write this.
data class Branch(val name: String)
data class Key(val name: String)
data class Value(val name: String)
val sharedBranch = Branch("1")
val listOfMaps: List<Map<Branch, Pair<Key, Value>>> = listOf(
mapOf(sharedBranch to Pair(Key("1"), Value("1")),
Branch("2") to Pair(Key("2"), Value("2"))),
mapOf(sharedBranch to Pair(Key("1a"), Value("1a")))
)
val mapValues: Map<Branch, List<Pair<Key, Value>>> = listOfMaps.asSequence()
.flatMap { map -> map.entries.asSequence() }
.groupBy(Map.Entry<Branch, Pair<Key, Value>>::key)
.mapValues { it.value.map(Map.Entry<Branch, Pair<Key, Value>>::value) }
println(mapValues)
Is it appliable for your needs?
Everyone else is using flatMap, but you can also consider using fold, which is a common operation for reducing a larger collection into a smaller one. (For example, you can fold a list of integers into a single sum; here, a list of maps into a single map).
Perhaps others will find this easier to read than the flatMap versions above:
val listOfMaps: List<Map<Key, Value>> = ...
val mergedMaps: Map<Key, List<Value>> =
listOfMaps
.fold(mutableMapOf()) { acc, curr ->
curr.forEach { entry -> acc.merge(entry.key, listOf(entry.value)) { new, old -> new + old } }
acc
}
What the above code is doing:
Create a new, empty map. This will be acc (that is, the accumulator).
Iterate through our list of maps.
Work on one map (curr) at a time.
For the current map, run over each of its key/value pairs.
For each key/value, call merge on acc, passing in a list of size one (wrapping the value). If nothing is associated with the key yet, that list is added; otherwise, it is appended to the list already there.
Return the accumulating map, so it's used again in the next step.
Surprised nobody has mentioned the associate function.
val listy: List<Map<String, Int>> =
listOf(mapOf("A" to 1, "B" to 2), mapOf("C" to 3, "D" to 4))
val flattened = listy
.flatMap { it.asSequence() }
.associate { it.key to it.value }
println(flattened)
will print out {A=1, B=2, C=3, D=4}
Extract it to an extension function
private fun <K, V> List<Map<K, V>>.group(): Map<K, List<V>> =
asSequence().flatMap { it.asSequence() }.groupBy({ it.key }, { it.value })
Use it like so:
val list = yourListOfMaps
val grouped = list.group()

How can I change this to use "for loop" instead of `forEach`

I'm struggling to change it to use a for loop and still do the same thing.
The program is supposed to read a file with some flights and this specific part of the program needs to read the file using two different days that the user inputs then it needs to show how many passengers there are per flight and each day.
And how it's done now works but I'm trying to change it to use a for loop as I said before but doesn't work because I don't know how to do the same thing as map does but only in the fun interval.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
val map = mapReservas(reservas)
for(day in dayInferior..daySuperior) {
map.forEach {
val reservasNum = it.key.first
val reservasDay = it.key.second
val reservasCount = it.value.count()
if (reservasDay == day) {
println("$reservasNum has $reservasCount passengers on day $day")
}
}
}
println()
println("Press Enter")
readLine()
}
fun mapReservas(reservas: List<Reservas>): Map<Pair<String, Int>, List<Reservas>> {
val map = mutableMapOf<Pair<String, Int>, MutableList<Reservas>>()
for (reserva in reservas) {
val key = reserva.numFlight to reserva.day
val list = map[key] ?: mutableListOf()
list.add(reserva)
map[key] = list
}
return map
}
All your code can be replaced only with one function.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
reservas.groupBy { reserva -> reserva.day to reserva.numFlight }
.filter { (key, _) -> key.first in dayInferior..daySuperior }
.forEach { (key, reservas) ->
val (reservasNum, reservasDay) = key
val reservasCount = reservas.count()
println("$reservasNum has $reservasCount passengers on day $reservasDay")
}
println()
println("Press Enter")
readLine()
}
Explaining:
As I undestand, at first you trying to group all your Reservas by day and numFlight. It can be done via one function groupBy where you pass pair of day and numFlight.
Filter all Reservas by day. It can be done by checking if day belongs to range dayInferior..daySuperior (operator in).
Print all reservas by using forEach.
Other things
Destructing declarations
val reservasNum = it.key.first
val reservasDay = it.key.second
same as
val (reservasNum, reservasDa) = it.key
Omitting one unused parameter in lamda:
.filter { (key, _) -> ... }
If you iterate with a for loop over the Map each element is a Pair. If you write (pair, list) you destructure each Pair which itself consists of a Pair and a List.
fun interval(reservas: List<Reservas>, dayInferior: Int, daySuperior: Int) {
val map = mapReservas(reservas)
for(day in dayInferior..daySuperior) {
for((pair, list) in map) {
val reservasNum = pair.first
val reservasDay = pair.second
val reservasCount = list.count()
// ...
}
}
// ...
}
Maybe this makes it more clear:
for(outerPair in map){
val (innerPair, list) = outerPair
val reservasNum = innerPair.first
val reservasDay = innerPair.second
val reservasCount = list.count()
// ...
}
I left this function (mapReservas) untouched intentionally, because maybe you are using it somewhere else. But you can improve it right away by using Type aliases (since Kotlin 1.1).
typealias FlightNum = String
typealias Day = Int
fun mapReservas(reservas: List<Reservas>):
Map<Pair<FlightNum, Day>, List<Reservas>> {
// ...
}
As you can see the code becomes much more readable if you use the destructure syntax and Type aliases.

fold pairs into map of set; create map entries if not existing already

using a list of pairs, want to transform them to a map of sets.
input: list of pairs is like this
listOf(Pair('bob', UGLY), Pair('sue', PETTY), Pair('bob', FAT))
desired output is a map of set where the key is first of pair, and the set is the second
mapOf('bob' to setOf(UGLY, FAT), 'sue' to setOf(PETTY))
I have tried this, but wow this is incredibly verbose. can this be reduced?
fun main(args: Array<String>) {
var m = HashMap<Int, MutableSet<Int>>()
listOf(1 to 1, 2 to 2, 1 to 3).map {
val set = m.getOrPut(it.first, { listOf<Int>().toMutableSet() })
set.add(it.second)
set
}
println (m)
}
-> {1=[1, 3], 2=[2]}
// yet another version, yields the correct result, but I feel a lack of clarity
// that maybe I'm missing a library function that would suit the purpose.
listOf(1 to 1, 2 to 2, 1 to 3).fold(m, {
mapSet, pair ->
val set = mapSet.getOrPut(pair.first, { listOf<Int>().toMutableSet() })
set.add(pair.second)
mapSet
})
-> {1=[1, 3], 2=[2]}
You can use groupBy and then a mapValues like this:
fun main(args: Array<String>) {
val pairs = listOf(Pair("bob", "UGLY"), Pair("sue", "PETTY"), Pair("bob", "FAT"))
val result = pairs
.groupBy { it.first }
.mapValues { it.value.map { p -> p.second }.toSet() }
println(result)
}