본문 바로가기

코틀린/코틀린 기본 문법

코틀린 Collection 표준 라이브러리 사전

오랫만에 쓰는 글이다 사실 계속 글을썻는데 뭔가 썻던글들이 좀더 많이 공부해야하는 것이여서 글들이 죄다 미완성 상태로 남아있다.ㅜㅜ 언제 마무리하냐 어쨋든 최근 또 공부해야한다고 느낀부분에 대해서 정리하고 넘어가려한다.

 

최근 우테코 프리코스,이펙티브 코틀린 읽기 등등을 하며 코틀린에 진하게 노출될 상황이 많았다. 특히 이펙티브 코틀린 책이 너무 좋다. 정말 이펙티브 코틀린을 빠르게 읽어보길 권한다. 어쨋든 코틀린의 적절한 사용을 강조하는 책들을 본다면 두가지를 많이 강조한다. 

1. 쓰잘데기없이 기능구현 하지말고 표준라이브러리(stdlib)에 있는거 가져다 써라

2. 컬렉션을 함수형으로 잘 다룬다면 예쁘고 가독성 좋게 만들수있다.

 

-> 결론적으로 함수에 넣어서 계속되는 임시변수에 담을것이 아니라 함수를 쭉타는 과정을 통해 원하는 가공을 하는것을 추구한다.

 

그런 의미에서 3가지 자료를 참고하여 컬렉션에서 자주쓰이고 알아둬야할 기능들을 정리하려고한다.

참고한자료는 이러하다.

1. 코틀린 공식문서

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.collections/

 

kotlin.collections - Kotlin Programming Language

 

kotlinlang.org

2.코틀린 쿡북

코틀린 쿡북 좋다던데 이부분에서는 별게 없는거같다 오히려 별로

3. 코틀린인 액션

사실 여기서도 그나마 상세하긴 한데 구글링한결과 별의별게 다있는데 좀 한정적이다.

 

어쩃든 그래서 코틀린 기본서들에서 찾은 정말 자주쓰이는 예시들과 코틀린 공식문서에 나온 표준라이브러리 중 사용하기 좋은것들을 쭉 정리하여 사전형식으로 찾아서 사용할수있도록 글을 적어볼 예정이다.(앞으로 계속 시간날때마다 업데이트 할 예정이다.)

 

근데 컬렉션을 함수형 스타일로 잘다룬다에서 함수형이 무엇일까? 그것부터 알아보자


- 함수형 프로그래밍

일단 함수형 프로그래밍에 대해서 공부하고 설명하기 시작하면 한도 끝도 없고 말도안되게 심오할테니 일단 간단한 정의와 우리가 중점적으로 관찰해야할것들만 살펴보고 지나가겠다.

 

정의 : 함수형 프로그래밍은 하나의 프로그래밍 패러다임으로 정의되는 일련의 코딩 접근 방식이며, 자료처리를 수학적 함수의 계산으로 취급하고 상태와 가변 데이터를 멀리하는 프로그래밍 패러다임을 의미한다.

 

유명한 책인 클린 코드(Clean Code)의 저자 Robert C.Martin은 함수형 프로그래밍을 대입문이 없는 프로그래밍이라고 정의하였다.

Functional Programming is programming without assignment satements
- Rober C.Martin -

 

정의와 내가 이해한 부분에서 가장 중요한 부분을 뽑아보았다. 한마디로 가변성을 피하고 즉 상태값을 가지지 않으려하고(var 없애버리자!!)

그리고 어떤 값을 얻기위해 불변값을 함수에 순차적으로 넣어서 쭉 처리된 값을 받아 사용하는 형태로 대입문을 없애버리는 형태로 코딩하는것이다.

 

추가적으로 더 알아보고싶다면 밑의 링크의 글을 참고해보자.

 

https://jongminfire.dev/%ED%95%A8%EC%88%98%ED%98%95-%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D%EC%9D%B4%EB%9E%80

 

함수형 프로그래밍이란?

jongminfire.dev

https://mangkyu.tistory.com/111

 

[프로그래밍] 함수형 프로그래밍(Functional Programming) 이란?

