I have a list `[A, B, B, B, A, B, B]` (`A` and `B`...
# announcements
p
I have a list
[A, B, B, B, A, B, B]
(
A
and
B
are instances of sealed classes). I want to transform it to
[[A], [B, B, B], [A], [B, B]]
(so, a list of lists, grouped by classes). How?
b
Have you tried with
fold()
? Looks like you want to accumulate everything to a List of Lists
Copy code
val result = listOf(A, B, B, B, A, B, B)
            .fold(mutableListOf(listOf<String>()), { acc, value ->
                if (value in acc.last() || acc.last().isEmpty())
                    acc.apply { add(removeLast() + value) }
                else
                    acc.apply { add(listOf(value)) }
            })
p
Thanks for the
fold
tip! I came up with something similar.
u
Folding is a bit overkill when the accumulator is mutable.
mutableListOf(listOf<String>).also { acc -> listOf(A, B ...).forEach { ... }}
would suffice
Note also that the inner lists are immutable and use an expensive linear time copy for each
... + value
operation. This will run in quadratic time in the length of the longest subsequence of repeated elements.
p
Copy code
menu.fold(mutableListOf<MutableList<MenuItem>>()) { list, item ->
    when (item) {
        is MenuHeader -> {
            list.add(mutableListOf(item))
            list.add(mutableListOf())
        }
        is MenuLink -> list.last().add(item)
    }
    list
}
I did it like this.
e
Copy code
inline fun <T, K> Iterable<T>.groupConsecutiveBy(
    keySelector: (T) -> K
): List<List<T>> = buildList {
    val lastGroup = this@groupConsecutiveBy.fold(mutableListOf<T>()) { acc, value ->
        if (acc.isEmpty() || keySelector(acc.last()) == keySelector(value)) {
            acc.apply { add(value) }
        } else {
            add(acc)
            mutableListOf(value)
        }
    }
    if (lastGroup.isNotEmpty()) add(lastGroup)
}
i
If we had such function in stdlib, under which name would you expect to find it?
e
it's called
groupBy
in Haskell and
itertools.groupby
in Python, but that name's already taken. it's similar to the
uniq
command, but I don't think that's a good name. StreamEx calls it
groupRuns
, Google Mug calls it
groupConsecutiveIf
. I think
groupConsecutiveBy
would be reasonable in Kotlin
u
The first thing I came up with as an alternative to
groupBy
was
groupRunsBy
, but I think
groupConsecutiveBy
is equally good.
i
Thanks I've noted the proposed names in the related issue: https://youtrack.jetbrains.com/issue/KT-8280
@Pitel If you could also describe your use case in a less abstract way (e.g. what are those sealed classes you're trying to group this way) in that issue's comments, it would help a lot to decide on this feature.
e
hmm. a signature of
(Sequence<T>).((T) -> K) -> Sequence<Pair<K, Sequence<T>>>
has some potential for misuse: unless the implementation performs (potentially unbounded) buffering, each subsequence must be consumed at most once before the next, and not at all after the next has started. this contract is clearer in StreamEx's and Mug's use of Stream/Collector, and could be made more obvious with a design like
Copy code
fun <T, K, R> Sequence<T>.groupConsecutiveByThen(keySelector: (T) -> K, aggregator: (K, Sequence<T>) -> R): Sequence<R>
but that's pretty ugly… of course none of this is a problem for List<T>, just Sequence<T>'
actually maybe that is resolvable by using something like the existing Grouping intermediate type