diff --git a/libraries/stdlib/test/collections/GroupingTest.kt b/libraries/stdlib/test/collections/GroupingTest.kt new file mode 100644 index 00000000000..e32aee11238 --- /dev/null +++ b/libraries/stdlib/test/collections/GroupingTest.kt @@ -0,0 +1,113 @@ +package test.collections + +import org.junit.Test +import java.util.* +import kotlin.test.* + +class GroupingTest { + + @Test fun groupingProducers() { + + fun verifyGrouping(grouping: Grouping, expectedElements: List, expectedKeys: List) { + val elements = grouping.elementIterator().asSequence().toList() + val keys = elements.map { grouping.keyOf(it) } // TODO: replace with grouping::keyOf when supported in JS + + assertEquals(expectedElements, elements) + assertEquals(expectedKeys, keys) + } + + val elements = listOf("foo", "bar", "value", "x") + val keySelector: (String) -> Int = { it.length } + val keys = elements.map(keySelector) + + fun verifyGrouping(grouping: Grouping) = verifyGrouping(grouping, elements, keys) + + verifyGrouping(elements.groupingBy { it.length }) + verifyGrouping(elements.toTypedArray().groupingBy(keySelector)) + verifyGrouping(elements.asSequence().groupingBy(keySelector)) + + val charSeq = "some sequence of chars" + verifyGrouping(charSeq.groupingBy { it.toInt() }, charSeq.toList(), charSeq.map { it.toInt() }) + } + + // aggregate and aggregateTo operations are not tested, but they're used in every other operation + + + @Test fun foldWithConstantInitialValue() { + val elements = listOf("foo", "bar", "flea", "zoo", "biscuit") + // only collect strings with even length + val result = elements.groupingBy { it.first() }.fold(listOf()) { acc, e -> if (e.length % 2 == 0) acc + e else acc } + + assertEquals(mapOf('f' to listOf("flea"), 'b' to emptyList(), 'z' to emptyList()), result) + + val moreElements = listOf("fire", "zero", "abstract") + val result2 = moreElements.groupingBy { it.first() }.foldTo(HashMap(result), listOf()) { acc, e -> if (e.length % 2 == 0) acc + e else acc } + + assertEquals(mapOf('f' to listOf("flea", "fire"), 'b' to emptyList(), 'z' to listOf("zero"), 'a' to listOf("abstract")), result2) + } + + data class Collector(val key: K, val values: MutableList = mutableListOf()) + + @Test fun foldWithComputedInitialValue() { + + fun Collector.accumulateIfEven(e: String) = apply { if (e.length % 2 == 0) values.add(e) } + fun Collector.toPair() = key to values as List + + val elements = listOf("foo", "bar", "flea", "zoo", "biscuit") + val result = elements.groupingBy { it.first() } + .fold({ k, e -> Collector(k)}, { k, acc, e -> acc.accumulateIfEven(e) }) + + val ordered = result.values.sortedBy { it.key }.map { it.toPair() } + assertEquals(listOf('b' to emptyList(), 'f' to listOf("flea"), 'z' to emptyList()), ordered) + + val moreElements = listOf("fire", "zero") + val result2 = moreElements.groupingBy { it.first() } + .foldTo(HashMap(result), + { k, e -> error("should not be called for $k") }, + { k, acc, e -> acc.accumulateIfEven(e) }) + + val ordered2 = result2.values.sortedBy { it.key }.map { it.toPair() } + assertEquals(listOf('b' to emptyList(), 'f' to listOf("flea", "fire"), 'z' to listOf("zero")), ordered2) + } + + inline fun > maxOfBy(a: T, b: T, keySelector: (T) -> K) = if (keySelector(a) >= keySelector(b)) a else b + + @Test fun reduce() { + val elements = listOf("foo", "bar", "flea", "zoo", "biscuit") + fun Char.isVowel() = this in "aeiou" + fun String.countVowels() = count(Char::isVowel) + val maxVowels = elements.groupingBy { it.first() }.reduce { k, a, b -> maxOfBy(a, b, String::countVowels) } + + assertEquals(mapOf('f' to "foo", 'b' to "biscuit", 'z' to "zoo"), maxVowels) + + val elements2 = listOf("bar", "z", "fork") + val concats = elements2.groupingBy { it.first() }.reduceTo(HashMap(maxVowels)) { k, acc, e -> acc + e } + + assertEquals(mapOf('f' to "foofork", 'b' to "biscuitbar", 'z' to "zooz"), concats) + } + + @Test fun countEach() { + val elements = listOf("foo", "bar", "flea", "zoo", "biscuit") + val counts = elements.groupingBy { it.first() }.eachCount() + + assertEquals(mapOf('f' to 2, 'b' to 2, 'z' to 1), counts) + + val elements2 = arrayOf("zebra", "baz", "cab") + val counts2 = elements2.groupingBy { it.last() }.eachCountTo(HashMap(counts)) + + assertEquals(mapOf('f' to 2, 'b' to 3, 'a' to 1, 'z' to 2), counts2) + } + + + @Test fun sumEach() { + val values = listOf("k" to 50, "b" to 20, "k" to 1000 ) + val summary = values.groupingBy { it.first }.eachSumOf { it.second } + + assertEquals(mapOf("k" to 1050, "b" to 20), summary) + + val values2 = listOf("key", "ball", "builder", "alpha") + val summary2 = values2.groupingBy { it.first().toString() }.eachSumOfTo(HashMap(summary)) { it.length } + + assertEquals(mapOf("k" to 1053, "b" to 31, "a" to 5), summary2) + } +} \ No newline at end of file