1. 함수형 프로그래밍(Functional Programming)에 대한 이해 [ 프로그래밍 패러다임(Programming Paradigm) ] 프로그래밍 패러다임(Programming Paradigm)은 프로그래머에게 프로그래밍의 관점을 갖게 하고 코드를

mangkyu.tistory.com


그래서 딱봐도 다루기 어려운 상태값을 줄일수 있고 컬렉션 자체를 쉽고 예쁘게(가독성 좋게) 다룰수 있으니 충분히 의미가 있다고 생각한다.

 

이제 시작해보자.


참고사항(전체적으로 쓰일수있는 부분)

이렇게 람다를 사용하다보면 멤버 참조하는것을 많이보는데 :: 로 자꾸 이상하게 넘긴다.

이 블로그에서 따온내용인데 한마디로 그냥 함수에 넘겨주는 매개변수가 하나면 :: 를 통해서 간지나게 넣어줄수있는것이다 

https://0391kjy.tistory.com/35

 

코틀린(Kotlin) - 람다 식과 멤버 참조

코틀린의 람다는 자바8의 람다식과 개념이 매우 비슷합니다. 람다를 쉽게 설명하자면, 값처럼 여기저기 전달할 수 있는 동작의 모음(?)이라고 할 수 있습니다. 기본적으로 람다식은 자바8부터 사

0391kjy.tistory.com

멤버 참조

람다를 넘길 때 프로퍼티 메소드를 단 하나만 호출하는 함수 값을 갖고 있다면, 간단하게 이중 콜론(::) 으로 사용할 수 있습니다.

 

val getAge = Person::age

 

::를 사용하는 식을 멤버 참조라고 부릅니다.

 

::를 사용하여 표현하는 여러 가지 방법에 대해 알아보도록 하겠습니다.

 

 

클래스의 멤버 표현

Person::age //{person: Person -> person.age}와 동일

표현식) 클래스::멤버

 

 

최상위 함수의 표현

fun showName() = println("코틀린")

>>> run(::showName)

표현식) ::최상위 함수

 

 

함수

val action = {person: Person, message: String -> sendMail(person, message)}
val action = ::sendMail

표현식) ::함수

 

 

생성자

data class Person(val name: String, val age: Int)

>>> val createPerson = ::Person
>>> val p = createPerson("코틀린", 30)
>>> println(p)
Person(name=코틀린, age=30)

표현식) ::클래스

 

 

확장 함수

fun Person.isAdult() = age >= 20
val predicate = Person::isAdult

표현식) 클래스::확장 함수


1-1.fold

컬렉션을 하나의 값으로 축약시키는 용도로 사용한다 -> 각 원소들에다가 뭔짓을해서(ex.다 더함) 하나의 값을 배출하는것

 

inline fun <T, R> Array<out T>.fold(
    initial: R, 
    operation: (acc: R, T) -> R
): R

fold 함수의 문법인 다음과 같은데 

initial 로 초기값을 받고 operation로 입력되는 함수를 매 원소마다 실행하는데  operation의 인자를 살펴보면 acc 는 accumulator(누적자)의 약자로 계산된 내용들이 쌓이는 값이고 두번째 인자는 각 원소를 뜻한다.

 

즉 첫번쨰 원소를 받아고 collection 의 각각의 원소들을 이용 람다로 입력해준 operation 의 행위를 하는것을 뜻한다.

 

사용예시

fun sum(vararg nums: Int) =
	nums.fold(0) { acc, n -> acc + n }

 이런식으로 사용하면된다.

 

1-2.foldIndexed

inline fun <T, R> Array<out T>.foldIndexed(
    initial: R,
    operation: (index: Int, acc: R, T) -> R
): R

자 살펴보면 fold 와 같은데 각 원소의 index도 인자로 넘어오는걸 볼수있다. 적절히 사용하면 될것같다.

 

1-3. foldRight

fun <T, R> Array<out T>.foldRight(
    initial: R,
    operation: (T, acc: R) -> R
): R

fold 와 같은데 오른쪽 즉 마지막 index 부터 역순으로 operation을 적용하는 것이다.

 

 

