Skip to content

Instantly share code, notes, and snippets.

@odbol
Created March 17, 2025 23:17
Show Gist options
  • Save odbol/6057e9530cd7a817609fcdb42e71a67f to your computer and use it in GitHub Desktop.
Save odbol/6057e9530cd7a817609fcdb42e71a67f to your computer and use it in GitHub Desktop.
Kotlin utils: useful little utils for Kotlin functional programming: sequences, collections, grouping, etc
package com.odbol.utils
/**
* Groups consecutive items in an iterable by the given key. i.e. the sequence will emit a new list
* each time the key changes, containing all items since the last key change.
*
* This is similar to Kotlin's built-in `groupBy` function, but it groups consecutive items with the
* same key, rather than all items with the same key. Thus the same key can appear multiple times in
* the output sequence.
*
* @param keySelector a function that returns a key for each item
* @return a sequence of lists, where each list contains items with the same key, in the same order
* as the input iterable. Each list is guaranteed to have at least one item.
*/
fun <T, R> Iterable<T>.groupByConsecutively(keySelector: (T) -> R?): Sequence<List<T>> = sequence {
var lastKey: R? = null
var currentGroup: MutableList<T> = mutableListOf()
for (item in this@groupByConsecutively) {
val currentKey = keySelector(item)
if (currentKey != lastKey && currentGroup.isNotEmpty()) {
yield(currentGroup)
currentGroup = mutableListOf()
}
currentGroup.add(item)
lastKey = currentKey
}
if (currentGroup.isNotEmpty()) {
yield(currentGroup)
}
}
package com.odbol.utils
import com.google.common.truth.Truth.assertThat
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
@RunWith(JUnit4::class)
class CollectionUtilsTest {
@Test
fun groupByConsecutively_emptyInput_returnsEmpty() {
val input = listOf<String>()
val grouped = input.groupByConsecutively { it.first() }
assertThat(grouped.toList()).isEmpty()
}
@Test
fun groupByConsecutively_groupsConsecutively() {
val input = listOf("a1", "a2", "a3", "b1", "a4", "c1", "c2", "b2", "b3", "a5", "c3")
val grouped = input.groupByConsecutively { it.first() }
assertThat(grouped.toList())
.containsExactly(
listOf("a1", "a2", "a3"),
listOf("b1"),
listOf("a4"),
listOf("c1", "c2"),
listOf("b2", "b3"),
listOf("a5"),
listOf("c3"),
)
.inOrder()
}
@Test
fun groupByConsecutively_groupsConsecutively_withNullKeys() {
val input = listOf("a1", "a2", null, null, "b1")
val grouped = input.groupByConsecutively { it?.first() }
assertThat(grouped.toList())
.containsExactly(listOf("a1", "a2"), listOf(null, null), listOf("b1"))
.inOrder()
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment