diff --git a/app/src/test/java/com/example/learningtest/collection/functional/FilteringTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/FilteringTest.kt new file mode 100644 index 0000000..f83b204 --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/FilteringTest.kt @@ -0,0 +1,149 @@ +package com.example.learningtest.collection.functional + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class FilteringTest : FreeSpec({ + "List Filter Api" - { + "filter - 조건을 만족하는 것만 남기기" { + val numbers: List = listOf(1, 2, 3, 4, 5, 6) + numbers.filter { it % 2 == 0 } shouldBe listOf(2, 4, 6) + } + + "filterNot - 조건을 만족하지 않는 요소만 남기기" { + val words: List = listOf("a", "be", "cat") + words.filterNot { it.length > 1 } shouldBe listOf("a") + } + + "filterIndexed - 인덱스와 함께 필터링" { + val words: List = listOf("zero", "one", "two", "three") + val result = words.filterIndexed { index, _ -> index % 2 == 0 } + result shouldBe listOf("zero", "two") + } + + "take - 앞에서부터 N개 가져오기" { + val numbers: List = listOf(0, 1, 2, 3, 4) + numbers.take(2) shouldBe listOf(0, 1) + } + + "drop - 앞에서부터 N 개 버리고 나머지 가져오기" { + val numbers: List = listOf(0, 1, 2, 3, 4) + numbers.drop(2) shouldBe listOf(2, 3, 4) + } + + "takeWhile - 앞에서부터 조건이 거짓이기 전까지 가져오기(거짓이되면 순회 중지)" { + val numbers: List = listOf(1, 2, 3, 0, 4) + numbers.takeWhile { number -> number > 0 } shouldBe listOf(1, 2, 3) + } + + "dropWhile - 앞에서부터 조건이 참이기 전까지 버리고 나머지 가져오기(참이 되면 순회 중지)" { + val numbers: List = listOf(1, 2, 3, 0, 4) + numbers.dropWhile { it > 0 } shouldBe listOf(0, 4) + } + + "filterTo - 필터링 결과를 기존 컬렉션에 추가" { + val target: MutableList = mutableListOf(0, 9) + val source: List = listOf(1, 2, 3, 4) + source.filterTo(target) { it > 2 } + + target shouldBe listOf(0, 9, 3, 4) + } + + "filterIndexedTo - 인덱스와 함께 필터링 결과를 기존 컬렉션에 추가" { + val target: MutableList = mutableListOf("hello", "world") + val source: List = listOf("a", "b", "c", "d") + source.filterIndexedTo(target) { index, _ -> index % 2 == 0 } + + target shouldBe listOf("hello", "world", "a", "c") + } + + "filterNotTo - 조건을 만족하지 않은 것만 필터링해서 기존 컬렉션에 추가" { + val target: MutableList = mutableListOf(3, 3) + val source: List = listOf(1, 2, 3, 4) + source.filterNotTo(target) { it % 2 == 0 } + + target shouldBe listOf(3, 3, 1, 3) + } + + "filterIsInstance - 타입 필터링" { + val list: List = listOf(1, "a", 2L, 'b', "c") + list.filterIsInstance() shouldBe listOf("a", "c") + } + + "filterIsInstanceTo - 타입 필터링 후 다른 컬렉션에 저장" { + val target: MutableList = mutableListOf("d") + val source: List = listOf("a", 1, 'b', 2L, "c") + + source.filterIsInstanceTo(target) shouldBe listOf("d", "a", "c") + } + + "distinct - 중복 제거" { + val list: List = listOf(1, 2, 3, 1, 2) + list.distinct() shouldBe listOf(1, 2, 3) + } + + "distinctBy - 조건에 따라 중복 제거 (대표 요소 하나만 남김)" { + val list: List = listOf("aaa", "bbb", "ccc", "aa", "bb", "cc", "a", "b", "c") + list.distinctBy { it.length } shouldBe listOf("aaa", "aa", "a") + } + + "partition - 조건에 따라 두 그룹으로 나누기" { + val list: List = listOf(1, 2, 3, 4, 5) + val (even: List, odd: List) = list.partition { it % 2 == 0 } + + even shouldBe listOf(2, 4) + odd shouldBe listOf(1, 3, 5) + } + } + + "Map Filter Api" - { + val map: Map = + mapOf( + "a" to 1, + "b" to 2, + "c" to 3, + "d" to 4, + ) + + "filter - (key, value) 쌍으로 필터링" { + // requires a pair of parentheses around the key and value in the lambda block. + val result: Map = + map.filter { (key, value) -> key in listOf("a", "c") && value % 2 == 1 } + + result shouldBe mapOf("a" to 1, "c" to 3) + } + + "filterKeys - key 기준 필터링" { + val result: Map = map.filterKeys { it > "b" } + result shouldBe mapOf("c" to 3, "d" to 4) + } + + "filterValues - value 기준 필터링" { + val result: Map = map.filterValues { it % 2 == 0 } + result shouldBe mapOf("b" to 2, "d" to 4) + } + + "filterNot - 조건을 만족하지 않는 (key, value) 필터링" { + val result: Map = map.filterNot { (_, value) -> value > 2 } + result shouldBe mapOf("a" to 1, "b" to 2) + } + + "filterTo - 조건을 만족하는 항목을 다른 MutableMap에 추가" { + val source: Map = mapOf("one" to 1, "two" to 2, "three" to 3) + val target: MutableMap = mutableMapOf() + + source.filterTo(target) { (_, v) -> v % 2 == 1 } + + target shouldBe mapOf("one" to 1, "three" to 3) + } + + "filterNotTo - 조건을 만족하지 않는 항목을 target에 추가" { + val source: Map = mapOf("a" to 1, "b" to 2, "c" to 3) + val result: MutableMap = mutableMapOf() + + source.filterNotTo(result) { (_, v) -> v > 1 } + + result shouldBe mapOf("a" to 1) + } + } +}) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt new file mode 100644 index 0000000..23d2524 --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt @@ -0,0 +1,200 @@ +package com.example.learningtest.collection.functional + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class FlatteningTest : FreeSpec({ + "List 의 flattening" - { + "flatten - 중첩된 리스트를 평탄화" { + val nested: List> = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + + nested.flatten() shouldBe listOf(1, 2, 3, 4) + } + + "flatten - 깊이 n 번의 중첩은 n 번의 flatten 으로 평탄화" { + val nested: List>> = + listOf( + listOf( + listOf(1, 2), + listOf(11, 22), + ), + listOf( + listOf(3, 4), + listOf(33, 44), + ), + ) + + val onceFlattened: List> = nested.flatten() + val twiceFlattened: List = onceFlattened.flatten() + + onceFlattened shouldBe + listOf( + listOf(1, 2), + listOf(11, 22), + listOf(3, 4), + listOf(33, 44), + ) + twiceFlattened shouldBe listOf(1, 2, 11, 22, 3, 4, 33, 44) + } + + "flatMap - 각 요소를 여러 개로 펼친 후 평탄화" { + val nested: List> = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + + val flattened: List = nested.flatMap { it } + flattened shouldBe listOf(1, 2, 3, 4) + } + + "flatMapIndexed - 인덱스와 함께 각 요소를 여러 개로 펼친 후 평탄화" { + val nested: List> = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + + val flattened: List = + nested.flatMapIndexed { index, list -> + list.map { value -> index * value } + } + + flattened shouldBe listOf(0, 0, 3, 4) + } + + "flatMapTo - 중첩된 리스트를 평탄화해서 기존 컬렉션에 추가" { + val source: List> = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + val destination: MutableList = mutableListOf(0) + source.flatMapTo(destination) { it } + + destination shouldBe listOf(0, 1, 2, 3, 4) + } + + "flatMapIndexed - 인덱스와 함께 각 요소를 여러 개로 펼친 후 평탄화해서 기존 컬렉션에 추가" { + val nested: List> = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + val destination: MutableList = mutableListOf(0, 1, 2) + nested.flatMapIndexedTo(destination) { index, list -> + list.map { value -> value * 2 } + } + + destination shouldBe listOf(0, 1, 2, 2, 4, 6, 8) + } + } + + "Map 의 flattening" - { + "flatMap - Map> 구조를 평탄화" { + val map: Map> = + mapOf( + "a" to listOf(1, 2), + "b" to listOf(3), + ) + + val result: List = + map.flatMap { (key, numbers) -> + numbers.map { number -> "$key:$number" } + } + + result shouldBe listOf("a:1", "a:2", "b:3") + } + + "flatMap - 사실 flatMap 의 람다 파라미터의 리턴 타입이 Iterable 타입이기만 하면 된다." { + val map: Map = + mapOf( + "a" to 1, + "b" to 2, + "c" to 3, + ) + + val result: List = + map.flatMap { (_, number) -> + List(number) { number } + } + + result shouldBe listOf(1, 2, 2, 3, 3, 3) + } + + "toList - Map 를 List> 로 변환" { + val map: Map> = + mapOf( + "a" to listOf(1, 2), + "b" to listOf(3), + ) + + val toList: List>> = map.toList() + + toList shouldBe + listOf>>( + "a" to listOf(1, 2), + "b" to listOf(3), + ) + } + + "flatMapValues 메서드는 따로 없어서 Map.values.flatten 사용" { + val map: Map> = + mapOf( + "a" to listOf(1, 2), + "b" to listOf(3), + ) + + val result: List = map.values.flatten() + result shouldBe listOf(1, 2, 3) + } + + "flatPairValues 메서드는 따로 없어서 아래처럼 재구성 필요" { + val map: Map> = + mapOf( + "a" to listOf(1, 2), + "b" to listOf(3), + ) + + val result1: List> = + map.flatMap { entry -> + entry.value.map { value -> entry.key to value } + } + + val result2: List> = + map.entries.flatMap { entry -> + entry.value.map { value -> entry.key to value } + } + + result1 shouldBe + listOf>( + "a" to 1, + "a" to 2, + "b" to 3, + ) + result1 shouldBe result2 + } + + "Map.toList(). 은 단순 List> 리스트로 변환" { + val map: Map = + mapOf( + "a" to "1", + "b" to "2", + "c" to "3", + ) + + val flatList: List> = map.toList() + + flatList shouldBe + listOf>( + "a" to "1", + "b" to "2", + "c" to "3", + ) + } + } +}) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt new file mode 100644 index 0000000..769de9d --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt @@ -0,0 +1,154 @@ +package com.example.learningtest.collection.functional + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class MappingTest : FreeSpec({ + "리스트 컬렉션의 mapping - 원소 변환" - { + "map - 각 원소를 변환" { + val numbers: List = listOf(1, 2, 3) + numbers.map { number -> number * 2 } shouldBe listOf(2, 4, 6) + } + + "mapIndexed - 인덱스를 함께 사용한 변환" { + val names: List = listOf("a", "b", "c") + names.mapIndexed { index, name -> "$index: $name" } shouldBe + listOf( + "0: a", + "1: b", + "2: c", + ) + } + + "mapNotNull - null 을 걸러내며 변환" { + val tags: List = listOf("1", "a", "2") + val numberTags: List = tags.mapNotNull { tag -> tag.toIntOrNull() } + + numberTags shouldBe listOf(1, 2) + } + + "mapIndexedNotNull - 인덱스를 함께 사용하며 null 을 걸러내며 변환" { + val tags: List = listOf("1", "a", "2", null) + val numberTags: List = + tags + .mapIndexedNotNull { index, tag -> + if (index == 0) { + null + } else { + tag?.toIntOrNull() + } + } + numberTags shouldBe listOf(2) + } + + "mapTo - 결과를 미리 만든 리스트에 추가" { + val source: List = listOf(1, 2, 3) + val destination: MutableList = mutableListOf(0, 0) + source.mapTo(destination) { it * it } + + destination shouldBe listOf(0, 0, 1, 4, 9) + } + + "mapIndexedTo -인덱스를 이용하고 결과를 미리 만든 리스트에 추가" { + val source: List = listOf(1, 2, 3) + val destination: MutableList> = mutableListOf() + source.mapIndexedTo(destination) { index, value -> + index to value + } + + destination shouldBe listOf(0 to 1, 1 to 2, 2 to 3) + } + + "mapNotNullTo - 결과를 미리 만든 리스트에 null 빼고 추가" { + val source: List = listOf(1, 2, null) + val destination: MutableList = mutableListOf(0, 0) + source.mapNotNullTo(destination) { it?.times(it) } + + destination shouldBe listOf(0, 0, 1, 4) + } + + "mapIndexedNotNullTo - 인덱스를 이용하고 null 을 제거하며 리스트에 추가 " { + val source: List = listOf("0", "a", "2", "b") + val result: MutableList> = mutableListOf() + + source.mapIndexedNotNullTo(result) { index, value -> + value.toIntOrNull()?.let { index to it } + } + + result shouldBe listOf(0 to 0, 2 to 2) + } + + "associate - List 를 Map 으로 변환 (Key, Value 모두 수동 지정)" { + val words: List = listOf("apple", "banana") + val wordsWithLength: Map = words.associate { word -> word to word.length } + wordsWithLength shouldBe mapOf("apple" to 5, "banana" to 6) + } + + "associateWith - value 만 지정하고 key 는 원본 사용" { + val fruits: List = listOf("apple", "banana") + val fruitsWithLength: Map = fruits.associateWith { it.length } + + fruitsWithLength shouldBe mapOf("apple" to 5, "banana" to 6) + } + + "associateBy - Key 만 지정하고 Value 는 원본 사용" { + data class User(val id: Int, val name: String) + + val users: List = listOf(User(1, "jimmy"), User(2, "tim")) + val usersByCode: Map = + users.associateBy { user -> + user.id * 32 + } + + usersByCode shouldBe + mapOf( + 32 to User(1, "jimmy"), + 64 to User(2, "tim"), + ) + } + + "associateTo - key 만 지정하고 value 는 원본 유지하며 기존 Map에 추가" { + val fruits: List = listOf("apple", "banana") + val fruitsWithLength: MutableMap = mutableMapOf("lemon" to 5) + + fruits.associateTo(fruitsWithLength) { fruit -> + fruit to fruit.length + } + + fruitsWithLength shouldBe mapOf("lemon" to 5, "apple" to 5, "banana" to 6) + } + + "associateWithTo - value 만 지정하고 key 는 원본 유지하며 기본 map 에 추가" { + val fruits: List = listOf("apple", "banana") + val fruitsWithLength: MutableMap = mutableMapOf("lemon" to 5) + + fruits.associateWithTo(fruitsWithLength) { fruit -> + fruit.length + } + + fruitsWithLength shouldBe + mapOf( + "lemon" to 5, + "apple" to 5, + "banana" to 6, + ) + } + + "associateByTo - key만 지정하고 value 는 원본 유지하며 기존 map 에 추가" { + data class User(val id: Int, val name: String) + + val users: List = listOf(User(1, "A"), User(2, "B")) + val codeWithUsers: MutableMap = mutableMapOf(10 to User(3, "ABC")) + + users.associateByTo(codeWithUsers) { user -> + user.id + 32 + } + codeWithUsers shouldBe + mapOf( + 10 to User(3, "ABC"), + 33 to User(1, "A"), + 34 to User(2, "B"), + ) + } + } +})