1-4. foldRightIndexed

자 이쯤되면 이름만 봐도 알꺼다 오른쪽에서 시작하는 인덱스까지 주는 fold이다.
 

1-5.foldTo(잘 안쓰이는거 같음??)

그룹화된 pair를 받아서 조건에 맞는 map을 반환하는것이다

inline fun <T, K, R, M : MutableMap<in K, R>> Grouping<T, K>.foldTo(
    destination: M,
    initialValueSelector: (key: K, element: T) -> R,
    operation: (key: K, accumulator: R, element: T) -> R
): M

map 형태로 결과 값을 생상해내는것인데 예시를 보면서 이해해보자

 

val fruits = listOf("cherry", "blueberry", "citrus", "apple", "apricot", "banana", "coconut")

val evenFruits = fruits.groupingBy { it.first() }
    .foldTo(mutableMapOf(), { key, _: String -> key to mutableListOf<String>() },
            { _, accumulator, element ->
                if (element.length % 2 == 0) accumulator.second.add(element)
                accumulator 
            })

val sorted = evenFruits.values.sortedBy { it.first }
println(sorted) // [(a, []), (b, [banana]), (c, [cherry, citrus])]

println(evenFruits) // {c=(c, [cherry, citrus]), b=(b, [banana]), a=(a, [])}

evenFruits.clear() // evenFruits is a mutable map

보면 일단 groupingBy의 결과물로 it.first를 first 로 원래 값 즉 cherry 같은것들이 second로 하는 pair 값들이 쭉나온다.

ex) ("c","cherry") 

이것들을 foldTo 를 적용하면 

첫번째 인자로 결과값을 담을 map을 넣어주고 

두번쨰 인자로 accumulator의 초기값을 만들어줄 initialValueSelector를 넣어준다

initialValueSelector는 accumulator 의 값이 없을떄만 호출되고 만약 기존에 누적값이 있다면 기존 누적값을 사용한다

initialValueSelector의 인자는 key와 String 을 받는데 이걸로 누적값의 초기값을 만들어주는 것이다.

그리고 operation 을 통해서 각 key 값에대한 누적 계산을 통해서 key에 맞는 value값을 누적으로 계산한다.

즉 원하는 조건들을 모아서 map을 생성할때 사용할수 있을것이다.

 

inline fun <T, K, R, M : MutableMap<in K, R>> Grouping<T, K>.foldTo(
    destination: M,
    initialValue: R,
    operation: (accumulator: R, element: T) -> R
): M

이런 형식도 있는데

 

initilaValue를 함수를 통해 생성하는것이 아닌 일정한 값으로 지정해주는것이다. 

 

val fruits = listOf("cherry", "blueberry", "citrus", "apple", "apricot", "banana", "coconut")

// collect only even length Strings
val evenFruits = fruits.groupingBy { it.first() }
    .foldTo(mutableMapOf(), emptyList<String>()) { acc, e -> if (e.length % 2 == 0) acc + e else acc }

println(evenFruits) // {c=[cherry, citrus], b=[banana], a=[]}

evenFruits.clear() // evenFruits is a mutable map

예시의 사용법은 이렇다.

 

구글링해봐도 거의 안나오는걸 보면 잘안쓰는거 같은데 사용처를 굳이 만들자면

map을 분해해서 다시 원하는 형태로 정리해서 뽑아낼때 사용하면 될것같다

 

2-1.  reduce

fold 와 비슷한건데 초기값을 받지않고 컬렉션의 첫번쨰 원소로 초기값을 대체한다.

inline fun <S, T : S> Array<out T>.reduce(
    operation: (acc: S, T) -> S
): S

예시 

val strings = listOf("a", "b", "c", "d")
println(strings.reduce { acc, string -> acc + string }) // abcd

초기값인 acc가 컬렉션의 첫번째 원소이고 나머지 원소가 두번쨰 인자로 들어간다.

 

Group 즉 pair의 값들을 쭉 받아서 처리할수도 있다 -> Map 을 반환한다.

inline fun <S, T : S, K> Grouping<T, K>.reduce(
    operation: (key: K, accumulator: S, element: T) -> S
): Map<K, S>
val animals = listOf("raccoon", "reindeer", "cow", "camel", "giraffe", "goat")

