This question already has answers here:
How to split a list into sublists using a predicate with Kotlin?
(2 answers)
Closed 1 year ago.
I want to group specific items in a list based on a 'split predicate'. An example of what I mean:
val list = listOf("1", "2", "3", "", "4", "5", "6", "", "7")
// looking for: a nice readable (maybe functional?) way to get to:
val result = listOf(
listOf("1", "2", "3"),
listOf("4", "5", "6"),
listOf("7"),
)
As you see, I want to split the list on items which are item.isBlank() - which I also want to drop in the process.
I know how to do it imperatively, but I think there must be a nice, readable, functional way!
Thanks a lot & regards
Marc
I had a solution that is similar to Matt's (had to run to a meeting and couldn't post it earlier!). It is materially the same, but allows you to specify an arbitrary predicate to split on, accounts for the fact that you might start with a blank item and end up with an unwanted empty list (or if you have two items that match the predicate in a row), and is defined as an extension function:
fun <T> List<T>.split(predicate: (T) -> Boolean): List<List<T>> =
fold(mutableListOf(mutableListOf<T>())) { acc, t ->
if (predicate(t)) acc.add(mutableListOf())
else acc.last().add(t)
acc
}.filterNot { it.isEmpty() }
And to call it:
list.split { it.isBlank() }
// Returns [[1, 2, 3], [4, 5, 6], [7, 8, 9]]
There's probably a cleaner way but I think this solves it
list.fold (mutableListOf(mutableListOf<String>())) { acc, item ->
if (item.isBlank()) {
acc.add(mutableListOf())
} else {
acc.last().add(item)
}
acc
}
Related
i have a list in my project like
val list1 = listOf("1", "pig", "3", "cow")
and i need to swap it to like "pig", "1", "cow", "3"
and the number and words will be random so it cant be only on these words
can anyone tell me how to do this?
You can combine chunked() to get pairs of items and then flatMap() to swap them and recreate a flat list:
list1
.chunked(2)
.flatMap { listOf(it[1], it[0]) } // or: it.reversed()
However, it looks pretty weird that you have a list like this in the first place. If items of this list are stored in two subsequent indexes, then such design complicates maintaining and processing of the data. Instead, create a data class for both fields and create a list of such data items:
val list = listOf(Animal("1", "pig"), Animal("3", "cow"))
data class Animal(
val id: String,
val name: String,
)
I am trying to match a JSON array with a predefined expected json. The problem is that one of the key values in actual JSON is a set of strings delimited by "|". Here is how it looks :
actualJSON = [
{
"a": "varA",
"mix: "X|Y|Z",
},
{
"b": "B",
"c": "C"
} ]
expectedJSON = [
{
"a": "varA",
"mix: "Y|Z|X",
},
{
"b": "B",
"c": "C"
} ]
Issue here is the mix key that represents a set of strings and the value can be any combination of "X|Y|Z" without any specific order like "Z|Y|X" etc. When the value of mix is Y|Z|X then
* match actualJSON contains expectedJSON
works fine but in other cases it fails as expected. Is there a way to do the matching when key value is dynamic?
My first suggestion is as far as possible to write tests where the response is 100% predictable and don't waste time on these weird cases. Also refer: https://stackoverflow.com/a/50350442/143475
That said this is easy to do if you write a JS function:
* def response = { foo: 'X|Y|Z' }
* def hasXyz = function(x){ return x.includes('X') && x.includes('Y') && x.includes('Z') }
* match response == { foo: '#? hasXyz(_)' }
I leave it to you to figure out better logic if you want. Refer: https://github.com/karatelabs/karate#self-validation-expressions
Introduction
Let's say I have a Kotlin enum class:
enum class Type {
ONE,
TWO,
THREE,
FOUR
}
and a following data class:
data class Item(
val name: String,
val type: Type
)
Then I have a Single that emits a list of Items – can by anything but for example purposes, let's say it looks like that:
val itemsSingle = Single.just(listOf(
Item("A", Type.ONE),
Item("B", Type.ONE),
Item("C", Type.TWO),
Item("D", Type.THREE),
))
Problem
What I'd like to achieve is to have an RxJava stream that will output a map where keys come from Type and values are lists of Items matching a given Type value (where an undetermined, unsorted list of Items is provided by some other Single stream). The signature would be:
Single<Map<Type, List<Item>> // or Observable<Map<Type, List<Item>>
One additional requirement is that the map's keys should always exhaust all values from Type enum even if the itemsSingle stream contains no items for some Type values (or no items at all). So, for the provided itemsSingle example stream the returned map should look like this:
{
ONE: [ Item(name: "A", type: ONE), Item(name: "B", type: ONE) ],
TWO: [ Item(name: "C", type: TWO) ],
THREE: [ Item(name: "D", type: THREE) ],
FOUR: []
}
Attempt
With all the above, I've kinda achieved the desired result with following steps:
To satisfy the requirement of exhausting all Type enum values I first create a map that has an empty list for all possible Type values:
val typeValuesMap = Type.values().associate { it to emptyList<Item>() }
val typeValuesMapSingle = Single.just(typeValuesMap)
// result: {ONE=[], TWO=[], THREE=[], FOUR=[]}
I can get a map that contains items from itemsSingle grouped under respective Type value keys:
val groupedItemsMapSingle = itemsSingle.flattenAsObservable { it }
.groupBy { it.type }
.flatMapSingle { it.toList() }
.toMap { list -> list[0].type } // the list is guaranteed to have at least one item
// result: {ONE=[Item(name=A, type=ONE), Item(name=B, type=ONE)], THREE=[Item(name=D, type=THREE)], TWO=[Item(name=C, type=TWO)]}
finally I can combine both lists using the combineLatest operator and overwriting initial empty list of items for a given Type value if itemsSingle contained any Items for this Type value:
Observable.combineLatest(
typeValuesMapSingle.flattenAsObservable { it.entries },
groupedItemsMapSingle.flattenAsObservable { it.entries }
) { a, b -> listOf(a, b) }
.defaultIfEmpty(typeValuesMap.entries.toList()) // in case itemsSingle is empty
.flatMapIterable { it }
.collect({mutableMapOf<Type, List<Item>>()}, { a, b -> a[b.key] = b.value})
// result: {FOUR=[], ONE=[Item(name=A, type=ONE), Item(name=B, type=ONE)], THREE=[Item(name=D, type=THREE)], TWO=[Item(name=C, type=TWO)]}
Summary
As you can see, it's quite a lot of code for a seemingly simple operation. So my question is – is there a simpler way to achieve the result I'm after?
Just merge a map of empty lists with a map of filled lists
val result = itemsSingle.map { items->
Type.values().associateWith { listOf<Item>() } + items.groupBy { it.type }
}
I have two lists:
val a = listOf("a", "b", "c")
val b = listOf("1", "2", "3")
I want to get a list that looks like this: ["a1", "b2", "c3"].
How to do this in the most efficient way?
The easiest way would look like this:
val c = a.zip(b).map {it.first + it.second }
I would like to split a list into a few sublists, but I have no idea how to do it.
Once of my ideas was splitting the list by index of element. For exmaple "B" index is 0, "S" index 2, so I would like to take a part between index 0 - 1 into the first sublist, then the second sublist should be the part between index 2 - 5.
Example of my list:
val listOfObj = listOf("B", "B" , "S", "B", "B", "X", "S", "B", "B", "P")
Result after splitting:
listOf(listOf("B","B"), listOf("S", "B", "B", "X"), listOf("S", "B", "B", "P") )
How do I achieve such a result?
Here it goes. I wrote it from my phone without checking but the idea is basic.
val result = mutableListOf<List<String>>()
var current = mutableList<String>()
listOfObj.forEach { letter ->
if (letter == "S") {
result.add(current)
current = mutableListOf<String>()
}
current.add(letter)
}
if (current.isNotEmpty()) {
result.add(current)
}
You can even create an extension function for a List<T> that gets a separator element as a parameter and returns a list of lists.