From 649115395723cad5c3f36166591eafdc2096c7b8 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Tue, 15 Apr 2025 15:55:01 +0900 Subject: [PATCH 01/32] Add ListFunctionalApi with basic examples of Kotlin collection functions --- .../functional/ListFunctionalApiTest.kt | 78 +++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/ListFunctionalApiTest.kt diff --git a/app/src/test/java/com/example/learningtest/collection/functional/ListFunctionalApiTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/ListFunctionalApiTest.kt new file mode 100644 index 0000000..b59fbe9 --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/ListFunctionalApiTest.kt @@ -0,0 +1,78 @@ +package com.example.learningtest.collection.functional + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class ListFunctionalApiTest : FreeSpec({ + "map - 원소 변환" - { + val names = listOf("a", "b", "c") + val users = names.map { "$it!" } + + users shouldBe listOf("a!", "b!", "c!") + } + + "filter - 조건에 맞는 원소만 남기기" - { + val numbers = listOf(1, 2, 3, 4, 5) + val evens = numbers.filter { it % 2 == 0 } + + evens shouldBe listOf(2, 4) + } + + "find == firstOrNull - 조건을 만족하는 첫 요소 찾기 " - { + val names = listOf("kim", "lee", "park") + val found = names.find { it.startsWith("p") } + val firstOrNull = names.firstOrNull { it.startsWith("p") } + + found shouldBe "park" + firstOrNull shouldBe "park" + } + + "any, all, none - 조건 검증" - { + val numbers = listOf(1, 2, 3, 4) + + numbers.any { it > 3 } shouldBe true + numbers.all { it < 10 } shouldBe true + numbers.none { it < 0 } shouldBe true + } + + "flatMap - 리스트 안의 리스트 펼치기" - { + val nested = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + + val flattened = nested.flatMap { it } + flattened shouldBe listOf(1, 2, 3, 4) + } + + "groupBy - 조건에 따라 그룹화" - { + val words = listOf("apple", "banana", "avocado", "blueberry") + val grouped = words.groupBy { it.first() } + + grouped['a'] shouldBe listOf("apple", "avocado") + grouped['b'] shouldBe listOf("banana", "blueberry") + } + + "fold - 누적 계산(초기값 있음)" - { + val numbers = listOf(1, 2, 3) + val sum = numbers.fold(10) { acc, value -> acc + value } + + sum shouldBe 16 + } + + "reduce - 누적 계산(초기값 없음)" - { + val numbers = listOf(1, 2, 3) + val product = numbers.reduce { acc, value -> acc * value } + + product shouldBe 6 + } + + "zip - 두 리스트를 하나로 결합" - { + val names = listOf("철수", "영희") + val ages = listOf(20, 21) + + val result = names.zip(ages) { name, age -> "$name ($age)" } + result shouldBe listOf("철수 (20)", "영희 (21)") + } +}) \ No newline at end of file From e082580342c10c57722d89c97902d0cceb6e8eb2 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Tue, 15 Apr 2025 16:45:05 +0900 Subject: [PATCH 02/32] test(MappingTest): mapping api for Kotlin List map, flatMap, associate methods --- .../collection/functional/MappingTest.kt | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt 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..8bbc1d9 --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt @@ -0,0 +1,79 @@ +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 = listOf(1, 2, 3) + numbers.map { number -> number * 2 } shouldBe listOf(2, 4, 6) + } + + "mapIndexed - 인덱스를 함께 사용한 변환" { + val names = listOf("a", "b", "c") + names.mapIndexed { index, name -> "$index: $name" } shouldBe listOf( + "0: a", + "1: b", + "2: c" + ) + } + + "mapNotNull - null 을 걸러내며 변환" { + val tags = listOf("1", "a", "2") + val numberTags = tags.mapNotNull { tag -> tag.toIntOrNull() } + + numberTags shouldBe listOf(1, 2) + } + + "mapIndexedNotNull - 인덱스를 함께 사용하며 null 을 걸러내며 변환" { + val tags = listOf("1", "a", "2", null) + val numberTags = tags + .mapIndexedNotNull { index, tag -> + if (index == 0) return@mapIndexedNotNull null + + tag?.toIntOrNull() + } + numberTags shouldBe listOf(2) + } + + "flatMap - 각 요소를 여러 개로 펼친 후 평탄화" { + val nested = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + + val flattened = nested.flatMap { it } + flattened shouldBe listOf(1, 2, 3, 4) + } + + "associate - List 를 Map 으로 변환 (Key, Value 모두 수동 지정)" { + val words = listOf("apple", "banana") + val wordsWithLength = words.associate { word -> word to word.length } + wordsWithLength shouldBe mapOf("apple" to 5, "banana" to 6) + } + + "associateBy - Key 만 지정하고 Value 는 원본 사용" { + data class User(val id: Int, val name: String) + + val users = listOf(User(1, "jimmy"), User(2, "tim")) + val usersByCode = users.associateBy { user -> + user.id * 32 + } + + usersByCode shouldBe mapOf( + 32 to User(1, "jimmy"), + 64 to User(2, "tim"), + ) + } + + "associateWith - value 만 지정하고 key 는 원본 사용" { + val fruits = listOf("apple", "banana") + val fruitsWithLength = fruits.associateWith { it.length } + + fruitsWithLength shouldBe mapOf("apple" to 5, "banana" to 6) + } + + } +}) \ No newline at end of file From 5bdbdc0feb28b33eb4535e2063b90ce7e870bf29 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Tue, 15 Apr 2025 16:58:14 +0900 Subject: [PATCH 03/32] test(MappingTest): add mapTo type methods --- .../collection/functional/MappingTest.kt | 85 ++++++++++++++++++- 1 file changed, 83 insertions(+), 2 deletions(-) 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 index 8bbc1d9..3ab48ad 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt @@ -4,7 +4,7 @@ import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe class MappingTest : FreeSpec({ - "mapping - 원소 변환" - { + "리스트 컬렉션의 mapping - 원소 변환" - { "map - 각 원소를 변환" { val numbers = listOf(1, 2, 3) numbers.map { number -> number * 2 } shouldBe listOf(2, 4, 6) @@ -37,6 +37,44 @@ class MappingTest : FreeSpec({ numberTags shouldBe listOf(2) } + "mapTo - 결과를 미리 만든 리스트에 추가" { + val source = listOf(1, 2, 3) + val destination = mutableListOf(0, 0) + source.mapTo(destination) { it * it } + + destination shouldBe listOf(0, 0, 1, 4, 9) + } + + "mapIndexedTo -인덱스를 이용하고 결과를 미리 만든 리스트에 추가" { + val source = listOf(1, 2, 3) + val destination = 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 = listOf(1, 2, null) + val destination = mutableListOf(0, 0) + source.mapNotNullTo(destination) { it?.times(it) } + + destination shouldBe listOf(0, 0, 1, 4) + } + + "mapIndexedNotNullTo - 인덱스를 이용하고 null 을 제거하며 리스트에 추가 " { + val source = listOf("0", "a", "2", "b") + val result = mutableListOf>() + + source.mapIndexedNotNullTo(result) { index, value -> + value.toIntOrNull()?.let { index to it } + } + + result shouldBe listOf(0 to 0, 2 to 2) + } + + "flatMap - 각 요소를 여러 개로 펼친 후 평탄화" { val nested = listOf( @@ -76,4 +114,47 @@ class MappingTest : FreeSpec({ } } -}) \ No newline at end of file +}) + +/* +"mapIndexedNotNullTo - 인덱스를 이용하고 null 제거하며 리스트에 추가" { + val source = listOf("0", "a", "2", "b") + val result = mutableListOf>() + + source.mapIndexedNotNullTo(result) { index, value -> + value.toIntOrNull()?.let { index to it } + } + + result shouldBe listOf(0 to 0, 2 to 2) + } + + "associateTo - key, value 모두 지정하고 기존 Map에 추가" { + val source = listOf("apple", "banana") + val result = mutableMapOf() + + source.associateTo(result) { it to it.length } + + result shouldBe mapOf("apple" to 5, "banana" to 6) + } + + "associateByTo - key만 지정하고 value는 원본 유지하며 기존 Map에 추가" { + data class User(val id: Int, val name: String) + val users = listOf(User(1, "A"), User(2, "B")) + val result = mutableMapOf() + + users.associateByTo(result) { it.id } + + result[1]?.name shouldBe "A" + } + + "associateWithTo - key는 원본 그대로, value는 지정한 값으로 기존 Map에 추가" { + val keys = listOf("apple", "banana") + val result = mutableMapOf() + + keys.associateWithTo(result) { it.length } + + result shouldBe mapOf("apple" to 5, "banana" to 6) + } + + +* */ \ No newline at end of file From 53aba00f16e806402556ad03e5c263e64343582a Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Tue, 15 Apr 2025 17:12:15 +0900 Subject: [PATCH 04/32] test(MappingTest): add flatMapTo type methods --- .../collection/functional/MappingTest.kt | 92 +++++++++++++++---- 1 file changed, 73 insertions(+), 19 deletions(-) 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 index 3ab48ad..4556337 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt @@ -12,11 +12,12 @@ class MappingTest : FreeSpec({ "mapIndexed - 인덱스를 함께 사용한 변환" { val names = listOf("a", "b", "c") - names.mapIndexed { index, name -> "$index: $name" } shouldBe listOf( - "0: a", - "1: b", - "2: c" - ) + names.mapIndexed { index, name -> "$index: $name" } shouldBe + listOf( + "0: a", + "1: b", + "2: c", + ) } "mapNotNull - null 을 걸러내며 변환" { @@ -28,12 +29,13 @@ class MappingTest : FreeSpec({ "mapIndexedNotNull - 인덱스를 함께 사용하며 null 을 걸러내며 변환" { val tags = listOf("1", "a", "2", null) - val numberTags = tags - .mapIndexedNotNull { index, tag -> - if (index == 0) return@mapIndexedNotNull null + val numberTags = + tags + .mapIndexedNotNull { index, tag -> + if (index == 0) return@mapIndexedNotNull null - tag?.toIntOrNull() - } + tag?.toIntOrNull() + } numberTags shouldBe listOf(2) } @@ -74,6 +76,15 @@ class MappingTest : FreeSpec({ result shouldBe listOf(0 to 0, 2 to 2) } + "flatten - " { + val nested = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + + nested.flatten() shouldBe listOf(1, 2, 3, 4) + } "flatMap - 각 요소를 여러 개로 펼친 후 평탄화" { val nested = @@ -86,6 +97,48 @@ class MappingTest : FreeSpec({ flattened shouldBe listOf(1, 2, 3, 4) } + "flatMapIndexed - " { + val nested = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + + val flattened = + nested.flatMapIndexed { index, list -> + println("index: $index , list: $list") + list.map { value -> index * value } + } + + flattened shouldBe listOf(0, 0, 3, 4) + } + + "flatMapTo" { + val source = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + val destination = mutableListOf(0) + source.flatMapTo(destination) { it } + + destination shouldBe listOf(0, 1, 2, 3, 4) + } + + "flatMapIndexed - " { + val nested = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + val destination = mutableListOf(0, 1, 2) + nested.flatMapIndexedTo(destination) { index, list -> + list.map { value -> value * 2 } + } + + destination shouldBe listOf(0, 1, 2, 2, 4, 6, 8) + } + "associate - List 를 Map 으로 변환 (Key, Value 모두 수동 지정)" { val words = listOf("apple", "banana") val wordsWithLength = words.associate { word -> word to word.length } @@ -96,14 +149,16 @@ class MappingTest : FreeSpec({ data class User(val id: Int, val name: String) val users = listOf(User(1, "jimmy"), User(2, "tim")) - val usersByCode = users.associateBy { user -> - user.id * 32 - } + val usersByCode = + users.associateBy { user -> + user.id * 32 + } - usersByCode shouldBe mapOf( - 32 to User(1, "jimmy"), - 64 to User(2, "tim"), - ) + usersByCode shouldBe + mapOf( + 32 to User(1, "jimmy"), + 64 to User(2, "tim"), + ) } "associateWith - value 만 지정하고 key 는 원본 사용" { @@ -112,7 +167,6 @@ class MappingTest : FreeSpec({ fruitsWithLength shouldBe mapOf("apple" to 5, "banana" to 6) } - } }) @@ -157,4 +211,4 @@ class MappingTest : FreeSpec({ } -* */ \ No newline at end of file +* */ From 2e866ebbfbecf945883455c7272aa4cb07b3e3d1 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Tue, 15 Apr 2025 17:30:35 +0900 Subject: [PATCH 05/32] test(MappingTest): add associateTo type methods associateTo, associateWithTo, associateByTo --- .../collection/functional/MappingTest.kt | 82 ++++++++++--------- 1 file changed, 42 insertions(+), 40 deletions(-) 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 index 4556337..a53ccc8 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt @@ -76,6 +76,7 @@ class MappingTest : FreeSpec({ result shouldBe listOf(0 to 0, 2 to 2) } + // TODO() flattening test 로 이동시키자. "flatten - " { val nested = listOf( @@ -145,6 +146,13 @@ class MappingTest : FreeSpec({ wordsWithLength shouldBe mapOf("apple" to 5, "banana" to 6) } + "associateWith - value 만 지정하고 key 는 원본 사용" { + val fruits = listOf("apple", "banana") + val fruitsWithLength = 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) @@ -161,54 +169,48 @@ class MappingTest : FreeSpec({ ) } - "associateWith - value 만 지정하고 key 는 원본 사용" { + "associateTo - key 만 지정하고 value 는 원본 유지하며 기존 Map에 추가" { val fruits = listOf("apple", "banana") - val fruitsWithLength = fruits.associateWith { it.length } + val fruitsWithLength = mutableMapOf("lemon" to 5) - fruitsWithLength shouldBe mapOf("apple" to 5, "banana" to 6) - } - } -}) - -/* -"mapIndexedNotNullTo - 인덱스를 이용하고 null 제거하며 리스트에 추가" { - val source = listOf("0", "a", "2", "b") - val result = mutableListOf>() + fruits.associateTo(fruitsWithLength) { fruit -> + fruit to fruit.length + } - source.mapIndexedNotNullTo(result) { index, value -> - value.toIntOrNull()?.let { index to it } + fruitsWithLength shouldBe mapOf("lemon" to 5, "apple" to 5, "banana" to 6) } - result shouldBe listOf(0 to 0, 2 to 2) - } - - "associateTo - key, value 모두 지정하고 기존 Map에 추가" { - val source = listOf("apple", "banana") - val result = mutableMapOf() - - source.associateTo(result) { it to it.length } - - result shouldBe mapOf("apple" to 5, "banana" to 6) - } - - "associateByTo - key만 지정하고 value는 원본 유지하며 기존 Map에 추가" { - data class User(val id: Int, val name: String) - val users = listOf(User(1, "A"), User(2, "B")) - val result = mutableMapOf() + "associateWithTo - value 만 지정하고 key 는 원본 유지하며 기본 map 에 추가" { + val fruits = listOf("apple", "banana") + val fruitsWithLength = mutableMapOf("lemon" to 5) - users.associateByTo(result) { it.id } + fruits.associateWithTo(fruitsWithLength) { fruit -> + fruit.length + } - result[1]?.name shouldBe "A" - } + fruitsWithLength shouldBe + mapOf( + "lemon" to 5, + "apple" to 5, + "banana" to 6, + ) + } - "associateWithTo - key는 원본 그대로, value는 지정한 값으로 기존 Map에 추가" { - val keys = listOf("apple", "banana") - val result = mutableMapOf() + "associateByTo - key만 지정하고 value 는 원본 유지하며 기존 map 에 추가" { + data class User(val id: Int, val name: String) - keys.associateWithTo(result) { it.length } + val users = listOf(User(1, "A"), User(2, "B")) + val codeWithUsers = mutableMapOf(10 to User(3, "ABC")) - result shouldBe mapOf("apple" to 5, "banana" to 6) + 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"), + ) + } } - - -* */ +}) From b5ba82cb5176c8d81c14b952daebb2a3ec72a7fc Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Tue, 15 Apr 2025 17:33:34 +0900 Subject: [PATCH 06/32] test(FlatteningTest): add flattening type methods test remove flattening test in MappingTest.kt --- .../collection/functional/FlatteningTest.kt | 71 +++++++++++++++++++ .../collection/functional/MappingTest.kt | 64 ----------------- 2 files changed, 71 insertions(+), 64 deletions(-) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt 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..5754858 --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt @@ -0,0 +1,71 @@ +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 = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + + nested.flatten() shouldBe listOf(1, 2, 3, 4) + } + + "flatMap - 각 요소를 여러 개로 펼친 후 평탄화" { + val nested = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + + val flattened = nested.flatMap { it } + flattened shouldBe listOf(1, 2, 3, 4) + } + + "flatMapIndexed - " { + val nested = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + + val flattened = + nested.flatMapIndexed { index, list -> + println("index: $index , list: $list") + list.map { value -> index * value } + } + + flattened shouldBe listOf(0, 0, 3, 4) + } + + "flatMapTo" { + val source = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + val destination = mutableListOf(0) + source.flatMapTo(destination) { it } + + destination shouldBe listOf(0, 1, 2, 3, 4) + } + + "flatMapIndexed - " { + val nested = + listOf( + listOf(1, 2), + listOf(3, 4), + ) + val destination = mutableListOf(0, 1, 2) + nested.flatMapIndexedTo(destination) { index, list -> + list.map { value -> value * 2 } + } + + destination shouldBe listOf(0, 1, 2, 2, 4, 6, 8) + } + } +}) 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 index a53ccc8..303e76f 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt @@ -76,70 +76,6 @@ class MappingTest : FreeSpec({ result shouldBe listOf(0 to 0, 2 to 2) } - // TODO() flattening test 로 이동시키자. - "flatten - " { - val nested = - listOf( - listOf(1, 2), - listOf(3, 4), - ) - - nested.flatten() shouldBe listOf(1, 2, 3, 4) - } - - "flatMap - 각 요소를 여러 개로 펼친 후 평탄화" { - val nested = - listOf( - listOf(1, 2), - listOf(3, 4), - ) - - val flattened = nested.flatMap { it } - flattened shouldBe listOf(1, 2, 3, 4) - } - - "flatMapIndexed - " { - val nested = - listOf( - listOf(1, 2), - listOf(3, 4), - ) - - val flattened = - nested.flatMapIndexed { index, list -> - println("index: $index , list: $list") - list.map { value -> index * value } - } - - flattened shouldBe listOf(0, 0, 3, 4) - } - - "flatMapTo" { - val source = - listOf( - listOf(1, 2), - listOf(3, 4), - ) - val destination = mutableListOf(0) - source.flatMapTo(destination) { it } - - destination shouldBe listOf(0, 1, 2, 3, 4) - } - - "flatMapIndexed - " { - val nested = - listOf( - listOf(1, 2), - listOf(3, 4), - ) - val destination = mutableListOf(0, 1, 2) - nested.flatMapIndexedTo(destination) { index, list -> - list.map { value -> value * 2 } - } - - destination shouldBe listOf(0, 1, 2, 2, 4, 6, 8) - } - "associate - List 를 Map 으로 변환 (Key, Value 모두 수동 지정)" { val words = listOf("apple", "banana") val wordsWithLength = words.associate { word -> word to word.length } From 006edd0c98b3493934c0b535a7c9a8a2e1b1bb72 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Thu, 17 Apr 2025 20:56:06 +0900 Subject: [PATCH 07/32] test(FilteringTest): add filtering and collection manipulation methods test --- .../collection/functional/FilteringTest.kt | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/FilteringTest.kt 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..a7cb444 --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/FilteringTest.kt @@ -0,0 +1,98 @@ +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 = listOf(1, 2, 3, 4, 5, 6) + numbers.filter { it % 2 == 0 } shouldBe listOf(2, 4, 6) + } + + "filterNot - 조건을 만족하지 않는 요소만 남기기" { + val words = listOf("a", "be", "cat") + words.filterNot { it.length > 1 } shouldBe listOf("a") + } + + "filterIndexed - 인덱스와 함께 필터링" { + val words = listOf("zero", "one", "two", "three") + val result = words.filterIndexed { index, _ -> index % 2 == 0 } + result shouldBe listOf("zero", "two") + } + + "take - 앞에서부터 N개 가져오기" { + val numbers = listOf(0, 1, 2, 3, 4) + numbers.take(2) shouldBe listOf(0, 1) + } + + "drop - 앞에서부터 N 개 버리고 나머지 가져오기" { + val numbers = listOf(0, 1, 2, 3, 4) + numbers.drop(2) shouldBe listOf(2, 3, 4) + } + + "takeWhile - 앞에서부터 조건이 거짓이기 전까지 가져오기(거짓이되면 순회 중지)" { + val numbers = listOf(1, 2, 3, 0, 4) + numbers.takeWhile { number -> number > 0 } shouldBe listOf(1, 2, 3) + } + + "dropWhile - 앞에서부터 조건이 참이기 전까지 버리고 나머지 가져오기(참이 되면 순회 중지)" { + val numbers = listOf(1, 2, 3, 0, 4) + numbers.dropWhile { it > 0 } shouldBe listOf(0, 4) + } + + "filterTo - 필터링 결과를 기존 컬렉션에 추가" { + val target = mutableListOf(0, 9) + val source = listOf(1, 2, 3, 4) + source.filterTo(target) { it > 2 } + + target shouldBe listOf(0, 9, 3, 4) + } + + "filterIndexedTo - 인덱스와 함께 필터링 결과를 기존 컬렉션에 추가" { + val target = mutableListOf("hello", "world") + val source = listOf("a", "b", "c", "d") + source.filterIndexedTo(target) { index, _ -> index % 2 == 0 } + + target shouldBe listOf("hello", "world", "a", "c") + } + + "filterNotTo - 조건을 만족하지 않은 것만 필터링해서 기존 컬렉션에 추가" { + val target = mutableListOf(3, 3) + val source = 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 = mutableListOf("d") + val source = listOf("a", 1, 'b', 2L, "c") + + source.filterIsInstanceTo(target) shouldBe listOf("d", "a", "c") + } + + "distinct - 중복 제거" { + val list = listOf(1, 2, 3, 1, 2) + list.distinct() shouldBe listOf(1, 2, 3) + } + + "distinctBy - 조건에 따라 중복 제거 (대표 요소 하나만 남김)" { + val list = listOf("aaa", "bbb", "ccc", "aa", "bb", "cc", "a", "b", "c") + list.distinctBy { it.length } shouldBe listOf("aaa", "aa", "a") + } + + "partition - 조건에 따라 두 그룹으로 나누기" { + val list = listOf(1, 2, 3, 4, 5) + val (even, odd) = list.partition { it % 2 == 0 } + + even shouldBe listOf(2, 4) + odd shouldBe listOf(1, 3, 5) + } + } +}) From 7b8589b8008e468e42cbb462095fae0e29dd36ab Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Thu, 17 Apr 2025 21:12:02 +0900 Subject: [PATCH 08/32] test(FilteringTest): add tests for Map type's filtering methods --- .../collection/functional/FilteringTest.kt | 51 +++++++++++++++++++ 1 file changed, 51 insertions(+) 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 index a7cb444..7323dd9 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/FilteringTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/FilteringTest.kt @@ -95,4 +95,55 @@ class FilteringTest : FreeSpec({ odd shouldBe listOf(1, 3, 5) } } + + "Map Filter Api" - { + val 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.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.filterKeys { it > "b" } + result shouldBe mapOf("c" to 3, "d" to 4) + } + + "filterValues - value 기준 필터링" { + val result = map.filterValues { it % 2 == 0 } + result shouldBe mapOf("b" to 2, "d" to 4) + } + + "filterNot - 조건을 만족하지 않는 (key, value) 필터링" { + val result = map.filterNot { (_, value) -> value > 2 } + result shouldBe mapOf("a" to 1, "b" to 2) + } + + "filterTo - 조건을 만족하는 항목을 다른 MutableMap에 추가" { + val source = mapOf("one" to 1, "two" to 2, "three" to 3) + val target = mutableMapOf() + + source.filterTo(target) { (_, v) -> v % 2 == 1 } + + target shouldBe mapOf("one" to 1, "three" to 3) + } + + "filterNotTo - 조건을 만족하지 않는 항목을 target에 추가" { + val source = mapOf("a" to 1, "b" to 2, "c" to 3) + val result = mutableMapOf() + + source.filterNotTo(result) { (_, v) -> v > 1 } + + result shouldBe mapOf("a" to 1) + } + } }) From b0ecd5ce9213bcee1544dd85fc6fd414135a3c57 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Thu, 17 Apr 2025 21:14:53 +0900 Subject: [PATCH 09/32] test(FlatteningTest): update test descriptions for clarity on flattening methods --- .../learningtest/collection/functional/FlatteningTest.kt | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) 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 index 5754858..aa2adba 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt @@ -5,7 +5,7 @@ import io.kotest.matchers.shouldBe class FlatteningTest : FreeSpec({ "List 의 flattening" - { - "flatten - " { + "flatten - 중첩된 리스트를 평탄화" { val nested = listOf( listOf(1, 2), @@ -26,7 +26,7 @@ class FlatteningTest : FreeSpec({ flattened shouldBe listOf(1, 2, 3, 4) } - "flatMapIndexed - " { + "flatMapIndexed - 인덱스와 함께 각 요소를 여러 개로 펼친 후 평탄화" { val nested = listOf( listOf(1, 2), @@ -42,7 +42,7 @@ class FlatteningTest : FreeSpec({ flattened shouldBe listOf(0, 0, 3, 4) } - "flatMapTo" { + "flatMapTo - 중첩된 리스트를 평탄화해서 기존 컬렉션에 추가" { val source = listOf( listOf(1, 2), @@ -54,7 +54,7 @@ class FlatteningTest : FreeSpec({ destination shouldBe listOf(0, 1, 2, 3, 4) } - "flatMapIndexed - " { + "flatMapIndexed - 인덱스와 함께 각 요소를 여러 개로 펼친 후 평탄화해서 기존 컬렉션에 추가" { val nested = listOf( listOf(1, 2), From c7da45bbc15d4f76d4bd6cbf88f830d92bb0d9fd Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Thu, 17 Apr 2025 22:37:55 +0900 Subject: [PATCH 10/32] test(FlatteningTest): add comprehensive tests for flattening methods in List and Map structures --- .../collection/functional/FlatteningTest.kt | 125 +++++++++++++++++- 1 file changed, 124 insertions(+), 1 deletion(-) 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 index aa2adba..3a08e3f 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt @@ -2,6 +2,7 @@ package com.example.learningtest.collection.functional import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe +import kotlin.collections.flatten class FlatteningTest : FreeSpec({ "List 의 flattening" - { @@ -15,6 +16,32 @@ class FlatteningTest : FreeSpec({ 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 = listOf( @@ -35,7 +62,6 @@ class FlatteningTest : FreeSpec({ val flattened = nested.flatMapIndexed { index, list -> - println("index: $index , list: $list") list.map { value -> index * value } } @@ -68,4 +94,101 @@ class FlatteningTest : FreeSpec({ destination shouldBe listOf(0, 1, 2, 2, 4, 6, 8) } } + + "Map 의 flattening" - { + "flatMap - Map> 구조를 평탄화" { + val 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 = + 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 = + mapOf>( + "a" to listOf(1, 2), + "b" to listOf(3), + ) + + map.toList() shouldBe + listOf>>( + "a" to listOf(1, 2), + "b" to listOf(3), + ) + } + + "flatMapValues 메서드는 따로 없어서 Map.values.flatten 사용" { + val 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 = + 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().flatten 은 단순 Key-Value Pair 리스트로 변환" { + val map = mapOf() + + val flatList: List> = map.toList() + + val flatMap = + map.flatMap { entry -> + entry.value.map { value -> entry.key to value } + } + + flatList shouldBe flatMap + } + } }) From 5f064386ec91b4d38b99c75414bead238aa21900 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Thu, 17 Apr 2025 22:46:06 +0900 Subject: [PATCH 11/32] test(FilteringTest, FlatteningTest, MappingTest): update variable declarations for type clarity --- .../collection/functional/FilteringTest.kt | 58 +++++++++---------- .../collection/functional/FlatteningTest.kt | 46 ++++++++------- .../collection/functional/MappingTest.kt | 52 ++++++++--------- 3 files changed, 79 insertions(+), 77 deletions(-) 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 index 7323dd9..f83b204 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/FilteringTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/FilteringTest.kt @@ -6,60 +6,60 @@ import io.kotest.matchers.shouldBe class FilteringTest : FreeSpec({ "List Filter Api" - { "filter - 조건을 만족하는 것만 남기기" { - val numbers = listOf(1, 2, 3, 4, 5, 6) + val numbers: List = listOf(1, 2, 3, 4, 5, 6) numbers.filter { it % 2 == 0 } shouldBe listOf(2, 4, 6) } "filterNot - 조건을 만족하지 않는 요소만 남기기" { - val words = listOf("a", "be", "cat") + val words: List = listOf("a", "be", "cat") words.filterNot { it.length > 1 } shouldBe listOf("a") } "filterIndexed - 인덱스와 함께 필터링" { - val words = listOf("zero", "one", "two", "three") + 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 = listOf(0, 1, 2, 3, 4) + val numbers: List = listOf(0, 1, 2, 3, 4) numbers.take(2) shouldBe listOf(0, 1) } "drop - 앞에서부터 N 개 버리고 나머지 가져오기" { - val numbers = listOf(0, 1, 2, 3, 4) + val numbers: List = listOf(0, 1, 2, 3, 4) numbers.drop(2) shouldBe listOf(2, 3, 4) } "takeWhile - 앞에서부터 조건이 거짓이기 전까지 가져오기(거짓이되면 순회 중지)" { - val numbers = listOf(1, 2, 3, 0, 4) + val numbers: List = listOf(1, 2, 3, 0, 4) numbers.takeWhile { number -> number > 0 } shouldBe listOf(1, 2, 3) } "dropWhile - 앞에서부터 조건이 참이기 전까지 버리고 나머지 가져오기(참이 되면 순회 중지)" { - val numbers = listOf(1, 2, 3, 0, 4) + val numbers: List = listOf(1, 2, 3, 0, 4) numbers.dropWhile { it > 0 } shouldBe listOf(0, 4) } "filterTo - 필터링 결과를 기존 컬렉션에 추가" { - val target = mutableListOf(0, 9) - val source = listOf(1, 2, 3, 4) + 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 = mutableListOf("hello", "world") - val source = listOf("a", "b", "c", "d") + 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 = mutableListOf(3, 3) - val source = listOf(1, 2, 3, 4) + 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) @@ -71,25 +71,25 @@ class FilteringTest : FreeSpec({ } "filterIsInstanceTo - 타입 필터링 후 다른 컬렉션에 저장" { - val target = mutableListOf("d") - val source = listOf("a", 1, 'b', 2L, "c") + 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 = listOf(1, 2, 3, 1, 2) + val list: List = listOf(1, 2, 3, 1, 2) list.distinct() shouldBe listOf(1, 2, 3) } "distinctBy - 조건에 따라 중복 제거 (대표 요소 하나만 남김)" { - val list = listOf("aaa", "bbb", "ccc", "aa", "bb", "cc", "a", "b", "c") + 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 = listOf(1, 2, 3, 4, 5) - val (even, odd) = list.partition { it % 2 == 0 } + 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) @@ -97,8 +97,8 @@ class FilteringTest : FreeSpec({ } "Map Filter Api" - { - val map = - mapOf( + val map: Map = + mapOf( "a" to 1, "b" to 2, "c" to 3, @@ -107,30 +107,30 @@ class FilteringTest : FreeSpec({ "filter - (key, value) 쌍으로 필터링" { // requires a pair of parentheses around the key and value in the lambda block. - val result = + 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.filterKeys { it > "b" } + val result: Map = map.filterKeys { it > "b" } result shouldBe mapOf("c" to 3, "d" to 4) } "filterValues - value 기준 필터링" { - val result = map.filterValues { it % 2 == 0 } + val result: Map = map.filterValues { it % 2 == 0 } result shouldBe mapOf("b" to 2, "d" to 4) } "filterNot - 조건을 만족하지 않는 (key, value) 필터링" { - val result = map.filterNot { (_, value) -> value > 2 } + val result: Map = map.filterNot { (_, value) -> value > 2 } result shouldBe mapOf("a" to 1, "b" to 2) } "filterTo - 조건을 만족하는 항목을 다른 MutableMap에 추가" { - val source = mapOf("one" to 1, "two" to 2, "three" to 3) - val target = mutableMapOf() + val source: Map = mapOf("one" to 1, "two" to 2, "three" to 3) + val target: MutableMap = mutableMapOf() source.filterTo(target) { (_, v) -> v % 2 == 1 } @@ -138,8 +138,8 @@ class FilteringTest : FreeSpec({ } "filterNotTo - 조건을 만족하지 않는 항목을 target에 추가" { - val source = mapOf("a" to 1, "b" to 2, "c" to 3) - val result = mutableMapOf() + val source: Map = mapOf("a" to 1, "b" to 2, "c" to 3) + val result: MutableMap = mutableMapOf() source.filterNotTo(result) { (_, v) -> v > 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 index 3a08e3f..8e22c95 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt @@ -7,7 +7,7 @@ import kotlin.collections.flatten class FlatteningTest : FreeSpec({ "List 의 flattening" - { "flatten - 중첩된 리스트를 평탄화" { - val nested = + val nested: List> = listOf( listOf(1, 2), listOf(3, 4), @@ -43,24 +43,24 @@ class FlatteningTest : FreeSpec({ } "flatMap - 각 요소를 여러 개로 펼친 후 평탄화" { - val nested = + val nested: List> = listOf( listOf(1, 2), listOf(3, 4), ) - val flattened = nested.flatMap { it } + val flattened: List = nested.flatMap { it } flattened shouldBe listOf(1, 2, 3, 4) } "flatMapIndexed - 인덱스와 함께 각 요소를 여러 개로 펼친 후 평탄화" { - val nested = + val nested: List> = listOf( listOf(1, 2), listOf(3, 4), ) - val flattened = + val flattened: List = nested.flatMapIndexed { index, list -> list.map { value -> index * value } } @@ -69,24 +69,24 @@ class FlatteningTest : FreeSpec({ } "flatMapTo - 중첩된 리스트를 평탄화해서 기존 컬렉션에 추가" { - val source = + val source: List> = listOf( listOf(1, 2), listOf(3, 4), ) - val destination = mutableListOf(0) + val destination: MutableList = mutableListOf(0) source.flatMapTo(destination) { it } destination shouldBe listOf(0, 1, 2, 3, 4) } "flatMapIndexed - 인덱스와 함께 각 요소를 여러 개로 펼친 후 평탄화해서 기존 컬렉션에 추가" { - val nested = + val nested: List> = listOf( listOf(1, 2), listOf(3, 4), ) - val destination = mutableListOf(0, 1, 2) + val destination: MutableList = mutableListOf(0, 1, 2) nested.flatMapIndexedTo(destination) { index, list -> list.map { value -> value * 2 } } @@ -97,8 +97,8 @@ class FlatteningTest : FreeSpec({ "Map 의 flattening" - { "flatMap - Map> 구조를 평탄화" { - val map = - mapOf>( + val map: Map> = + mapOf( "a" to listOf(1, 2), "b" to listOf(3), ) @@ -112,8 +112,8 @@ class FlatteningTest : FreeSpec({ } "flatMap - 사실 flatMap 의 람다 파라미터의 리턴 타입이 Iterable 타입이기만 하면 된다." { - val map = - mapOf( + val map: Map = + mapOf( "a" to 1, "b" to 2, "c" to 3, @@ -128,13 +128,15 @@ class FlatteningTest : FreeSpec({ } "toList - Map 를 List> 로 변환" { - val map = - mapOf>( + val map: Map> = + mapOf( "a" to listOf(1, 2), "b" to listOf(3), ) - map.toList() shouldBe + val toList: List>> = map.toList() + + toList shouldBe listOf>>( "a" to listOf(1, 2), "b" to listOf(3), @@ -142,8 +144,8 @@ class FlatteningTest : FreeSpec({ } "flatMapValues 메서드는 따로 없어서 Map.values.flatten 사용" { - val map = - mapOf>( + val map: Map> = + mapOf( "a" to listOf(1, 2), "b" to listOf(3), ) @@ -153,8 +155,8 @@ class FlatteningTest : FreeSpec({ } "flatPairValues 메서드는 따로 없어서 아래처럼 재구성 필요" { - val map = - mapOf>( + val map: Map> = + mapOf( "a" to listOf(1, 2), "b" to listOf(3), ) @@ -179,11 +181,11 @@ class FlatteningTest : FreeSpec({ } "Map.toList().flatten 은 단순 Key-Value Pair 리스트로 변환" { - val map = mapOf() + val map: Map = mapOf() val flatList: List> = map.toList() - val flatMap = + val flatMap: List> = map.flatMap { entry -> entry.value.map { value -> entry.key to value } } 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 index 303e76f..8f69828 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt @@ -6,12 +6,12 @@ import io.kotest.matchers.shouldBe class MappingTest : FreeSpec({ "리스트 컬렉션의 mapping - 원소 변환" - { "map - 각 원소를 변환" { - val numbers = listOf(1, 2, 3) + val numbers: List = listOf(1, 2, 3) numbers.map { number -> number * 2 } shouldBe listOf(2, 4, 6) } "mapIndexed - 인덱스를 함께 사용한 변환" { - val names = listOf("a", "b", "c") + val names: List = listOf("a", "b", "c") names.mapIndexed { index, name -> "$index: $name" } shouldBe listOf( "0: a", @@ -21,15 +21,15 @@ class MappingTest : FreeSpec({ } "mapNotNull - null 을 걸러내며 변환" { - val tags = listOf("1", "a", "2") - val numberTags = tags.mapNotNull { tag -> tag.toIntOrNull() } + val tags: List = listOf("1", "a", "2") + val numberTags: List = tags.mapNotNull { tag -> tag.toIntOrNull() } numberTags shouldBe listOf(1, 2) } "mapIndexedNotNull - 인덱스를 함께 사용하며 null 을 걸러내며 변환" { - val tags = listOf("1", "a", "2", null) - val numberTags = + val tags: List = listOf("1", "a", "2", null) + val numberTags: List = tags .mapIndexedNotNull { index, tag -> if (index == 0) return@mapIndexedNotNull null @@ -40,16 +40,16 @@ class MappingTest : FreeSpec({ } "mapTo - 결과를 미리 만든 리스트에 추가" { - val source = listOf(1, 2, 3) - val destination = mutableListOf(0, 0) + 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 = listOf(1, 2, 3) - val destination = mutableListOf>() + val source: List = listOf(1, 2, 3) + val destination: MutableList> = mutableListOf() source.mapIndexedTo(destination) { index, value -> index to value } @@ -58,16 +58,16 @@ class MappingTest : FreeSpec({ } "mapNotNullTo - 결과를 미리 만든 리스트에 null 빼고 추가" { - val source = listOf(1, 2, null) - val destination = mutableListOf(0, 0) + 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 = listOf("0", "a", "2", "b") - val result = mutableListOf>() + val source: List = listOf("0", "a", "2", "b") + val result: MutableList> = mutableListOf() source.mapIndexedNotNullTo(result) { index, value -> value.toIntOrNull()?.let { index to it } @@ -77,14 +77,14 @@ class MappingTest : FreeSpec({ } "associate - List 를 Map 으로 변환 (Key, Value 모두 수동 지정)" { - val words = listOf("apple", "banana") - val wordsWithLength = words.associate { word -> word to word.length } + 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 = listOf("apple", "banana") - val fruitsWithLength = fruits.associateWith { it.length } + val fruits: List = listOf("apple", "banana") + val fruitsWithLength: Map = fruits.associateWith { it.length } fruitsWithLength shouldBe mapOf("apple" to 5, "banana" to 6) } @@ -92,8 +92,8 @@ class MappingTest : FreeSpec({ "associateBy - Key 만 지정하고 Value 는 원본 사용" { data class User(val id: Int, val name: String) - val users = listOf(User(1, "jimmy"), User(2, "tim")) - val usersByCode = + val users: List = listOf(User(1, "jimmy"), User(2, "tim")) + val usersByCode: Map = users.associateBy { user -> user.id * 32 } @@ -106,8 +106,8 @@ class MappingTest : FreeSpec({ } "associateTo - key 만 지정하고 value 는 원본 유지하며 기존 Map에 추가" { - val fruits = listOf("apple", "banana") - val fruitsWithLength = mutableMapOf("lemon" to 5) + val fruits: List = listOf("apple", "banana") + val fruitsWithLength: MutableMap = mutableMapOf("lemon" to 5) fruits.associateTo(fruitsWithLength) { fruit -> fruit to fruit.length @@ -117,8 +117,8 @@ class MappingTest : FreeSpec({ } "associateWithTo - value 만 지정하고 key 는 원본 유지하며 기본 map 에 추가" { - val fruits = listOf("apple", "banana") - val fruitsWithLength = mutableMapOf("lemon" to 5) + val fruits: List = listOf("apple", "banana") + val fruitsWithLength: MutableMap = mutableMapOf("lemon" to 5) fruits.associateWithTo(fruitsWithLength) { fruit -> fruit.length @@ -135,8 +135,8 @@ class MappingTest : FreeSpec({ "associateByTo - key만 지정하고 value 는 원본 유지하며 기존 map 에 추가" { data class User(val id: Int, val name: String) - val users = listOf(User(1, "A"), User(2, "B")) - val codeWithUsers = mutableMapOf(10 to User(3, "ABC")) + 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 From 8233cc345d0a304044f46c1a658fb2752067e3c2 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Thu, 17 Apr 2025 22:46:54 +0900 Subject: [PATCH 12/32] chore(ListFunctionalApiTest.kt): remove --- .../functional/ListFunctionalApiTest.kt | 78 ------------------- 1 file changed, 78 deletions(-) delete mode 100644 app/src/test/java/com/example/learningtest/collection/functional/ListFunctionalApiTest.kt diff --git a/app/src/test/java/com/example/learningtest/collection/functional/ListFunctionalApiTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/ListFunctionalApiTest.kt deleted file mode 100644 index b59fbe9..0000000 --- a/app/src/test/java/com/example/learningtest/collection/functional/ListFunctionalApiTest.kt +++ /dev/null @@ -1,78 +0,0 @@ -package com.example.learningtest.collection.functional - -import io.kotest.core.spec.style.FreeSpec -import io.kotest.matchers.shouldBe - -class ListFunctionalApiTest : FreeSpec({ - "map - 원소 변환" - { - val names = listOf("a", "b", "c") - val users = names.map { "$it!" } - - users shouldBe listOf("a!", "b!", "c!") - } - - "filter - 조건에 맞는 원소만 남기기" - { - val numbers = listOf(1, 2, 3, 4, 5) - val evens = numbers.filter { it % 2 == 0 } - - evens shouldBe listOf(2, 4) - } - - "find == firstOrNull - 조건을 만족하는 첫 요소 찾기 " - { - val names = listOf("kim", "lee", "park") - val found = names.find { it.startsWith("p") } - val firstOrNull = names.firstOrNull { it.startsWith("p") } - - found shouldBe "park" - firstOrNull shouldBe "park" - } - - "any, all, none - 조건 검증" - { - val numbers = listOf(1, 2, 3, 4) - - numbers.any { it > 3 } shouldBe true - numbers.all { it < 10 } shouldBe true - numbers.none { it < 0 } shouldBe true - } - - "flatMap - 리스트 안의 리스트 펼치기" - { - val nested = - listOf( - listOf(1, 2), - listOf(3, 4), - ) - - val flattened = nested.flatMap { it } - flattened shouldBe listOf(1, 2, 3, 4) - } - - "groupBy - 조건에 따라 그룹화" - { - val words = listOf("apple", "banana", "avocado", "blueberry") - val grouped = words.groupBy { it.first() } - - grouped['a'] shouldBe listOf("apple", "avocado") - grouped['b'] shouldBe listOf("banana", "blueberry") - } - - "fold - 누적 계산(초기값 있음)" - { - val numbers = listOf(1, 2, 3) - val sum = numbers.fold(10) { acc, value -> acc + value } - - sum shouldBe 16 - } - - "reduce - 누적 계산(초기값 없음)" - { - val numbers = listOf(1, 2, 3) - val product = numbers.reduce { acc, value -> acc * value } - - product shouldBe 6 - } - - "zip - 두 리스트를 하나로 결합" - { - val names = listOf("철수", "영희") - val ages = listOf(20, 21) - - val result = names.zip(ages) { name, age -> "$name ($age)" } - result shouldBe listOf("철수 (20)", "영희 (21)") - } -}) \ No newline at end of file From e8b03431446f88d2e46c36db5d2e764a6e4b91d7 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 18 Apr 2025 17:15:11 +0900 Subject: [PATCH 13/32] test(FlatteningTest): improve test description and update map flattening logic --- .../collection/functional/FlatteningTest.kt | 22 +++++++++++-------- 1 file changed, 13 insertions(+), 9 deletions(-) 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 index 8e22c95..23d2524 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/FlatteningTest.kt @@ -2,7 +2,6 @@ package com.example.learningtest.collection.functional import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe -import kotlin.collections.flatten class FlatteningTest : FreeSpec({ "List 의 flattening" - { @@ -180,17 +179,22 @@ class FlatteningTest : FreeSpec({ result1 shouldBe result2 } - "Map.toList().flatten 은 단순 Key-Value Pair 리스트로 변환" { - val map: Map = mapOf() + "Map.toList(). 은 단순 List> 리스트로 변환" { + val map: Map = + mapOf( + "a" to "1", + "b" to "2", + "c" to "3", + ) val flatList: List> = map.toList() - val flatMap: List> = - map.flatMap { entry -> - entry.value.map { value -> entry.key to value } - } - - flatList shouldBe flatMap + flatList shouldBe + listOf>( + "a" to "1", + "b" to "2", + "c" to "3", + ) } } }) From 3d766d46a23acdb350fe328fecfc33c3268616e3 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 18 Apr 2025 17:15:18 +0900 Subject: [PATCH 14/32] test(MappingTest): refactor mapIndexedNotNull logic for improved readability --- .../learningtest/collection/functional/MappingTest.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) 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 index 8f69828..769de9d 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/MappingTest.kt @@ -32,9 +32,11 @@ class MappingTest : FreeSpec({ val numberTags: List = tags .mapIndexedNotNull { index, tag -> - if (index == 0) return@mapIndexedNotNull null - - tag?.toIntOrNull() + if (index == 0) { + null + } else { + tag?.toIntOrNull() + } } numberTags shouldBe listOf(2) } From 2fad10ddfce202494795e8a8e30064ac23441378 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 18 Apr 2025 17:44:22 +0900 Subject: [PATCH 15/32] test(SearchingTest): add comprehensive tests for list searching methods first, find, firstOrNull, last, findLast, lastOrNull, indexOf, all, any, none method --- .../collection/functional/SearchingTest.kt | 142 ++++++++++++++++++ 1 file changed, 142 insertions(+) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt diff --git a/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt new file mode 100644 index 0000000..2564166 --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt @@ -0,0 +1,142 @@ +package com.example.learningtest.collection.functional + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class SearchingTest : FreeSpec({ + "List Searching" - { + "first - 조건을 만족하는 첫 요소 (없으면 예외 던짐)" { + val numbers: List = listOf(1, 2, 3, 4) + + numbers.first() shouldBe 1 + numbers.first { it > 2 } shouldBe 3 + shouldThrow { numbers.first { it > 10 } } + } + + "find == firstOrNull - 조건을 만족하는 첫 요소(없으면 null)" { + val words: List = listOf("apple", "banana", "cherry") + + words.find { it.startsWith("b") } shouldBe "banana" + words.find { it.startsWith("z") } shouldBe null + words.firstOrNull { it.startsWith("z") } shouldBe null + } + + "last - 조건을 만족하는 마지막 요소 (없으면 예외)" { + val numbers: List = listOf(1, 3, 5, 2, 4) + + numbers.last() shouldBe 4 + numbers.last { it % 2 == 1 } shouldBe 5 + shouldThrow { numbers.last { it > 10 } } + } + + "findLast & lastOrNull - 조건을 만족하는 마지막 요소 (없으면 null)" { + val numbers: List = listOf(1, 2, 3, 4) + + numbers.findLast { it % 2 == 1 } shouldBe 3 + numbers.findLast { it > 5 } shouldBe null + + numbers.lastOrNull() shouldBe 4 + numbers.lastOrNull { it > 5 } shouldBe null + } + + "indexOf - 해당 값의 첫 위치 (없으면 -1)" { + val names: List = listOf("jim", "pam", "jim") + + names.indexOf("jim") shouldBe 0 + names.indexOf("shim") shouldBe -1 + } + + "all - 모든 요소가 조건을 만족하면 true" { + val numbers: List = listOf(2, 4, 6) + + numbers.all { it % 2 == 0 } shouldBe true + numbers.all { it > 4 } shouldBe false + } + + "any - 조건을 만족하는 요소가 하나라도 있으면 true" { + val numbers: List = listOf(1, 2, 3) + + numbers.any { it > 2 } shouldBe true + numbers.any { it < 0 } shouldBe false + } + + "none - 모든 요소가 조건을 만족하지 않으면 true" { + val characters = listOf('a', 'b', 'c') + + characters.none { it == 'z' } shouldBe true + characters.none { it == 'a' } shouldBe false + } + } +}) + +/* +@file:Suppress("TestFunctionName") + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe +import io.kotest.assertions.throwables.shouldThrow + +@DisplayName("고급 Searching API") +class AdvancedSearchingTest : FreeSpec({ + + "indexOfFirst - 조건을 만족하는 첫 인덱스 (없으면 -1)" { + val names = listOf("kim", "lee", "choi", "kim") + names.indexOfFirst { it.startsWith("k") } shouldBe 0 + names.indexOfFirst { it == "park" } shouldBe -1 + } + + "indexOfLast - 조건을 만족하는 마지막 인덱스 (없으면 -1)" { + val names = listOf("kim", "lee", "choi", "kim") + names.indexOfLast { it.startsWith("k") } shouldBe 3 + names.indexOfLast { it == "park" } shouldBe -1 + } + + "firstNotNullOf - 변환 결과가 null 아닌 첫 값 (없으면 예외)" { + data class User(val name: String?, val age: Int) + val users = listOf( + User(null, 20), + User("a", 25), + User("b", 30), + ) + + val name = users.firstNotNullOf { it.name } + name shouldBe "a" + + shouldThrow { + listOf().firstNotNullOf { it.name } + } + } + + "firstNotNullOfOrNull - 변환 결과가 null 아닌 첫 값 (없으면 null)" { + data class Item(val value: String?) + val items = listOf( + Item(null), + Item(null), + Item("found"), + ) + + val result = items.firstNotNullOfOrNull { it.value } + result shouldBe "found" + + val result2 = items.take(2).firstNotNullOfOrNull { it.value } + result2 shouldBe null + } + + // 보너스: lastNotNullOf 없음 → 대체 방법 + "lastNotNullOfOrNull - 직접 구현 예시 (mapNotNull + lastOrNull)" { + data class Log(val content: String?) + val logs = listOf( + Log(null), + Log("warn"), + Log("info"), + Log(null), + ) + + val last = logs.mapNotNull { it.content }.lastOrNull() + last shouldBe "info" + } + +}) + +* */ From aeb0a7f0959ec20b86030a8fd325ebced14c8c99 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 18 Apr 2025 19:30:30 +0900 Subject: [PATCH 16/32] test(SearchingTest): add more indexOf tests --- .../collection/functional/SearchingTest.kt | 104 +++++++++--------- 1 file changed, 49 insertions(+), 55 deletions(-) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt index 2564166..e1b91df 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt @@ -5,6 +5,8 @@ import io.kotest.core.spec.style.FreeSpec import io.kotest.matchers.shouldBe class SearchingTest : FreeSpec({ + data class User(val name: String?, val age: Int) + "List Searching" - { "first - 조건을 만족하는 첫 요소 (없으면 예외 던짐)" { val numbers: List = listOf(1, 2, 3, 4) @@ -40,13 +42,6 @@ class SearchingTest : FreeSpec({ numbers.lastOrNull { it > 5 } shouldBe null } - "indexOf - 해당 값의 첫 위치 (없으면 -1)" { - val names: List = listOf("jim", "pam", "jim") - - names.indexOf("jim") shouldBe 0 - names.indexOf("shim") shouldBe -1 - } - "all - 모든 요소가 조건을 만족하면 true" { val numbers: List = listOf(2, 4, 6) @@ -68,75 +63,74 @@ class SearchingTest : FreeSpec({ characters.none { it == 'a' } shouldBe false } } -}) -/* -@file:Suppress("TestFunctionName") - -import io.kotest.core.spec.style.FreeSpec -import io.kotest.matchers.shouldBe -import io.kotest.assertions.throwables.shouldThrow + "indexOf - 해당 값의 첫 위치 (없으면 -1)" { + val names: List = listOf("jim", "pam", "jim") -@DisplayName("고급 Searching API") -class AdvancedSearchingTest : FreeSpec({ + names.indexOf("jim") shouldBe 0 + names.indexOf("shim") shouldBe -1 + } "indexOfFirst - 조건을 만족하는 첫 인덱스 (없으면 -1)" { - val names = listOf("kim", "lee", "choi", "kim") + val names: List = listOf("kim", "lee", "choi", "kim") + names.indexOfFirst { it.startsWith("k") } shouldBe 0 names.indexOfFirst { it == "park" } shouldBe -1 } - "indexOfLast - 조건을 만족하는 마지막 인덱스 (없으면 -1)" { - val names = listOf("kim", "lee", "choi", "kim") + "indexOfLast - 조건을 만족하는 마지막 인덱스(없으면 -1)" { + val names: List = listOf("kim", "lee", "choi", "kim") + names.indexOfLast { it.startsWith("k") } shouldBe 3 names.indexOfLast { it == "park" } shouldBe -1 } - "firstNotNullOf - 변환 결과가 null 아닌 첫 값 (없으면 예외)" { - data class User(val name: String?, val age: Int) - val users = listOf( - User(null, 20), - User("a", 25), - User("b", 30), - ) + "firstNotNullOf - 변환 결과가 null 이 아닌 첫 값(없으면 예외)" { + + val users1: List = + listOf( + User(null, 20), + User("a", 25), + User("b", 30), + ) + val firstName = users1.firstNotNullOf { it.name } + firstName shouldBe "a" - val name = users.firstNotNullOf { it.name } - name shouldBe "a" + val users2: List = listOf(User(null, 20), User(null, 25)) shouldThrow { - listOf().firstNotNullOf { it.name } + users2.firstNotNullOf { it.name } } } - "firstNotNullOfOrNull - 변환 결과가 null 아닌 첫 값 (없으면 null)" { - data class Item(val value: String?) - val items = listOf( - Item(null), - Item(null), - Item("found"), - ) + "firstNotNullOfOrNull - 변환 결과가 null 이 아닌 첫 값(없으면 null)" { + val users1: List = + listOf( + User(null, 20), + User(null, 25), + User("Lilly", 30), + ) - val result = items.firstNotNullOfOrNull { it.value } - result shouldBe "found" + val firstName: String? = users1.firstNotNullOfOrNull { it.name } + firstName shouldBe "Lilly" - val result2 = items.take(2).firstNotNullOfOrNull { it.value } - result2 shouldBe null - } + val users2: List = + listOf( + User(null, 20), + User(null, 25), + ) - // 보너스: lastNotNullOf 없음 → 대체 방법 - "lastNotNullOfOrNull - 직접 구현 예시 (mapNotNull + lastOrNull)" { - data class Log(val content: String?) - val logs = listOf( - Log(null), - Log("warn"), - Log("info"), - Log(null), - ) - - val last = logs.mapNotNull { it.content }.lastOrNull() - last shouldBe "info" + users2.firstNotNullOfOrNull { it.name } shouldBe null } -}) + "lastNotNullOf & lastNotNullOfOrNull 메서드는 없음 - 직접 구현 예시(mapNotNull 활용)" { + val users: List = + listOf( + User(null, 30), + User(null, 25), + ) -* */ + val lastName: String? = users.mapNotNull { it.name }.lastOrNull() + lastName shouldBe null + } +}) From 6d4fd4cd0c2ec62351a78285ff28e23ba4874b6d Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 18 Apr 2025 19:36:51 +0900 Subject: [PATCH 17/32] test(SearchingTest): add more indexOf tests lastIndexOf, indexOfFirst, indexOfLast --- .../collection/functional/SearchingTest.kt | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt index e1b91df..a853da6 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/SearchingTest.kt @@ -71,20 +71,42 @@ class SearchingTest : FreeSpec({ names.indexOf("shim") shouldBe -1 } - "indexOfFirst - 조건을 만족하는 첫 인덱스 (없으면 -1)" { + "lastIndexOf - 해당 값의 마지막 위치 (없으면 -1)" { + val names: List = listOf("jim", "pam", "jim") + + names.lastIndexOf("jim") shouldBe 2 + names.lastIndexOf("shim") shouldBe -1 + } + + "indexOfFirst - 조건을 만족하는 원소 중 첫 인덱스 (없으면 -1)" { val names: List = listOf("kim", "lee", "choi", "kim") names.indexOfFirst { it.startsWith("k") } shouldBe 0 names.indexOfFirst { it == "park" } shouldBe -1 } - "indexOfLast - 조건을 만족하는 마지막 인덱스(없으면 -1)" { + "indexOfLast - 조건을 만족하는 원소 중 마지막 인덱스(없으면 -1)" { val names: List = listOf("kim", "lee", "choi", "kim") names.indexOfLast { it.startsWith("k") } shouldBe 3 names.indexOfLast { it == "park" } shouldBe -1 } + "조건을 만족하는 원소가 없으면 -1 인덱스를 리턴하는 게 아닌 null 리턴하도록" { + val names: List = listOf("kim", "lee", "choi", "kim") + + names + .indexOf("park") + .takeIf { it != -1 } shouldBe null + names + .indexOfFirst { it.startsWith("p") } + .takeIf { it != -1 } shouldBe null + + names + .indexOfLast { it.startsWith("p") } + .takeIf { it != -1 } shouldBe null + } + "firstNotNullOf - 변환 결과가 null 이 아닌 첫 값(없으면 예외)" { val users1: List = From 4f650832e9eea423f9eff907c14d86610edd3af8 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Tue, 22 Apr 2025 15:53:27 +0900 Subject: [PATCH 18/32] test(SortingTest): add comprehensive tests for list sorting methods sorted, sortedDescending, sortedBy, sortedByDescending, reversed --- .../collection/functional/SortingTest.kt | 73 +++++++++++++++++++ 1 file changed, 73 insertions(+) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt diff --git a/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt new file mode 100644 index 0000000..9059aac --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt @@ -0,0 +1,73 @@ +package com.example.learningtest.collection.functional + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.collections.shouldBeSorted +import io.kotest.matchers.collections.shouldBeSortedBy +import io.kotest.matchers.collections.shouldBeSortedDescending +import io.kotest.matchers.collections.shouldBeSortedDescendingBy +import io.kotest.matchers.shouldBe + +class SortingTest : FreeSpec({ + "List Sorting" - { + data class User(val name: String, val age: Int) + + "sorted - 오름차순 정렬" { + val numbers: List = listOf(3, 1, 4, 2) + val sorted: List = numbers.sorted() + + sorted shouldBe listOf(1, 2, 3, 4) + sorted.shouldBeSorted() + } + + "sortedDescending - 내림차순 정렬" { + val numbers: List = listOf(3, 1, 4, 2) + val sortedDescending: List = numbers.sortedDescending() + + sortedDescending shouldBe listOf(4, 3, 2, 1) + sortedDescending.shouldBeSortedDescending() + } + + "sortedBy - 특정 속성 기준 정렬" { + val users: List = + listOf( + User("A", 30), + User("B", 20), + User("C", 25), + ) + val sorted: List = users.sortedBy { it.age } + + sorted shouldBe + listOf( + User("B", 20), + User("C", 25), + User("A", 30), + ) + sorted shouldBeSortedBy { it.age } + } + + "sortedByDescending - 특정 속성 기준 내림차순 정렬" { + val users: List = + listOf( + User("A", 30), + User("B", 20), + User("C", 25), + ) + val sorted: List = users.sortedByDescending { it.age } + + sorted shouldBe + listOf( + User("A", 30), + User("C", 25), + User("B", 20), + ) + sorted shouldBeSortedDescendingBy { it.age } + } + + "reversed - 순서만 뒤집기 (뒤집으면서 정렬하지는 않음)" { + val original: List = listOf(1, 3, 2) + val reversed: List = original.reversed() + + reversed shouldBe listOf(2, 3, 1) + } + } +}) From f8fd8936798cdb065f16108cbf897fc28cd03677 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Tue, 22 Apr 2025 17:29:46 +0900 Subject: [PATCH 19/32] test(SortingTest): add tests for sortedWith functionality with various comparators --- .../collection/functional/SortingTest.kt | 124 ++++++++++++++++++ 1 file changed, 124 insertions(+) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt index 9059aac..36b3817 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt @@ -5,6 +5,7 @@ import io.kotest.matchers.collections.shouldBeSorted import io.kotest.matchers.collections.shouldBeSortedBy import io.kotest.matchers.collections.shouldBeSortedDescending import io.kotest.matchers.collections.shouldBeSortedDescendingBy +import io.kotest.matchers.collections.shouldBeSortedWith import io.kotest.matchers.shouldBe class SortingTest : FreeSpec({ @@ -63,6 +64,129 @@ class SortingTest : FreeSpec({ sorted shouldBeSortedDescendingBy { it.age } } + "sortedWith 는 Comparator 를 사용하여 정렬" - { + "sortedWith - 기본" { + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Bob", 28), + ) + val sortedByAge1: List = users.sortedWith(compareBy { it.age }) + val sortedByAge2: List = + users.sortedWith { user1, user2 -> + user1.age.compareTo(user2.age) + } + val sortedByAge3: List = + users.sortedWith { user1, user2 -> + user1.age - user2.age + } + + val expected: List = + listOf( + User("Cathy", 22), + User("Bob", 28), + User("Alice", 30), + ) + + sortedByAge1 shouldBe expected + sortedByAge2 shouldBe expected + sortedByAge3 shouldBe expected + + sortedByAge1.shouldBeSortedWith(compareBy { it.age }) + sortedByAge2.shouldBeSortedWith(compareBy { it.age }) + sortedByAge3.shouldBeSortedWith(compareBy { it.age }) + } + + "sortedWith - 이름이 같을 경우 입력 순서 유지 (Stable Sort)" { + val users: List = + listOf( + User("Alice", 30), + User("Bob", 25), + User("Alice", 22), + ) + val sortedByName1: List = users.sortedWith(compareBy { it.name }) + val sortedByName2: List = + users.sortedWith { user1, user2 -> + user1.name.compareTo(user2.name) + } + /* String 타입의 비교는 - 연산으로 불가능하다. + val sortedByName3 : List = users.sortedWith { user1, user2 -> + user1.name - user2.name + } + */ + + val expected: List = + listOf( + User("Alice", 30), + User("Alice", 22), + User("Bob", 25), + ) + + sortedByName1 shouldBe expected + sortedByName2 shouldBe expected + + sortedByName1 shouldBeSortedWith (compareBy { it.name }) + sortedByName2 shouldBeSortedWith (compareBy(User::name)) + } + + "sortedWith + compareByDescending" { + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Bob", 28), + ) + + val sortedByDescendingAge1: List = + users.sortedWith( + compareByDescending { + it.age + }, + ) + val sortedByDescendingAge2: List = + users.sortedWith { user1, user2 -> + user2.age.compareTo(user1.age) + } + val sortedByDescendingAge3: List = + users.sortedWith { user1, user2 -> + user2.age - user1.age + } + + val expected: List = + listOf( + User("Alice", 30), + User("Bob", 28), + User("Cathy", 22), + ) + + sortedByDescendingAge1 shouldBe expected + sortedByDescendingAge2 shouldBe expected + sortedByDescendingAge3 shouldBe expected + + sortedByDescendingAge1 shouldBeSortedWith (compareByDescending(User::age)) + sortedByDescendingAge2 shouldBeSortedWith (compareByDescending(User::age)) + sortedByDescendingAge3 shouldBeSortedWith (compareByDescending(User::age)) + } + + "sortedWith - 이름으로 오름차순 정렬, 이름이 같다면 나이로 오름차순 정렬" { + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Alice", 28), + ) + + val sorted1: List = + users.sortedWith(compareBy { it.name }.thenBy { it.age }) + val sorted2: List = users.sortedWith(compareBy(User::name).thenBy(User::age)) + val sorted3: List = + users.sortedWith { user1, user2 -> + user1.name.compareTo(user2.name) + } + } + } + "reversed - 순서만 뒤집기 (뒤집으면서 정렬하지는 않음)" { val original: List = listOf(1, 3, 2) val reversed: List = original.reversed() From 7bc7c007ea8de346ed1b5119c519fb8a3a253bfe Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 2 May 2025 19:06:08 +0900 Subject: [PATCH 20/32] =?UTF-8?q?test(SortingTest):=20sortedWith=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit compareBy, thenBy, thenByDescending, naturalOrder, reverseOrder 추가 --- .../collection/functional/SortingTest.kt | 117 +++++++++++++++++- 1 file changed, 112 insertions(+), 5 deletions(-) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt index 36b3817..1ebff1f 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt @@ -72,7 +72,7 @@ class SortingTest : FreeSpec({ User("Cathy", 22), User("Bob", 28), ) - val sortedByAge1: List = users.sortedWith(compareBy { it.age }) + val sortedByAge1: List = users.sortedWith(compareBy(User::age)) val sortedByAge2: List = users.sortedWith { user1, user2 -> user1.age.compareTo(user2.age) @@ -98,7 +98,7 @@ class SortingTest : FreeSpec({ sortedByAge3.shouldBeSortedWith(compareBy { it.age }) } - "sortedWith - 이름이 같을 경우 입력 순서 유지 (Stable Sort)" { + "sortedWith - 나이로 정렬하되 다른 필드가 모두 같을 경우 입력 순서 유지 (Stable Sort)" { val users: List = listOf( User("Alice", 30), @@ -179,11 +179,118 @@ class SortingTest : FreeSpec({ val sorted1: List = users.sortedWith(compareBy { it.name }.thenBy { it.age }) - val sorted2: List = users.sortedWith(compareBy(User::name).thenBy(User::age)) - val sorted3: List = + val sorted2: List = users.sortedWith { user1, user2 -> - user1.name.compareTo(user2.name) + val nameComparison = user1.name.compareTo(user2.name) + if (nameComparison != 0) { + return@sortedWith nameComparison + } + return@sortedWith user1.age.compareTo(user2.age) } + + val expected: List = + listOf( + User("Alice", 28), + User("Alice", 30), + User("Cathy", 22), + ) + + sorted1 shouldBe expected + sorted2 shouldBe expected + } + + "sortedWith - 이름으로 오름차순 정렬, 이름이 같다면 나이로 내림차순 정렬" { + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Alice", 28), + ) + + val sorted1: List = + users.sortedWith(compareBy { it.name }.thenByDescending { it.age }) + val sorted2: List = + users.sortedWith { user1, user2 -> + val nameComparison = user1.name.compareTo(user2.name) + if (nameComparison != 0) { + return@sortedWith nameComparison + } + return@sortedWith user2.age.compareTo(user1.age) + } + + val expected: List = + listOf( + User("Alice", 30), + User("Alice", 28), + User("Cathy", 22), + ) + + sorted1 shouldBe expected + sorted2 shouldBe expected + } + + "naturalOrder() - Comparable 인터페이스를 구현하는 타입의 자연스러운 오름차순 순서에 따라 정렬하는 Comparator" { + data class User( + val name: String, + val age: Int, + ) : Comparable { + override fun compareTo(other: User): Int = + compareBy(User::name) + .thenBy(User::age) + .compare(this, other) + } + + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Alice", 28), + ) + + // sortedWith(naturalOrder()) == sorted() + val sorted1 = users.sortedWith(naturalOrder()) + val sorted2 = users.sorted() + + val expected: List = + listOf( + User("Alice", 28), + User("Alice", 30), + User("Cathy", 22), + ) + sorted1 shouldBe expected + sorted2 shouldBe expected + } + + "reversedOrder() - Comparable 인터페이스를 구현하는 타입의 자연스러운 순서의 역순에 따라 정렬하는 Comparator" { + data class User( + val name: String, + val age: Int, + ) : Comparable { + override fun compareTo(other: User): Int = + compareBy(User::name) + .thenBy(User::age) + .compare(this, other) + } + + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Alice", 28), + ) + + // sortedWith(reversedOrder()) == sortedDescending() + val sorted1 = users.sortedWith(reverseOrder()) + val sorted2 = users.sortedDescending() + + val expected: List = + listOf( + User("Cathy", 22), + User("Alice", 30), + User("Alice", 28), + ) + sorted1 shouldBe expected + sorted2 shouldBe expected } } From e9ad6d6e0b5d6cb04c186ec7eb7c537b7e97aa8a Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 2 May 2025 19:11:15 +0900 Subject: [PATCH 21/32] =?UTF-8?q?test(SortingTest):=20=EA=B8=B0=EC=A1=B4?= =?UTF-8?q?=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A0=95=EB=A0=AC=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=8F=AC?= =?UTF-8?q?=EA=B4=84=EC=A0=81=EC=9D=B8=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit sort, sortDescending, sortBy, sortWith, reverse --- .../collection/functional/SortingTest.kt | 529 ++++++++++-------- 1 file changed, 295 insertions(+), 234 deletions(-) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt index 1ebff1f..7ba0c01 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt @@ -12,293 +12,354 @@ class SortingTest : FreeSpec({ "List Sorting" - { data class User(val name: String, val age: Int) - "sorted - 오름차순 정렬" { - val numbers: List = listOf(3, 1, 4, 2) - val sorted: List = numbers.sorted() + "정렬하여 새로운 컬렉션 생성" - { + "sorted - 오름차순 정렬" { + val numbers: List = listOf(3, 1, 4, 2) + val sorted: List = numbers.sorted() - sorted shouldBe listOf(1, 2, 3, 4) - sorted.shouldBeSorted() - } - - "sortedDescending - 내림차순 정렬" { - val numbers: List = listOf(3, 1, 4, 2) - val sortedDescending: List = numbers.sortedDescending() - - sortedDescending shouldBe listOf(4, 3, 2, 1) - sortedDescending.shouldBeSortedDescending() - } + sorted shouldBe listOf(1, 2, 3, 4) + sorted.shouldBeSorted() + } - "sortedBy - 특정 속성 기준 정렬" { - val users: List = - listOf( - User("A", 30), - User("B", 20), - User("C", 25), - ) - val sorted: List = users.sortedBy { it.age } - - sorted shouldBe - listOf( - User("B", 20), - User("C", 25), - User("A", 30), - ) - sorted shouldBeSortedBy { it.age } - } + "sortedDescending - 내림차순 정렬" { + val numbers: List = listOf(3, 1, 4, 2) + val sortedDescending: List = numbers.sortedDescending() - "sortedByDescending - 특정 속성 기준 내림차순 정렬" { - val users: List = - listOf( - User("A", 30), - User("B", 20), - User("C", 25), - ) - val sorted: List = users.sortedByDescending { it.age } - - sorted shouldBe - listOf( - User("A", 30), - User("C", 25), - User("B", 20), - ) - sorted shouldBeSortedDescendingBy { it.age } - } + sortedDescending shouldBe listOf(4, 3, 2, 1) + sortedDescending.shouldBeSortedDescending() + } - "sortedWith 는 Comparator 를 사용하여 정렬" - { - "sortedWith - 기본" { + "sortedBy - 특정 속성 기준 정렬" { val users: List = listOf( - User("Alice", 30), - User("Cathy", 22), - User("Bob", 28), + User("A", 30), + User("B", 20), + User("C", 25), ) - val sortedByAge1: List = users.sortedWith(compareBy(User::age)) - val sortedByAge2: List = - users.sortedWith { user1, user2 -> - user1.age.compareTo(user2.age) - } - val sortedByAge3: List = - users.sortedWith { user1, user2 -> - user1.age - user2.age - } + val sorted: List = users.sortedBy { it.age } - val expected: List = - listOf( - User("Cathy", 22), - User("Bob", 28), - User("Alice", 30), + sorted shouldBe + listOf( + User("B", 20), + User("C", 25), + User("A", 30), ) - - sortedByAge1 shouldBe expected - sortedByAge2 shouldBe expected - sortedByAge3 shouldBe expected - - sortedByAge1.shouldBeSortedWith(compareBy { it.age }) - sortedByAge2.shouldBeSortedWith(compareBy { it.age }) - sortedByAge3.shouldBeSortedWith(compareBy { it.age }) + sorted shouldBeSortedBy { it.age } } - "sortedWith - 나이로 정렬하되 다른 필드가 모두 같을 경우 입력 순서 유지 (Stable Sort)" { + "sortedByDescending - 특정 속성 기준 내림차순 정렬" { val users: List = listOf( - User("Alice", 30), - User("Bob", 25), - User("Alice", 22), + User("A", 30), + User("B", 20), + User("C", 25), ) - val sortedByName1: List = users.sortedWith(compareBy { it.name }) - val sortedByName2: List = - users.sortedWith { user1, user2 -> - user1.name.compareTo(user2.name) - } - /* String 타입의 비교는 - 연산으로 불가능하다. - val sortedByName3 : List = users.sortedWith { user1, user2 -> - user1.name - user2.name - } - */ + val sorted: List = users.sortedByDescending { it.age } - val expected: List = - listOf( - User("Alice", 30), - User("Alice", 22), - User("Bob", 25), + sorted shouldBe + listOf( + User("A", 30), + User("C", 25), + User("B", 20), ) + sorted shouldBeSortedDescendingBy { it.age } + } + + "sortedWith 는 Comparator 를 사용하여 정렬" - { + "sortedWith - 기본" { + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Bob", 28), + ) + val sortedByAge1: List = users.sortedWith(compareBy(User::age)) + val sortedByAge2: List = + users.sortedWith { user1, user2 -> + user1.age.compareTo(user2.age) + } + val sortedByAge3: List = + users.sortedWith { user1, user2 -> + user1.age - user2.age + } - sortedByName1 shouldBe expected - sortedByName2 shouldBe expected + val expected: List = + listOf( + User("Cathy", 22), + User("Bob", 28), + User("Alice", 30), + ) - sortedByName1 shouldBeSortedWith (compareBy { it.name }) - sortedByName2 shouldBeSortedWith (compareBy(User::name)) - } + sortedByAge1 shouldBe expected + sortedByAge2 shouldBe expected + sortedByAge3 shouldBe expected - "sortedWith + compareByDescending" { - val users: List = - listOf( - User("Alice", 30), - User("Cathy", 22), - User("Bob", 28), - ) + sortedByAge1.shouldBeSortedWith(compareBy { it.age }) + sortedByAge2.shouldBeSortedWith(compareBy { it.age }) + sortedByAge3.shouldBeSortedWith(compareBy { it.age }) + } - val sortedByDescendingAge1: List = - users.sortedWith( - compareByDescending { - it.age - }, - ) - val sortedByDescendingAge2: List = - users.sortedWith { user1, user2 -> - user2.age.compareTo(user1.age) - } - val sortedByDescendingAge3: List = - users.sortedWith { user1, user2 -> - user2.age - user1.age + "sortedWith - 나이로 정렬하되 다른 필드가 모두 같을 경우 입력 순서 유지 (Stable Sort)" { + val users: List = + listOf( + User("Alice", 30), + User("Bob", 25), + User("Alice", 22), + ) + val sortedByName1: List = users.sortedWith(compareBy { it.name }) + val sortedByName2: List = + users.sortedWith { user1, user2 -> + user1.name.compareTo(user2.name) + } + /* String 타입의 비교는 - 연산으로 불가능하다. + val sortedByName3 : List = users.sortedWith { user1, user2 -> + user1.name - user2.name } + */ - val expected: List = - listOf( - User("Alice", 30), - User("Bob", 28), - User("Cathy", 22), - ) + val expected: List = + listOf( + User("Alice", 30), + User("Alice", 22), + User("Bob", 25), + ) - sortedByDescendingAge1 shouldBe expected - sortedByDescendingAge2 shouldBe expected - sortedByDescendingAge3 shouldBe expected + sortedByName1 shouldBe expected + sortedByName2 shouldBe expected - sortedByDescendingAge1 shouldBeSortedWith (compareByDescending(User::age)) - sortedByDescendingAge2 shouldBeSortedWith (compareByDescending(User::age)) - sortedByDescendingAge3 shouldBeSortedWith (compareByDescending(User::age)) - } + sortedByName1 shouldBeSortedWith (compareBy { it.name }) + sortedByName2 shouldBeSortedWith (compareBy(User::name)) + } - "sortedWith - 이름으로 오름차순 정렬, 이름이 같다면 나이로 오름차순 정렬" { - val users: List = - listOf( - User("Alice", 30), - User("Cathy", 22), - User("Alice", 28), - ) + "sortedWith + compareByDescending" { + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Bob", 28), + ) + + val sortedByDescendingAge1: List = + users.sortedWith( + compareByDescending { + it.age + }, + ) + val sortedByDescendingAge2: List = + users.sortedWith { user1, user2 -> + user2.age.compareTo(user1.age) + } + val sortedByDescendingAge3: List = + users.sortedWith { user1, user2 -> + user2.age - user1.age + } + + val expected: List = + listOf( + User("Alice", 30), + User("Bob", 28), + User("Cathy", 22), + ) - val sorted1: List = - users.sortedWith(compareBy { it.name }.thenBy { it.age }) - val sorted2: List = - users.sortedWith { user1, user2 -> - val nameComparison = user1.name.compareTo(user2.name) - if (nameComparison != 0) { - return@sortedWith nameComparison + sortedByDescendingAge1 shouldBe expected + sortedByDescendingAge2 shouldBe expected + sortedByDescendingAge3 shouldBe expected + + sortedByDescendingAge1 shouldBeSortedWith (compareByDescending(User::age)) + sortedByDescendingAge2 shouldBeSortedWith (compareByDescending(User::age)) + sortedByDescendingAge3 shouldBeSortedWith (compareByDescending(User::age)) + } + + "sortedWith - 이름으로 오름차순 정렬, 이름이 같다면 나이로 오름차순 정렬" { + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Alice", 28), + ) + + val sorted1: List = + users.sortedWith(compareBy { it.name }.thenBy { it.age }) + val sorted2: List = + users.sortedWith { user1, user2 -> + val nameComparison = user1.name.compareTo(user2.name) + if (nameComparison != 0) { + return@sortedWith nameComparison + } + return@sortedWith user1.age.compareTo(user2.age) + } + + val expected: List = + listOf( + User("Alice", 28), + User("Alice", 30), + User("Cathy", 22), + ) + + sorted1 shouldBe expected + sorted2 shouldBe expected + } + + "sortedWith - 이름으로 오름차순 정렬, 이름이 같다면 나이로 내림차순 정렬" { + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Alice", 28), + ) + + val sorted1: List = + users.sortedWith(compareBy { it.name }.thenByDescending { it.age }) + val sorted2: List = + users.sortedWith { user1, user2 -> + val nameComparison = user1.name.compareTo(user2.name) + if (nameComparison != 0) { + return@sortedWith nameComparison + } + return@sortedWith user2.age.compareTo(user1.age) } - return@sortedWith user1.age.compareTo(user2.age) + + val expected: List = + listOf( + User("Alice", 30), + User("Alice", 28), + User("Cathy", 22), + ) + + sorted1 shouldBe expected + sorted2 shouldBe expected + } + + "naturalOrder() - Comparable 인터페이스를 구현하는 타입의 자연스러운 오름차순 순서에 따라 정렬하는 Comparator" { + data class User( + val name: String, + val age: Int, + ) : Comparable { + override fun compareTo(other: User): Int = + compareBy(User::name) + .thenBy(User::age) + .compare(this, other) } - val expected: List = - listOf( - User("Alice", 28), - User("Alice", 30), - User("Cathy", 22), - ) + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Alice", 28), + ) + + // sortedWith(naturalOrder()) == sorted() + val sorted1 = users.sortedWith(naturalOrder()) + val sorted2 = users.sorted() + + val expected: List = + listOf( + User("Alice", 28), + User("Alice", 30), + User("Cathy", 22), + ) + sorted1 shouldBe expected + sorted2 shouldBe expected + } - sorted1 shouldBe expected - sorted2 shouldBe expected + "reversedOrder() - Comparable 인터페이스를 구현하는 타입의 자연스러운 순서의 역순에 따라 정렬하는 Comparator" { + data class User( + val name: String, + val age: Int, + ) : Comparable { + override fun compareTo(other: User): Int = + compareBy(User::name) + .thenBy(User::age) + .compare(this, other) + } + + val users: List = + listOf( + User("Alice", 30), + User("Cathy", 22), + User("Alice", 28), + ) + + // sortedWith(reversedOrder()) == sortedDescending() + val sorted1 = users.sortedWith(reverseOrder()) + val sorted2 = users.sortedDescending() + + val expected: List = + listOf( + User("Cathy", 22), + User("Alice", 30), + User("Alice", 28), + ) + sorted1 shouldBe expected + sorted2 shouldBe expected + } } - "sortedWith - 이름으로 오름차순 정렬, 이름이 같다면 나이로 내림차순 정렬" { - val users: List = - listOf( - User("Alice", 30), - User("Cathy", 22), - User("Alice", 28), - ) + "reversed - 순서만 뒤집기 (뒤집으면서 정렬하지는 않음)" { + val original: List = listOf(1, 3, 2) + val reversed: List = original.reversed() - val sorted1: List = - users.sortedWith(compareBy { it.name }.thenByDescending { it.age }) - val sorted2: List = - users.sortedWith { user1, user2 -> - val nameComparison = user1.name.compareTo(user2.name) - if (nameComparison != 0) { - return@sortedWith nameComparison - } - return@sortedWith user2.age.compareTo(user1.age) - } + reversed shouldBe listOf(2, 3, 1) + } + } - val expected: List = - listOf( - User("Alice", 30), - User("Alice", 28), - User("Cathy", 22), - ) + "기존 Mutable 컬렉션을 정렬" - { + "sort" { + val original: MutableList = mutableListOf(3, 1, 2) + original.sort() - sorted1 shouldBe expected - sorted2 shouldBe expected + original shouldBe listOf(1, 2, 3) } - "naturalOrder() - Comparable 인터페이스를 구현하는 타입의 자연스러운 오름차순 순서에 따라 정렬하는 Comparator" { - data class User( - val name: String, - val age: Int, - ) : Comparable { - override fun compareTo(other: User): Int = - compareBy(User::name) - .thenBy(User::age) - .compare(this, other) - } + "sortDescending" { + val original: MutableList = mutableListOf(3, 1, 2) + original.sortDescending() - val users: List = - listOf( - User("Alice", 30), - User("Cathy", 22), - User("Alice", 28), - ) + original shouldBe listOf(3, 2, 1) + } - // sortedWith(naturalOrder()) == sorted() - val sorted1 = users.sortedWith(naturalOrder()) - val sorted2 = users.sorted() + "sortBy - MutableList 특정 속성 기준 제자리 정렬" { + data class User(val name: String, val age: Int) - val expected: List = - listOf( - User("Alice", 28), - User("Alice", 30), - User("Cathy", 22), + val users: MutableList = + mutableListOf( + User("A", 30), + User("B", 20), + User("C", 25), + ) + users.sortBy { it.age } + users shouldBe + listOf( + User("B", 20), + User("C", 25), + User("A", 30), ) - sorted1 shouldBe expected - sorted2 shouldBe expected } - "reversedOrder() - Comparable 인터페이스를 구현하는 타입의 자연스러운 순서의 역순에 따라 정렬하는 Comparator" { - data class User( - val name: String, - val age: Int, - ) : Comparable { - override fun compareTo(other: User): Int = - compareBy(User::name) - .thenBy(User::age) - .compare(this, other) - } + "sortWith - MutableList Comparator 사용 제자리 정렬" { + data class User(val name: String, val age: Int) - val users: List = - listOf( + val users: MutableList = + mutableListOf( User("Alice", 30), User("Cathy", 22), - User("Alice", 28), + User("Bob", 28), ) - - // sortedWith(reversedOrder()) == sortedDescending() - val sorted1 = users.sortedWith(reverseOrder()) - val sorted2 = users.sortedDescending() - - val expected: List = - listOf( + users.sortWith(compareBy { it.age }) // 원본 리스트가 변경됨 + users shouldBe + listOf( User("Cathy", 22), + User("Bob", 28), User("Alice", 30), - User("Alice", 28), ) - sorted1 shouldBe expected - sorted2 shouldBe expected } - } - "reversed - 순서만 뒤집기 (뒤집으면서 정렬하지는 않음)" { - val original: List = listOf(1, 3, 2) - val reversed: List = original.reversed() + "reverse - 순서만 뒤집기 (뒤집으면서 정렬하지는 않음)" { + val original: MutableList = mutableListOf(1, 3, 2) + original.reverse() - reversed shouldBe listOf(2, 3, 1) + original shouldBe listOf(2, 3, 1) + } } } }) From 16d4bfbab9e0fe047c7b2292adf7048c3ad3d4a7 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 2 May 2025 19:48:24 +0900 Subject: [PATCH 22/32] =?UTF-8?q?test(SortingTest):=20map=20=ED=83=80?= =?UTF-8?q?=EC=9E=85=EC=9D=98=20=EC=A0=95=EB=A0=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit toSortedMap --- .../collection/functional/SortingTest.kt | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt index 7ba0c01..c2ae916 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/SortingTest.kt @@ -7,6 +7,7 @@ import io.kotest.matchers.collections.shouldBeSortedDescending import io.kotest.matchers.collections.shouldBeSortedDescendingBy import io.kotest.matchers.collections.shouldBeSortedWith import io.kotest.matchers.shouldBe +import java.util.SortedMap class SortingTest : FreeSpec({ "List Sorting" - { @@ -362,4 +363,32 @@ class SortingTest : FreeSpec({ } } } + + "Map Sorting" - { + "toSortedMap - 키를 기준으로 오름차순 정렬하여 SortedMap 생성" { + val map: Map = mapOf("c" to 3, "a" to 1, "b" to 2) + val sortedMap: SortedMap = map.toSortedMap() + + sortedMap shouldBe mapOf("a" to 1, "b" to 2, "c" to 3) + } + + "sortedBy - Map의 값을 기준으로 오름차순 정렬하여 List> 생성" { + val map: Map = mapOf("c" to 3, "a" to 1, "b" to 2) + + val sortedList: List> = + map.entries.sortedBy(Map.Entry::value) + + sortedList.map(Map.Entry::value) shouldBe listOf(1, 2, 3) + sortedList.map(Map.Entry::key) shouldBe listOf("a", "b", "c") + } + + "sortedWith - Map의 키를 기준으로 내림차순 정렬하여 List> 생성" { + val map: Map = mapOf("c" to 3, "a" to 1, "b" to 2) + val sortedList: List> = + map.entries.sortedWith(compareByDescending { it.key }) + + sortedList.map(Map.Entry::key) shouldBe listOf("c", "b", "a") + sortedList.map(Map.Entry::value) shouldBe listOf(3, 2, 1) + } + } }) From 6a79e5ec9a1edad1832e2ac2236c8672ac347b02 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 2 May 2025 20:51:33 +0900 Subject: [PATCH 23/32] feat(AggregationTest): aggregation api count, sum, sumOf, average, max, maxOfOrNull, reduce, fold, runningReduce, runningFold --- .../collection/functional/AggregationTest.kt | 201 ++++++++++++++++++ 1 file changed, 201 insertions(+) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt diff --git a/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt new file mode 100644 index 0000000..4ca014d --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt @@ -0,0 +1,201 @@ +package com.example.learningtest.collection.functional + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class AggregationTest : FreeSpec({ + data class User( + val name: String, + val age: Int, + ) + + "List Aggregation" - { + + "count - 컬렉션의 요소 개수" { + val numbers: List = listOf(1, 2, 3, 4, 5) + + numbers.count() shouldBe 5 + numbers.count { it % 2 == 0 } shouldBe 2 + } + + "sum - 컬렉션의 숫자 요소 합계" { + val numbers: List = listOf(1, 2, 3, 4, 5) + + numbers.sum() shouldBe 15 + } + + "sumOf - " { + val users: List = + listOf( + User("Alice", 30), + User("Bob", 25), + User("Charlie", 35), + ) + + users.sumOf { user -> + user.age.takeIf { age -> age >= 30 } ?: 0 + } shouldBe 65 + } + + "average - 컬렉션의 숫자 요소 평균" { + val numbers: List = listOf(1.0, 2.0, 3.0, 4.0, 5.0) + + numbers.average() shouldBe 3.0 + emptyList().average() shouldBe Double.NaN + } + + "max - 컬렉션에서 가장 큰 요소(빈 컬렉션이면 NoSuchElementException 예외 발생)" { + val numbers: List = listOf(10, 5, 20, 15) + + numbers.max() shouldBe 20 + shouldThrow { + emptyList().max() + } + } + + "maxOrNull - 컬렉션에서 가장 큰 요소 (없으면 null)" { + val numbers: List = listOf(10, 5, 20, 15) + + numbers.maxOrNull() shouldBe 20 + emptyList().maxOrNull() shouldBe null + } + + "maxOf - 특정 속성 기준으로 컬렉션에서 가장 큰 요소 (빈 컬렉션이면 예외)" { + val numbers: List = + listOf( + User("Alice", 30), + User("Bob", 25), + User("Charlie", 35), + ) + + numbers.maxOf { user -> user.age } shouldBe 35 + } + + "maxOfOrNull - 특정 속성 기준으로 컬렉션에서 가장 큰 요소 (없으면 null)" { + val users: List = + listOf( + User("Alice", 30), + User("Bob", 25), + User("Charlie", 35), + ) + + users.maxOfOrNull(User::age) shouldBe 35 + emptyList().maxOfOrNull(User::age) shouldBe null + } + + "maxOfOrNull - 특정 속성 기준으로 컬렉션에서 가장 작은 요소 (없으면 null)" { + val users: List = + listOf( + User("Alice", 30), + User("Bob", 25), + User("Charlie", 35), + ) + + users.minOfOrNull(User::age) shouldBe 25 + emptyList().minOfOrNull(User::age) shouldBe null + } + + "reduce - 첫 번째 요소부터 시작하여 누적 연산 (빈 컬렉션에는 사용 불가)" { + val numbers: List = listOf(1, 2, 3, 4) + + // acc: 0, number: 1 -> 1 + // acc: 1, number: 2 -> 3 + // acc: 3, number: 3 -> 2 + // acc: 2, number: 4 -> 6 + numbers.reduce { acc, number -> + if (acc >= number) acc - 1 else acc + number + } shouldBe 6 + + val words: List = listOf("hello", " ", "world") + + words.reduce { acc, word -> acc + word } shouldBe "hello world" + + shouldThrow { + emptyList().reduce { acc, i -> acc + i } + } + } + + "fold - 초기값을 가지고 누적 연산(빈 리스트도 가능)" { + val numbers: List = listOf(1, 2, 3, 4) + val initialValue = 10 + + // acc: 10, number: 1 -> 11 + // acc: 11, number: 2 -> 11 + // acc: 11, number: 3 -> 14 + // acc: 14, number: 4 -> 18 + numbers.fold(initialValue) { acc, number -> + if (number % 2 == 0) acc else acc + number + } + + val words: List = listOf("hello", " ", "world") + val initialString = "Start: " + words.fold(initialString) { acc, s -> acc + s } shouldBe "Start: hello world" + + emptyList().fold(0) { accumulator, element -> accumulator + element } shouldBe 0 + } + + "runningReduce - 각 단계의 누적 결과를 포함하는 리스트 반환 (빈 컬렉션에는 사용 불가)" { // New test case + val numbers: List = listOf(1, 2, 3, 4) + + val runningSum = numbers.runningReduce { acc, number -> acc + number } + runningSum shouldBe listOf(1, 3, 6, 10) // 1, 1 + 2, 1 + 2 + 3, 1 + 2 + 3 + 4 + } + + "runningFold - 초기값과 함께 각 단계의 누적 결과를 포함하는 리스트 반환 (빈 리스트도 가능)" { // New test case + val numbers: List = listOf(1, 2, 3, 4) + + val runningFolded = numbers.runningFold(10) { acc, number -> acc + number } + runningFolded shouldBe listOf(10, 11, 13, 16, 20) + + val words: List = listOf("a", "b", "c") + + val runningFolded2 = words.runningFold("Start: ") { acc, word -> acc + word } + runningFolded2 shouldBe listOf("Start: a", "Start: ab", "Start: abc") + } + } + + "Map Aggregation" - { + val map: Map = mapOf("a" to 1, "b" to 2, "c" to 3, "d" to 4) + + "count - Map의 항목 개수" { + map.count() shouldBe 4 + emptyMap().count() shouldBe 0 + } + + "reduce - Map.Entry의 누적 연산 (빈 Map에는 사용 불가)" { + val map: Map = mapOf("a" to 1, "b" to 2, "c" to 3) + + val reducedEntry: Map.Entry = + map.entries.reduce { acc, entry -> + object : Map.Entry { + override val key: String = acc.key + entry.key + override val value: Int = acc.value + entry.value + } + } + + reducedEntry.key shouldBe "abc" + reducedEntry.value shouldBe 6 + } + + "fold - Map.Entry의 누적 연산 (초기값 사용)" { + val map: Map = mapOf("a" to 1, "b" to 2, "c" to 3) + val initial: Map.Entry = + object : Map.Entry { + override val key: String = "start " + override val value: Int = 100 + } + + val foldedValue: Map.Entry = + map.entries.fold(initial) { acc, entry -> + object : Map.Entry { + override val key: String = acc.key + entry.key + override val value: Int = acc.value + entry.value + } + } + + foldedValue.key shouldBe "start abc" + foldedValue.value shouldBe 106 + } + } +}) From 304000ff270bf92cdf59107b10b15d398d3ad738 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 2 May 2025 20:58:21 +0900 Subject: [PATCH 24/32] refactor(AggregationTest): rename variables --- .../collection/functional/AggregationTest.kt | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt index 4ca014d..ec65937 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt @@ -138,20 +138,20 @@ class AggregationTest : FreeSpec({ "runningReduce - 각 단계의 누적 결과를 포함하는 리스트 반환 (빈 컬렉션에는 사용 불가)" { // New test case val numbers: List = listOf(1, 2, 3, 4) - val runningSum = numbers.runningReduce { acc, number -> acc + number } + val runningSum: List = numbers.runningReduce { acc, number -> acc + number } runningSum shouldBe listOf(1, 3, 6, 10) // 1, 1 + 2, 1 + 2 + 3, 1 + 2 + 3 + 4 } "runningFold - 초기값과 함께 각 단계의 누적 결과를 포함하는 리스트 반환 (빈 리스트도 가능)" { // New test case val numbers: List = listOf(1, 2, 3, 4) - val runningFolded = numbers.runningFold(10) { acc, number -> acc + number } - runningFolded shouldBe listOf(10, 11, 13, 16, 20) + val runningNumbersFolded = numbers.runningFold(10) { acc, number -> acc + number } + runningNumbersFolded shouldBe listOf(10, 11, 13, 16, 20) val words: List = listOf("a", "b", "c") - val runningFolded2 = words.runningFold("Start: ") { acc, word -> acc + word } - runningFolded2 shouldBe listOf("Start: a", "Start: ab", "Start: abc") + val runningWordsFolded = words.runningFold("Start: ") { acc, word -> acc + word } + runningWordsFolded shouldBe listOf("Start: a", "Start: ab", "Start: abc") } } From 50269feccfaba851f074881d32b3b3e6fd94a02c Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 2 May 2025 21:02:22 +0900 Subject: [PATCH 25/32] refactor(AggregationTest): specify some variable's type --- .../learningtest/collection/functional/AggregationTest.kt | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt index ec65937..ab6503b 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt @@ -25,7 +25,7 @@ class AggregationTest : FreeSpec({ numbers.sum() shouldBe 15 } - "sumOf - " { + "sumOf - 컬렉션의 특정 속성을 기준으로 숫자 요소 합계" { val users: List = listOf( User("Alice", 30), @@ -145,12 +145,12 @@ class AggregationTest : FreeSpec({ "runningFold - 초기값과 함께 각 단계의 누적 결과를 포함하는 리스트 반환 (빈 리스트도 가능)" { // New test case val numbers: List = listOf(1, 2, 3, 4) - val runningNumbersFolded = numbers.runningFold(10) { acc, number -> acc + number } + val runningNumbersFolded: List = numbers.runningFold(10) { acc, number -> acc + number } runningNumbersFolded shouldBe listOf(10, 11, 13, 16, 20) val words: List = listOf("a", "b", "c") - val runningWordsFolded = words.runningFold("Start: ") { acc, word -> acc + word } + val runningWordsFolded: List = words.runningFold("Start: ") { acc, word -> acc + word } runningWordsFolded shouldBe listOf("Start: a", "Start: ab", "Start: abc") } } From e9a69cc71206330e4bc9e0e168a4b34544cf437e Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Fri, 2 May 2025 21:07:51 +0900 Subject: [PATCH 26/32] =?UTF-8?q?fix(AggregationTest):=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9D=B4=EB=A6=84=20=EC=88=98=EC=A0=95,?= =?UTF-8?q?=20runningFold=20=EC=8B=A4=ED=8C=A8=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../collection/functional/AggregationTest.kt | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt index ab6503b..223b25d 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/AggregationTest.kt @@ -84,7 +84,7 @@ class AggregationTest : FreeSpec({ emptyList().maxOfOrNull(User::age) shouldBe null } - "maxOfOrNull - 특정 속성 기준으로 컬렉션에서 가장 작은 요소 (없으면 null)" { + "minOfOrNull - 특정 속성 기준으로 컬렉션에서 가장 작은 요소 (없으면 null)" { val users: List = listOf( User("Alice", 30), @@ -145,13 +145,15 @@ class AggregationTest : FreeSpec({ "runningFold - 초기값과 함께 각 단계의 누적 결과를 포함하는 리스트 반환 (빈 리스트도 가능)" { // New test case val numbers: List = listOf(1, 2, 3, 4) - val runningNumbersFolded: List = numbers.runningFold(10) { acc, number -> acc + number } + val runningNumbersFolded: List = + numbers.runningFold(10) { acc, number -> acc + number } runningNumbersFolded shouldBe listOf(10, 11, 13, 16, 20) val words: List = listOf("a", "b", "c") - val runningWordsFolded: List = words.runningFold("Start: ") { acc, word -> acc + word } - runningWordsFolded shouldBe listOf("Start: a", "Start: ab", "Start: abc") + val runningWordsFolded: List = + words.runningFold("Start: ") { acc, word -> acc + word } + runningWordsFolded shouldBe listOf("Start: ", "Start: a", "Start: ab", "Start: abc") } } From 7c8b0cadc7fc8d3b31c7f64847ee237c6d8cc8a9 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Sat, 3 May 2025 00:29:16 +0900 Subject: [PATCH 27/32] =?UTF-8?q?test(GroupingTest):=20=EB=A6=AC=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EA=B7=B8=EB=A3=A8=ED=95=91=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit groupBy, groupingBy.eachCount, groupingBy.fold, groupingBy.reduce, groupingBy.aggregate --- .../collection/functional/GroupingTest.kt | 118 ++++++++++++++++++ 1 file changed, 118 insertions(+) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/GroupingTest.kt diff --git a/app/src/test/java/com/example/learningtest/collection/functional/GroupingTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/GroupingTest.kt new file mode 100644 index 0000000..acb360b --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/GroupingTest.kt @@ -0,0 +1,118 @@ +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class GroupingTest : FreeSpec({ + data class Person(val name: String, val city: String, val age: Int) + + val people = + listOf( + Person("Alice", "London", 30), + Person("Bob", "Paris", 25), + Person("Charlie", "London", 35), + Person("David", "Paris", 30), + Person("Eve", "London", 25), + ) + + "List Grouping" - { + "groupBy - 키를 기준으로 요소들을 그룹화. 리턴 타입은 Map>" { + val groupedByCity: Map> = people.groupBy(Person::city) + + groupedByCity shouldBe + mapOf>( + "London" to + listOf( + Person("Alice", "London", 30), + Person("Charlie", "London", 35), + Person("Eve", "London", 25), + ), + "Paris" to + listOf( + Person("Bob", "Paris", 25), + Person("David", "Paris", 30), + ), + ) + } + + "groupBy - 키와 값 변환을 함께 적용하여 그룹화" { + val groupedCityNames: Map> = + people.groupBy( + keySelector = (Person::city), + valueTransform = { it.name + 1 }, + ) + + groupedCityNames shouldBe + mapOf>( + "London" to listOf("Alice1", "Charlie1", "Eve1"), + "Paris" to listOf("Bob1", "David1"), + ) + } + + "groupingBy - eachCount() 로 각 그룹의 요소 개수를 계산" { + // 도시별 인구 수 map + val groupingByCity: Grouping = people.groupingBy(Person::city) + val cityWithPeopleCount = groupingByCity.eachCount() + + cityWithPeopleCount shouldBe + mapOf( + "London" to 3, + "Paris" to 2, + ) + } + + "groupingBy - fold() 로 각 그룹 내에서 누적 연산 수행" { + // 도시별 인구 나이의 합 map + val groupingByCity: Grouping = people.groupingBy(Person::city) + val cityWithPeopleAgeSum: Map = + groupingByCity.fold(0) { acc, person -> + acc + person.age + } + + cityWithPeopleAgeSum shouldBe + mapOf( + "London" to 30 + 35 + 25, + "Paris" to 25 + 30, + ) + } + + "groupingBy - reduce() 로 각 그룹 내에서 누적 연산 수행 (빈 그룹에는 사용 불가)" { + val animals = listOf("raccoon", "reindeer", "cow", "camel", "giraffe", "goat") + // 모음을 가장 많이 포함한 문자열만 수집 + val groupingByFirstChar: Grouping = animals.groupingBy(String::first) + + val comparator = compareBy { str: String -> str.count { it in "aeiou" } } + + val firstCharWithMaxVowelsAnimal: Map = + groupingByFirstChar.reduce { key: Char, acc: String, animal: String -> + maxOf(acc, animal, comparator) + } + firstCharWithMaxVowelsAnimal shouldBe + mapOf( + 'r' to "reindeer", + 'c' to "camel", + 'g' to "giraffe", + ) + } + + "groupingBy - aggregate() 로 더 복잡한 그룹별 집계 수행" { + val numbers = listOf(3, 4, 5, 6, 7, 8, 9) + + val groupedElements: Map = + numbers + .groupingBy { it % 3 } + .aggregate { key: Int, accumulator: StringBuilder?, element: Int, first -> + if (first) { + StringBuilder().append(key).append("-").append(element) + } else { + accumulator!!.append("-").append(element) + } + }.mapValues { (_, sb) -> sb.toString() } + + groupedElements shouldBe + mapOf( + 0 to "0-3-6-9", + 1 to "1-4-7", + 2 to "2-5-8", + ) + } + } +}) From 19bec611313f720afb6df833ab94f13efe30ea7b Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Sat, 3 May 2025 01:21:57 +0900 Subject: [PATCH 28/32] =?UTF-8?q?test(TransformationTest):=20List=20?= =?UTF-8?q?=EB=B3=80=ED=98=95=20=EB=A9=94=EC=84=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit distinct, distinctBy, zip, zipWithNext, windowed, chunked, partition, unzip --- .../functional/TransformationTest.kt | 120 ++++++++++++++++++ 1 file changed, 120 insertions(+) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt diff --git a/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt new file mode 100644 index 0000000..d042aec --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt @@ -0,0 +1,120 @@ +package com.example.learningtest.collection.functional + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class TransformationTest : FreeSpec({ + "List Transformation" - { + "distinct - 중복 제거" { + val list: List = listOf(1, 2, 3, 1, 2, 4, 5, 4) + list.distinct() shouldBe listOf(1, 2, 3, 4, 5) + } + + "distinctBy - 특정 조건에 따라 중복 제거" { + data class Person(val name: String, val age: Int) + + val people: List = + listOf( + Person("Alice", 30), + Person("Bob", 25), + Person("Alice", 35), + Person("Charlie", 30), + Person("Bob", 40), + ) + + people.distinctBy(Person::name) shouldBe + listOf( + Person("Alice", 30), + Person("Bob", 25), + Person("Charlie", 30), + ) + + people.distinctBy(Person::age) shouldBe + listOf( + Person("Alice", 30), + Person("Bob", 25), + Person("Alice", 35), + Person("Bob", 40), + ) + } + + "zip - 두 컬렉션을 Pair 로 묶기(크기가 작은 컬렉션에 맞춰서 Pair 가 생성됨)" { + val numbers: List = listOf(1, 2, 3) + val letters: List = listOf("a", "b", "c", "d") + + val zipped: List> = numbers.zip(letters) + zipped shouldBe listOf(1 to "a", 2 to "b", 3 to "c") + } + + "zipWithNext - 인접한 요소끼리 Pair 로 묶기" { + val numbers: List = listOf(1, 2, 3) + + val adjacentPairs: List> = numbers.zipWithNext() + adjacentPairs shouldBe listOf(1 to 2, 2 to 3) + } + + "windowed - 슬라이딩 윈도우를 만들고 각 윈도우에 대한 리스트 반환" { + val numbers = listOf(1, 2, 3, 4, 5, 6) + + // windowed(size = 3, step = 1, partialWindows = false) + numbers.windowed(3) shouldBe + listOf( + listOf(1, 2, 3), + listOf(2, 3, 4), + listOf(3, 4, 5), + listOf(4, 5, 6), + ) + + // windowed(size = 3, step = 2, partialWindows = false) + numbers.windowed(3, step = 2) shouldBe + listOf( + listOf(1, 2, 3), + listOf(3, 4, 5), + ) + + // windowed(size = 3, step = 2, partialWindows = true) + numbers.windowed(3, step = 2, partialWindows = true) shouldBe + listOf( + listOf(1, 2, 3), + listOf(3, 4, 5), + listOf(5, 6), + ) + + // (1,2,3), (2,3,4), (3,4,5), (4,5,6) => 6, 9, 12, 15 + numbers.windowed(3) { it.sum() } shouldBe listOf(6, 9, 12, 15) + } + + "chunked - 컬렉션을 지정된 크기의 덩어리로 나누기" { + val numbers: List = listOf(1, 2, 3, 4, 5, 6, 7) + + numbers.chunked(3) shouldBe + listOf>( + listOf(1, 2, 3), + listOf(4, 5, 6), + listOf(7), + ) + numbers.windowed(size = 3, step = 3, partialWindows = true) shouldBe numbers.chunked(3) + + numbers.chunked(size = 3, transform = { it.sum() }) shouldBe listOf(6, 15, 7) + numbers.chunked(3) { it.sum() } shouldBe listOf(6, 15, 7) + } + + "partition - 조건을 만족하는 요소와 만족하지 않는 요소로 분리" { + val numbers: List = listOf(1, 2, 3, 4, 5, 6) + + val (even, odd) = numbers.partition { it % 2 == 0 } + + even shouldBe listOf(2, 4, 6) + odd shouldBe listOf(1, 3, 5) + } + + "unzip - Pair를 요소로 갖는 리스트를 두 개의 리스트로 분리" { + val pairs = listOf("a" to 1, "b" to 2, "c" to 3) + + val (letters, numbers) = pairs.unzip() + + letters shouldBe listOf("a", "b", "c") + numbers shouldBe listOf(1, 2, 3) + } + } +}) From f5a9d7d44b46ba67378268cc55d6521d9aff5e60 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Sat, 3 May 2025 01:22:21 +0900 Subject: [PATCH 29/32] =?UTF-8?q?test(TransformationTest):=20Set=20?= =?UTF-8?q?=EB=B3=80=ED=98=95=20=EB=A9=94=EC=84=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit zip, zipWithNext, windowed, chunked, partition, unzip --- .../functional/TransformationTest.kt | 37 +++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt index d042aec..bbf3cf1 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt @@ -117,4 +117,41 @@ class TransformationTest : FreeSpec({ numbers shouldBe listOf(1, 2, 3) } } + + "Set Transformation" - { + val set: Set = setOf(1, 2, 3, 4) + + "zip - 두 Set을 Pair로 묶기" { + val set1: Set = setOf("a", "b", "c") + val set2: Set = setOf(1, 2, 3, 4) + + set1.zip(set2) shouldBe listOf("a" to 1, "b" to 2, "c" to 3) + } + + "zipWithNext - 인접한 요소끼리 Pair로 묶기" { + set.zipWithNext() shouldBe listOf(1 to 2, 2 to 3, 3 to 4) + } + + "windowed - 슬라이딩 윈도우를 만들고 각 윈도우에 대한 리스트 반환" { + set.windowed(2) shouldBe listOf(listOf(1, 2), listOf(2, 3), listOf(3, 4)) + } + + "chunked - 컬렉션을 지정된 크기의 덩어리로 나누기" { + set.chunked(3) shouldBe listOf(listOf(1, 2, 3), listOf(4)) + } + + "partition - 조건을 만족하는 요소와 만족하지 않는 요소로 분리" { + val sets: Set = setOf(1, 2, 3, 4, 5, 6) + val (even: List, odd: List) = sets.partition { it % 2 == 0 } + even shouldBe listOf(2, 4, 6) + odd shouldBe listOf(1, 3, 5) + } + + "unzip - Pair를 요소로 갖는 Set을 두 개의 Set로 분리" { + val sets: Set> = setOf(1 to 2, 3 to 4, 5 to 6) + val (set1: List, set2: List) = sets.unzip() + set1 shouldBe listOf(1, 3, 5) + set2 shouldBe listOf(2, 4, 6) + } + } }) From 3a74c0e18c219f8f499477f6267096fbb4110b21 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Sat, 3 May 2025 01:22:50 +0900 Subject: [PATCH 30/32] =?UTF-8?q?test(TransformationTest):=20Map=20transfo?= =?UTF-8?q?rmation=20=EB=A9=94=EC=84=9C=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit mapKeys, mapValues, entries, keys, values --- .../functional/TransformationTest.kt | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt index bbf3cf1..6a57ef5 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt @@ -154,4 +154,29 @@ class TransformationTest : FreeSpec({ set2 shouldBe listOf(2, 4, 6) } } + + "Map Transformation" - { + val map: Map = mapOf("a" to 1, "b" to 2, "c" to 3) + + "mapKeys - Key에 변환 함수 적용하여 새로운 Map 생성" { + val transformedKeysMap = map.mapKeys { (key, _) -> key.uppercase() } + transformedKeysMap shouldBe mapOf("A" to 1, "B" to 2, "C" to 3) + } + + "mapValues - Value에 변환 함수 적용하여 새로운 Map 생성" { + val transformedValuesMap = map.mapValues { (_, value) -> value * 10 } + transformedValuesMap shouldBe mapOf("a" to 10, "b" to 20, "c" to 30) + } + + "entries, keys, values - Map의 구성 요소를 Collection으로 변환" { + map.entries shouldBe + setOf( + mapOf("a" to 1).entries.first(), + mapOf("b" to 2).entries.first(), + mapOf("c" to 3).entries.first(), + ) + map.keys shouldBe setOf("a", "b", "c") + map.values shouldBe listOf(1, 2, 3) + } + } }) From 84c78529d9d82efe58450bde964dec89e4e9b446 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Mon, 5 May 2025 15:58:59 +0900 Subject: [PATCH 31/32] =?UTF-8?q?test(CombinationTest):=20=EC=BB=AC?= =?UTF-8?q?=EB=A0=89=EC=85=98=20=EC=A1=B0=ED=95=A9=20=EC=97=B0=EC=82=B0?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=9C=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 리스트 조합 연산(+, -, union, intersect, subtract) 맵 조합 연산(+, merge, entries/keys/values를 사용한 union, intersect, subtract --- .../collection/functional/CombinationTest.kt | 121 ++++++++++++++++++ .../functional/TransformationTest.kt | 10 +- 2 files changed, 126 insertions(+), 5 deletions(-) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/CombinationTest.kt diff --git a/app/src/test/java/com/example/learningtest/collection/functional/CombinationTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/CombinationTest.kt new file mode 100644 index 0000000..ae5b306 --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/CombinationTest.kt @@ -0,0 +1,121 @@ +package com.example.learningtest.collection.functional + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class CombinationTest : FreeSpec({ + "List Combination" - { + "plus (+) - 두 컬렉션을 이어붙이거나 요소 하나를 추가" { + val list1 = listOf(1, 3) + val list2 = listOf(2, 4) + + list1 + list2 shouldBe listOf(1, 3, 2, 4) + + list1 + 2 shouldBe listOf(1, 3, 2) +// (2 + list1) 처럼 (원소 + 리스트)는 불가능 + } + + "minus (-) - 다른 컬렉션이나 특정 요소를 제거" { + val list1 = listOf(1, 2, 3, 2) + val list2 = listOf(1, 4) + + list1 - list2 shouldBe listOf(2, 3, 2) + list1 - 1 shouldBe listOf(2, 3, 2) + } + + "union - 두 컬렉션의 합집합 (중복 제거)" { + val list1 = listOf(1, 2, 3) + val list2 = listOf(3, 4, 5) + + (list1 union list2) shouldBe setOf(1, 2, 3, 4, 5) + } + + "intersect - 두 컬렉션의 교집합 (중복 제거)" { + val list1 = listOf(1, 2, 3, 4, 2) + val list2 = listOf(3, 4, 5, 4) + + (list1 intersect list2) shouldBe setOf(3, 4) + } + + "subtract - 첫 번째 컬렉션에서 두 번째 컬렉션의 요소를 제외한 차집합 (중복 제거)" { + val list1 = listOf(1, 2, 3, 4, 2) + val list2 = listOf(3, 4, 5, 4) + + (list1 subtract list2) shouldBe setOf(1, 2) // list1 에서의 중복도 제거됨. + } + } + + "Map Combination" - { + val map1 = mapOf("a" to 1, "b" to 2) + val map2 = mapOf("b" to 20, "c" to 30) + + "plus (+) " { + map1 + map2 shouldBe mapOf("a" to 1, "b" to 20, "c" to 30) + } + + "merge - 키 존재 시 값 결합, 키 부재 시 추가" { + val mutableMap = mutableMapOf("a" to 1, "b" to 2) + + mutableMap.merge("c", 10) { oldValue, newValue -> + oldValue + newValue + } + + mutableMap shouldBe mapOf("a" to 1, "b" to 2, "c" to 10) + + mutableMap.merge("a", 10) { oldValue, newValue -> + oldValue + newValue + } + + mutableMap shouldBe mapOf("a" to 11, "b" to 2, "c" to 10) + } + + "entries, keys, values 를 이용한 union, intersect, subtract (기존 테스트 유지)" - { + "entries 를 이용한 union (결과 Set)" { + val unionResult: Set> = map1.entries union map2.entries + unionResult shouldBe + setOf( + mapOf("a" to 1).entries.first(), + mapOf("b" to 2).entries.first(), + mapOf("b" to 20).entries.first(), + mapOf("c" to 30).entries.first(), + ) + } + + "entries 를 이용한 intersect (결과 Set)" { + val intersectResult: Set> = + map1.entries intersect map2.entries + + intersectResult shouldBe emptySet() + + val map3 = mapOf("a" to 1, "b" to 2) + val map4 = mapOf("a" to 1, "b" to 4) + + val intersectResult2: Set> = + map3.entries intersect map4.entries + + intersectResult2 shouldBe setOf(mapOf("a" to 1).entries.first()) + } + + "keys 를 이용한 union (결과 Set)" { + val unionResult: Set = map1.keys union map2.keys + unionResult shouldBe setOf("a", "b", "c") + } + + "keys 를 이용한 intersect (결과 Set)" { + val intersectResult: Set = map1.keys intersect map2.keys + intersectResult shouldBe setOf("b") + } + + "keys 를 이용한 subtract (결과 Set)" { + val subtractResult: Set = map1.keys subtract map2.keys + subtractResult shouldBe setOf("a") + } + + "values 를 이용한 조합 union (결과 Set)" { + val unionResult: Set = map1.values union map2.values + + unionResult shouldBe setOf(1, 2, 20, 30) + } + } + } +}) diff --git a/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt index 6a57ef5..34af80b 100644 --- a/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt +++ b/app/src/test/java/com/example/learningtest/collection/functional/TransformationTest.kt @@ -170,11 +170,11 @@ class TransformationTest : FreeSpec({ "entries, keys, values - Map의 구성 요소를 Collection으로 변환" { map.entries shouldBe - setOf( - mapOf("a" to 1).entries.first(), - mapOf("b" to 2).entries.first(), - mapOf("c" to 3).entries.first(), - ) + setOf( + mapOf("a" to 1).entries.first(), + mapOf("b" to 2).entries.first(), + mapOf("c" to 3).entries.first(), + ) map.keys shouldBe setOf("a", "b", "c") map.values shouldBe listOf(1, 2, 3) } From b4ae358a842e260e74bd63f4ffbfa2a6a2426a92 Mon Sep 17 00:00:00 2001 From: sh1mj1 <7wlgns@gmail.com> Date: Mon, 5 May 2025 16:12:27 +0900 Subject: [PATCH 32/32] =?UTF-8?q?test(ControlFlowExtensionTest):=20Kotlin?= =?UTF-8?q?=20=EC=8A=A4=EC=BD=94=ED=94=84=20=ED=95=A8=EC=88=98=20=EB=B0=8F?= =?UTF-8?q?=20=EC=A1=B0=EA=B1=B4=EB=B6=80=20=ED=95=A8=EC=88=98=20=ED=95=99?= =?UTF-8?q?=EC=8A=B5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 스코프 함수에 대한 테스트: let, run, with, apply, also. 조건부 함수에 대한 테스트: takeIf, takeUnless. --- .../functional/ControlFlowExtensionTest.kt | 129 ++++++++++++++++++ 1 file changed, 129 insertions(+) create mode 100644 app/src/test/java/com/example/learningtest/collection/functional/ControlFlowExtensionTest.kt diff --git a/app/src/test/java/com/example/learningtest/collection/functional/ControlFlowExtensionTest.kt b/app/src/test/java/com/example/learningtest/collection/functional/ControlFlowExtensionTest.kt new file mode 100644 index 0000000..84d6027 --- /dev/null +++ b/app/src/test/java/com/example/learningtest/collection/functional/ControlFlowExtensionTest.kt @@ -0,0 +1,129 @@ +package com.example.learningtest.collection.functional + +import io.kotest.core.spec.style.FreeSpec +import io.kotest.matchers.shouldBe + +class ControlFlowExtensionTest : FreeSpec({ + + data class Person(var name: String, var age: Int, var city: String) + + "Scope Functions (스코프 함수) - 객체 컨텍스트에서 코드 블록 실행" - { + + "let - 객체를 it 으로 참조, 람다 결과 반환" { + val person = Person("Alice", 20, "Amsterdam") + val result: String = + person.let { + it.age += 1 + "Name: ${it.name}, Age: ${it.age}" + } + result shouldBe "Name: Alice, Age: 21" + person.age shouldBe 21 + } + + "run - 객체를 this로 참조, 람다 결과 반환 (Non-extension run은 객체 없이 사용)" { + val person = Person("Bob", 25, "London") + val result: String = + person.run { + this.city = "Paris" + age = 26 + "Age: $age, City: $city" + } + result shouldBe "Age: 26, City: Paris" + person.city shouldBe "Paris" + } + + "with - 객체를 첫 번째 인자로, 객체를 this로 참조, 람다 결과 반환 (확장 함수 아님)" { + val person = Person("Charlie", 30, "New York") + val result: String = + with(person) { + age += 5 + "New Age: $age" + } + result shouldBe "New Age: 35" + person.age shouldBe 35 + } + + "apply - 객체를 this로 참조, 객체 자체 반환 (주로 객체 초기화/설정)" { + val person: Person = + Person("David", 35, "Tokyo").apply { + age += 2 + city = "Seoul" + } + + person.apply { + age += 2 + } + + person.age shouldBe 39 + person.city shouldBe "Seoul" + } + + "also - 객체를 it 으로 참조, 객체 자체 반환 (주로 부수 효과 로깅 등)" { + val numbers = mutableListOf(1, 2, 3) + val resultList: MutableList = + numbers.also { + it.add(4) + println("List after adding element: $it") + } + resultList shouldBe listOf(1, 2, 3, 4) + numbers shouldBe listOf(1, 2, 3, 4) + } + } + + "Conditional Execution Functions (조건부 실행 함수) - 조건에 따라 객체 반환" - { + + "takeIf - 조건이 true 이면 객체 자체 반환, false 이면 null 반환" { + val number = 10 + val evenNumber: Int? = number.takeIf { it % 2 == 0 } + val oddNumber: Int? = number.takeIf { it % 2 != 0 } + + evenNumber shouldBe 10 + oddNumber shouldBe null + } + + "takeUnless - 조건이 false 이면 객체 자체 반환, true 이면 null 반환" { + val number = 10 + val notOddNumber = number.takeUnless { it % 2 != 0 } + val notEvenNumber = number.takeUnless { it % 2 == 0 } + + notOddNumber shouldBe 10 + notEvenNumber shouldBe null + } + } + + "Combining Scope and Conditional Functions - 스코프 함수와 조건부 함수 조합" - { + "takeIf 와 let 을 함께 사용하여 조건 만족 시에만 코드 실행" { + val person: Person? = Person("Grace", 22, "Sydney") + + val result1 = + person?.takeIf { it.age >= 18 }?.let { + "Adult: ${it.name}" + } + result1 shouldBe "Adult: Grace" + + val anotherPerson: Person? = Person("Heidi", 17, "Vienna") + val result2 = + anotherPerson?.takeIf { it.age >= 18 }?.let { + "Adult: ${it.name}" + } + result2 shouldBe null // 조건 불만족 시 takeIf가 null 반환하고 let 실행 안됨 + } + + "takeUnless 와 run 을 함께 사용하여 조건 불만족 시에만 코드 실행" { + val person: Person? = Person("Ivy", 28, "Moscow") + + val result1 = + person?.takeUnless { it.age < 18 }?.run { + "Not a minor: ${this.name}" + } + result1 shouldBe "Not a minor: Ivy" + + val anotherPerson: Person? = Person("Jack", 15, "Cairo") + val result2 = + anotherPerson?.takeUnless { it.age < 18 }?.run { + "Not a minor: ${this.name}" + } + result2 shouldBe null // 조건 만족 시 takeUnless가 null 반환하고 run 실행 안됨 + } + } +})