// grouping by first char and collect only max of contains vowels
val compareByVowelCount = compareBy { s: String -> s.count { it in "aeiou" } }

val maxVowels = animals.groupingBy { it.first() }.reduce { _, a, b -> maxOf(a, b, compareByVowelCount) }

println(maxVowels) // {r=reindeer, c=camel, g=giraffe}

예시인데 딱히 자주 쓸것같지는 않다. 인자로 키값도 들어가서 처리하고 map 으로 반환한다.

2-2.  reduceIndexed

각원소의 index 도 받아서 같이 처리할수있다. 

inline fun <S, T : S> Iterable<T>.reduceIndexed(
    operation: (index: Int, acc: S, T) -> S
): S

예시

val strings = listOf("a", "b", "c", "d")
println(strings.reduceIndexed { index, acc, string -> acc + string + index }) // ab1c2d3

초기값의 index는 받을수가 없다.

2-3.  reduceIndexedOrNull

reduceIndexed와 같은데 emptyList에다가 적용하면 null 값을 뱉는다

inline fun <S, T : S> Iterable<T>.reduceIndexedOrNull(
    operation: (index: Int, acc: S, T) -> S
): S?

예시

val strings = listOf("a", "b", "c", "d")
println(strings.reduceOrNull { acc, string -> acc + string }) // abcd
println(strings.reduceIndexedOrNull { index, acc, string -> acc + string + index }) // ab1c2d3

println(emptyList<String>().reduceOrNull { _, _ -> "" }) // null

2-4 . reduceOrNull

reduce와 같은데 emptyList에다가 적용하면 null 값을 뱉는다

2-5.  reduceRight

reduce 인데 맨끝 원소부터 시작하는것이다

2-6. reduceRightIndexed

이름으로 파악할수있을것이다

2-7.  reduceRightIndexedOrNull

이름으로 파악할수있을것이다

2-8. reduceRightOrNull

이름으로 파악할수있을것이다

2-9. reduceTo

foldTo 와 비슷하지만 초기값을 지정해주는것이 없다

inline fun <S, T : S, K, M : MutableMap<in K, S>> Grouping<T, K>.reduceTo(
    destination: M,
    operation: (key: K, accumulator: S, element: T) -> S
): M

예시

val animals = listOf("raccoon", "reindeer", "cow", "camel", "giraffe", "goat")
val maxVowels = mutableMapOf<Char, String>()

// grouping by first char and collect only max of contains vowels
val compareByVowelCount = compareBy { s: String -> s.count { it in "aeiou" } }

animals.groupingBy { it.first() }.reduceTo(maxVowels) { _, a, b -> maxOf(a, b, compareByVowelCount) }

println(maxVowels) // {r=reindeer, c=camel, g=giraffe}

val moreAnimals = listOf("capybara", "rat")
moreAnimals.groupingBy { it.first() }.reduceTo(maxVowels) { _, a, b -> maxOf(a, b, compareByVowelCount) }

println(maxVowels) // {r=reindeer, c=capybara, g=giraffe}

첫번쨰 인자로 결과값이 담길 map 을 넣어준다

그리고 두번째 operation으로 각 원소들을 돌며 operation을 호출하는데 인자로 키값,초기값,원소를 받아서 무언가를 하고 반환하는 값을 각 결과값의 원소에 담기게 된다.

 

3-1. fliter(진짜 미친듯이 많이씀)

map 과 연쇄적으로 사용한다면 컬렉션 관련 왠만한 처리를 다할수있다.

filter은 람다로 넘겨주는 조건식에 원소들을 대입했을떄 참값이 나오는것들만 모아서 리스트를 만들어 반환한다.

 

inline fun <T> Array<out T>.filter(
    predicate: (T) -> Boolean
): List<T>

예시

val numbers: List<Int> = listOf(1, 2, 3, 4, 5, 6, 7)
val evenNumbers = numbers.filter { it % 2 == 0 }

println(evenNumbers) // [2, 4, 6]

