I have a list with items:
Header1, Item1, Item2, Item3, Header2, Item4, Item5, Header3, Item6, Item7
Every single header has title.
How I can group that elements into Header items, Header items etc. and sort it alphabetically by header title?
CodePudding user response:
I believe this problem isn't trivial to solve with functional-style transformations like group by, etc. Such operations are not that useful if we need to reference previous items in the collection.
I suggest using imperative style and good old loops:
fun Iterable<Base>.groupByHeader(): Map<Header, List<Item>> {
val iter = iterator()
if (! iter.hasNext()) {
return emptyMap()
}
var currHeader = requireNotNull(iter.next() as? Header)
var currItems = mutableListOf<Item>()
val result = sortedMapOf(compareBy(Header::title), currHeader to currItems)
iter.forEach { item ->
when (item) {
is Header -> {
currHeader = item
currItems = mutableListOf()
result[currHeader] = currItems
}
is Item -> currItems.add(item)
else -> throw IllegalArgumentException()
}
}
return result
}
If we really like functional style then reduce/fold is probably our best option. We can use fold()
in a similar way as above, but we can also utilize runningFold()
like this:
fun Iterable<Base>.groupByHeader(): Map<Header, List<Item>> {
return runningFold(Pair<Header?, Item?>(null, null)) { acc, item ->
when (item) {
is Header -> item to null
is Item -> acc.first to item
else -> throw IllegalArgumentException()
}
}.filter { it.first != null && it.second != null }
.groupByTo(sortedMapOf(compareBy(Header::title)), { it.first!! }, { it.second!! })
}
This is shorter, but probably harder to understand, much less performant and it ignores Item
objects placed at the beginning - first solution throws an exception in such a case.