Created
March 17, 2025 23:17
-
-
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
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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