이런식으로 조건식에 맞는 원솓들만 골라낼수있는데

 

여기서 한가지 주의점이 filter 내에 계산들이 동일한 행위를 하는데 무거운 연산이있다면 외부로 빼주는것이 좋다.

-> 표기 자체는 간단해 보이지만 매번 같은연산을 시행하기에 무거운연산이 아주많이 반복될수있다.

ex)

people.filter { it.age == people.maxBy(Person::age)!!.age)
// 이러면 maxBy연산이 중복되어서 계속 일어난다.

val maxAge = people.maxBy(Person::age)!!.age
people.filter { it.age == maxAge } 
//이렇게 사용하도록 하자

3-2. filterIndexed

inline fun <T> Array<out T>.filterIndexed(
    predicate: (index: Int, T) -> Boolean
): List<T>
val numbers: List<Int> = listOf(0, 1, 2, 3, 4, 8, 6)
val numbersOnSameIndexAsValue = numbers.filterIndexed { index, i -> index == i }

println(numbersOnSameIndexAsValue) // [0, 1, 2, 3, 4, 6]

이제 이름만 봐도 알겠지만 조건식에 index값이 넘어온다.

 

3-3.filterIndexedTo

inline fun <T, C : MutableCollection<in T>> Array<out T>.filterIndexedTo(
    destination: C,
    predicate: (index: Int, T) -> Boolean
): C

예시

val numbers: List<Int> = listOf(0, 1, 2, 3, 4, 8, 6)
val numbersOnSameIndexAsValue = mutableListOf<Int>()

println(numbersOnSameIndexAsValue) // []

numbers.filterIndexedTo(numbersOnSameIndexAsValue) { index, i -> index == i }

println(numbersOnSameIndexAsValue) // [0, 1, 2, 3, 4, 6]

filter 에 적용된값을 첫번째 인자로 넣어주는 목표지점에 업데이트 할수있다 -> 이럴꺼면 딱히 함수형으로 안해도될꺼같아 안쓸꺼같다.

 

3-4. filterIsInstanceTo

이건 뭔가 필요에의해서 쓸거같기도한데 인스턴스를 어떤 자료형인지 구분해서 그 인스턴스들만 모은 자료형을 반환한다.

fun <reified R, C : MutableCollection<in R>> Array<*>.filterIsInstanceTo(
    destination: C
): C
(source)
fun <reified R, C : MutableCollection<in R>> Iterable<*>.filterIsInstanceTo(
    destination: C
): C
(source)

예시

open class Animal(val name: String) {
    override fun toString(): String {
        return name
    }
}
class Dog(name: String): Animal(name)
class Cat(name: String): Animal(name)

val animals: List<Animal> = listOf(Cat("Scratchy"), Dog("Poochie"))
val cats = mutableListOf<Cat>()

println(cats) // []

animals.filterIsInstanceTo<Cat, MutableList<Cat>>(cats)

println(cats) // [Scratchy]
//이거 이름나오는거 위에서 toString 을 name 를 return 하게 되어있는거다 즉 그냥 인스턴스 자체가 넘어오는것

-> 자료형이 Cat인 인스턴스들만 걸러서 모을수있다.

fun <C : MutableCollection<in R>, R> Array<*>.filterIsInstanceTo(
    destination: C,
    klass: Class<R>
): C
(source)
fun <C : MutableCollection<in R>, R> Iterable<*>.filterIsInstanceTo(
    destination: C,
    klass: Class<R>
): C
(source)

제너릭 없이 이런식으로도 사용가능하다.

 

예시

open class Animal(val name: String) {
    override fun toString(): String {
        return name
    }
}
class Dog(name: String): Animal(name)
class Cat(name: String): Animal(name)

val animals: List<Animal> = listOf(Cat("Scratchy"), Dog("Poochie"))
val cats = mutableListOf<Cat>()

println(cats) // []

animals.filterIsInstanceTo(cats, Cat::class.java)

println(cats) // [Scratchy]

3-5. filterKeys

맵 전용 필터인데 key 값이 인자로 넘어와서 판단할수있도록한다.

