natpryce
10/22/2021, 7:23 AMList::chunked
doesn’t do what I want, nor does List::windowed
.
E.g. if I have a list, x = [“a”, “b”, “C”, “d”, “E”, “F”, “g”] and I want to split it into sublists where the case is the same within the sublist, I could pass in a predicate to this function something like…
x.split { x, b -> a.isUpperCase() != b.isUpperCase() }
giving me:
[["a", "b"], ["C"], ["d"], ["E", "F"], ["g"]]
I often need this operation, but always end up writing it with imperative code. Is there something in the stdlib I’m missing.mcpiroman
10/22/2021, 7:32 AMx.partition { it.isUpperCase() }
mcpiroman
10/22/2021, 7:33 AMnatpryce
10/22/2021, 7:36 AMnatpryce
10/22/2021, 7:39 AMephemient
10/22/2021, 8:17 AMnatpryce
10/22/2021, 5:07 PMnatpryce
10/22/2021, 5:11 PMephemient
10/22/2021, 5:14 PMx.groupConsecutiveBy { it.isUpperCase() }
(or whatever it is named, if it existed) would give you the same groupings you asked fornatpryce
10/22/2021, 5:18 PMnatpryce
10/22/2021, 5:19 PMnatpryce
10/22/2021, 5:21 PMPaul Griffith
10/22/2021, 8:32 PMFor my actual use case, I need to compare consecutive elements to decide whether to split the list between them (edited)That's sort of the opposite of a functional pattern, which is probably why there's no builtin stdlib function that's generalizable
ephemient
10/22/2021, 11:55 PMprivate object Sentinel@Suppress("UNCHECKED_CAST")
fun <T> Iterable<T>.splitWhen(predicate: (T, T) -> Boolean): List<List<T>> {
var last: Any? = Sentinel
var counter = 0
return groupBy {
if (last !is Sentinel && predicate(last as T, it)) {
counter++
}
last = it
counter
}.values.toList()
}
efficient (by streaming) but prone to misuse (breaks if inner sequence is not iterated exactly once fully):
private object Sentinel
@Suppress("UNCHECKED_CAST")
fun <T> Sequence<T>.splitWhen(predicate: (T, T) -> Boolean): Sequence<Sequence<T>> = sequence {
var next: Any? = Sentinel
val iterator = this@splitWhen.iterator()
while (next !== Sentinel || iterator.hasNext()) {
yield(sequence loop@{
for (item in iterator) {
val last = next
next = item
if (last !== Sentinel) {
yield(last as T)
if (predicate(last, item)) return@loop
}
}
if (next !== Sentinel) yield((next as T).also { next = Sentinel })
})
}
}
either way I don't see an elegant functional solutionnatpryce
10/23/2021, 1:17 PMilya.gorbunov
10/23/2021, 4:22 PMnatpryce
10/23/2021, 10:50 PMnatpryce
10/23/2021, 10:50 PMnatpryce
10/23/2021, 10:52 PMnatpryce
10/23/2021, 10:52 PMephemient
10/24/2021, 2:29 AMData.List.groupBy
is close, but it always performs comparisons between the first item of a group and the item to be considered (in your original example, it'll compare "a" and "C", not "b" and "C")ephemient
10/24/2021, 2:30 AMgroup-by
, which is like Kotlin's groupBy
, and partition-by
, which is similar to aforementioned proposal on YouTrack.natpryce
10/24/2021, 8:37 AMephemient
10/24/2021, 8:41 AMfun <T> Iterable<T>.splitWhen(predicate: (T, T) -> Boolean): List<List<T>> = this.asSequence().splitWhen(predicate).map { it.toList() }.toList()
or avoiding Sequence by building the lists in-line, as you mentionedephemient
10/24/2021, 8:48 AMgroupBy
is close, but it really only works with equivalence classes, as it does not use consecutive values