inline fun <K, V> Map<out K, V>.filterKeys(
    predicate: (K) -> Boolean
): Map<K, V>
val originalMap = mapOf("key1" to 1, "key2" to 2, "something_else" to 3)

val filteredMap = originalMap.filterKeys { it.contains("key") }
println(filteredMap) // {key1=1, key2=2}
// original map has not changed
println(originalMap) // {key1=1, key2=2, something_else=3}

val nonMatchingPredicate: (String) -> Boolean = { it == "key3" }
val emptyMap = originalMap.filterKeys(nonMatchingPredicate)
println(emptyMap) // {}

 

3-6.filterValues

이것도 맵 전용 필터이고 value 값이 인자로 넘어온다.

 

3-7. filterNotNull

reduce와 fold와는 다르게 그냥 null값이 섞여있는 value가 있다면 없애버린다.

fun <T : Any> Array<out T?>.filterNotNull(): List<T>
(source)
fun <T : Any> Iterable<T?>.filterNotNull(): List<T>
(source)
val numbers: List<Int?> = listOf(1, 2, null, 4)
val nonNullNumbers = numbers.filterNotNull()

println(nonNullNumbers) // [1, 2, 4]

3-8 filter 관련 이름만 봐도 할수있는것들

filterNot(필터 조건 false 인거)

filterNotNullTo(filterNotNull 의 목적지 버전)

filterNotTo(filterNot 목적지 버전)

filterTo(filter 목적지 버전)

 

 

4-1. map(핵심하나 추가요)

filter은 걸러내는것이지 각 원소들에 변화를 가할수는 없기때문에 그런경우에는 map을 이용해서 값들의 변화를 거친 결과값들을 받는다.

inline fun <T, R> Array<out T>.map(
    transform: (T) -> R
): List<R>

 

 

Iterable의 경우

val numbers = listOf(1, 2, 3)
println(numbers.map { it * it }) // [1, 4, 9]

map의 경우

inline fun <K, V, R> Map<out K, V>.map(
    transform: (Entry<K, V>) -> R
): List<R>
val peopleToAge = mapOf("Alice" to 20, "Bob" to 21)
println(peopleToAge.map { (name, age) -> "$name is $age years old" }) // [Alice is 20 years old, Bob is 21 years old]
println(peopleToAge.map { it.value }) // [20, 21]

map 의 경우 저렇게 람다의 인자로 key와 value 를 명시해서 받을수있다.

 

4-2 mapIndexed

당연하게 값을 조작할 인자로 index를 받는것이다.

inline fun <T, R> Iterable<T>.mapIndexed(
    transform: (index: Int, T) -> R
): List<R>
(source)
val names = listOf("foo", "bar")

names.mapIndexed{index, string -> Pair(index, string)}

이런식으로 사용할수있다.

 

4-3 mapNotNull

값을 변환했을때  null값이 있다면 그거는 빼고 반환한다.

inline fun <T, R : Any> Iterable<T>.mapNotNull(
    transform: (T) -> R?
): List<R>
(source)
val strings: List<String> = listOf("12a", "45", "", "3")
val ints: List<Int> = strings.mapNotNull { it.toIntOrNull() }

println(ints) // [45, 3]
println(ints.sum()) // 48

toIntOrNull 을 거치며 int가 될수없는 값들은 null 이 되므로 "12a"도 빠진다.

 

Map 자료형의 경우

inline fun <K, V, R : Any> Map<out K, V>.mapNotNull(
    transform: (Entry<K, V>) -> R?
): List<R>
(source)
val map = mapOf("Alice" to 20, "Tom" to 13, "Bob" to 18)
val adults = map.mapNotNull { (name, age) -> name.takeIf { age >= 18 } }

println(adults) // [Alice, Bob]

이런식이다 name 만 가져왔으니 결과값은 name의 배열이 나오고 takeIf로 18살 미만은 null로 리턴해버리므로 Tom 이 빠진다.

 

 

4-4 mapKeys

Map자료형 관련이다. 함수에서 it이 key랑 value pair 를 받아서 원하는대로 조작하는데 결국 우리가 조작한 것들이 key 값만의 변형을 일으킨다 즉 value 는 못바꾼다. Map 자료형을 다시 뱉는다.

inline fun <K, V, R> Map<out K, V>.mapKeys(
    transform: (Entry<K, V>) -> R
): Map<R, V>
(source)
val map1 = mapOf("beer" to 2.7, "bisquit" to 5.8)
val map2 = map1.mapKeys { it.key.length }
println(map2) // {4=2.7, 7=5.8}

val map3 = map1.mapKeys { it.key.take(1) }
println(map3) // {b=5.8}

예시인데 쓸모없는거같다.

4-5 mapValues

이정도는 유추할수있을 것이다. mapKeys 를 참고하자

 

4-6 map 관련 이름만 들어도 아는것들

mapIndexedNotNull (조건에 index 들어가며 결과값에 null 이있는경우 제외하고 반환)

mapIndexedNotNullTo(mapIndexedNotNull 목적지 버전)

mapIndexedTo(mapIndexed 목적지버전)

mapKeysTo(mapKeys 목적지 버전)

mapNotNullTo(mapNotNull 목적지버전)

mapTo(목적지)

mapValuesTo(목적지)

 


컬렉션에 술어를 적용해봅시다.

5. all

boolean 을 반환하는 함수로 요소를 모두 검사하여 해당조건에 맞으면 true/false 를 반환하는함수

모두 조건에 맞아야 true를 반환

 

if (a.all{ it is Int }) {
        println("all: 모두 조건에 맞으면 true를 반환")
    }

6. any

boolean 을 반환하는 함수로 요소를 모두 검사하여 해당조건에 맞으면 true/false 를 반환하는함수

조건에 하나라도 걸리면 true 반환

 

if (a.any{ it==2 }) {
        println("any: 하나라도 조건에 맞으면 true를 반환")
    }

 

7.none

boolean 을 반환하는 함수로 요소를 모두 검사하여 해당조건에 맞으면 true/false 를 반환하는함수

조건에 맞는게 하나도 없다면 true 반환

 if (a.none{ it > 4 }) {
        println("none: 하나도 조건에 맞지 않으면 true를 반환")
    }

 

8.count

조건을 안걸고 사용시 리스트의 길이를 반환하고 조건을 람다로 넘길시 조건에 맞는 아이템의 갯수를 반환한다.

fun main() {
    var a: List<Int> = listOf(1, 2, 3, 4)
    
    println(a.count())
    println(a.count{ it > 2 })
}

 

9. find,findLast

find: 조건을 만족하는 첫번째 원소 반환

findlast: 조건을 만족하는 마지막 원소 반환

조건을 충족하는 것이 없다면 null 반환

val words = listOf("Lets", "find", "something", "in", "collection", "somehow") 

// "something" 반환함
val first = words.find { it.startsWith("some") }                                
// "somehow" 반환함
val last = words.findLast { it.startsWith("some") }                             
// null 반환함
val nothing = words.find { it.contains("nothing") }

 

10.first,last,firstOrNull,lastOrNull

이 함수들은 그냥 사용한다면 컬렉션의 첫번째 원소반환(first) 컬렉션의 마지막 원소 반환(last)을 하지만

인자로 람다를 넘겨주면 람다 조건에 맞는 첫번째와 마지막 원소를 반환한다 -> find와 findLast와 호환된다.

 

first와 last의 경우 조건에 맞는 원소가 없는경우 NoSuchElementException 예외가 일어나므로

예외없이 사용하고싶다면  firstOrNull 또는 lastOrNull 을 사용하여 조건에 맞는 원소가 없는경우 null 을 반환하도록 사용하면된다.

 var a: List<Int> = listOf(1, 2, 3, 4)
    
    println(a.first())
    println(a.first{ it > 1 })
    
    println(a.last())
    println(a.last{ it < 4 })

 

11.getOrElse

접근하려는 컬렉션에서 원하는 요소가 없다면 ex) list 길이가 3인데 list 5번째 원소에 접근

이럴경우 기본적으로 지정해놓은 디폴트값이 들어간다

inline fun <T> List<T>.getOrElse(
    index: Int,
    defaultValue: (Int) -> T
): T
val list = listOf(0, 10, 20)
println(list.getOrElse(1) { 42 })   //10 출력
println(list.getOrElse(10) { 42 })	//42 출력

map 자료형에도 마찬가지로 적용된다

inline fun <K, V> Map<K, V>.getOrElse(
    key: K,
    defaultValue: () -> V
): V
val map = mutableMapOf<String, Int?>()
println(map.getOrElse("x") { 1 }) // 1

map["x"] = 3
println(map.getOrElse("x") { 1 }) // 3

map["x"] = null
println(map.getOrElse("x") { 1 }) // 1

12.getOrDefault

getOrElse 랑 똑같다 다를게 없다.

fun <K, V> Map<out K, V>.getOrDefault(
    key: K,
    defaultValue: V
): V

Map 자료형에만 사용가능하다.

12.getOrNull

원하는 값이 없다면 null 값을 배출한다.

fun <T> List<T>.getOrNull(index: Int): T?
(source)
val list = listOf(1, 2, 3)
println(list.getOrNull(0)) // 1
println(list.getOrNull(2)) // 3
println(list.getOrNull(3)) // null

val emptyList = emptyList<Int>()
println(emptyList.getOrNull(0)) // null

13.getOrPut

Map 자료형에서만 쓸수있는데 해당 key 값을 찍어봤는데 값이 있다면 값을 반환하고 아니면 넘겨준 람다의 반환값을 넣어준다.

inline fun <K, V> MutableMap<K, V>.getOrPut(
    key: K,
    defaultValue: () -> V
): V
val map = mutableMapOf<String, Int?>()

println(map.getOrPut("x") { 2 }) // 2
// 기존에 x에 값이 없었으니 2를 넣어준다.
println(map.getOrPut("x") { 3 }) // 2
//이제 x에 값이 있으니 기존값인 2를 반환한다.


println(map.getOrPut("y") { null }) // null
// 이렇게 람다의 반환값을 null로 할수도 있다
println(map.getOrPut("y") { 42 }) // 42

14.minOrNull,maxOrNull

min값과 max 값을 반환하되 컬렉션 자체가 비어있다면 null 값을 반환한다.

val numbers = listOf(1, 2, 3)
val empty = emptyList<Int>()
val only = listOf(3)


println("Numbers: $numbers, min = ${numbers.minOrNull()} max = ${numbers.maxOrNull()}") 
// 1, 3 을 반환함
println("Empty: $empty, min = ${empty.minOrNull()}, max = ${empty.maxOrNull()}")   
// null을 반환함
println("Only: $only, min = ${only.minOrNull()}, max = ${only.maxOrNull()}")
// 3을 반환함

이제 여타 함수들을 살펴봅시다.

15. groupBy

16.associateBy

 

17.partition

조건에따라 분류하여 조건에 부합하는 리스트와 부합하지 않는 리스트를 각각 만들어 pair 형태로 반환한다.

pair의 첫번째가 조건을 통과한것들 pair의 두번째가 조건을 통과하지 못한것이다.

val array = intArrayOf(1, 2, 3, 4, 5)
val (even, odd) = array.partition { it % 2 == 0 }
println(even) // [2, 4]
println(odd) // [1, 3, 5]

18.flatMap

19.distinct,distinctBy

- 컬렉션 내에 포함된 항목 중 중복된 항목을 걸러낸 결과를 반환한다. 항목의 중복 여부는 equals()로 판단하며, distinctBy() 함수를 사용하면 비교에 사용할 키 값을 직접 설정할 수 있다.-> 중복 기준 설정 가능

 

distinct:

fun <T> Iterable<T>.distinct(): List<T>

distinctBy

inline fun <T, K> Iterable<T>.distinctBy(
    selector: (T) -> K
): List<T>
fun main(args: Array<String>) {
    val cities = listOf("Seoul", "Tokyo", "London", "Seoul", "Tokyo")

    // Seoul, Tokyo, London
    cities.distinct()
        .forEach { println(it) }

    // Seoul, London
    cities.distinctBy { it.length } // 도시 이름의 길이를 판단 기준으로 사용
        .forEach { println(it) }
}