본문 바로가기

Kotlin/Play Kotlin Example

Kotlin 공식 Example로 공부하기 - Collections#2(associateBy, groupBy, partition, flatMap, sorted, withDefault, zip, getOrElse)

반응형

Collections (2)

  • 저는 전문 번역가도 아니고, 의역을 넘어 오역, 심지어 그냥 제가 읽고 싶은대로 읽은 내용이 있을 수 있습니다.
  • 개인 공부를 한 것을 포스트로 남기고 있으며 틀린 부분이 있으면 지적해주시면 수정하도록 하겠습니다.
  • 원문 : https://play.kotlinlang.org/byExample/05_Collections/01_List
  • Collections 부분은 꽤 길어서 나누어 작성했습니다.

associateBy, groupBy

associateBy 함수와 groupBy 함수는 지정된 키(Key)에 의해 인덱싱된 컬렉션의 원소로부터 Map을 만듭니다.

키(Key)는 KeySelector 파라미터에서 정의됩니다. 뿐만 아니라 옵션으로 valueSelector 를 정의할 수 있습니다. (기본적으로는 해당 원소(element)가 value가 됩니다.)

associateBy 와 groupBy 의 차이

  • associateBy : value를 만들 때, 마지막으로 적합한 값을 value로 지정합니다. (→ key, value 형식의 Map을 만들고 있기 때문에 key가 중복된다면 마지막에 있는 원소가 value가 된다는 얘기입니다.)
  • groupBy : value를 만들 때, 모든 적합한 요소를 사용한다. (→ 모든 적합한 요소라 함은 key가 중복될 때, value를 리스트로 만들어준다는 얘기입니다.)

반환된 Map은 원래 컬렉션의 아이템 순서를 유지합니다.

data class Person(val name: String, val city: String, val phone: String) // 1

val people = listOf(                                                     // 2
    Person("John", "Boston", "+1-888-123456"),
    Person("Sarah", "Munich", "+49-777-789123"),
    Person("Svyatoslav", "Saint-Petersburg", "+7-999-456789"),
    Person("Vasilisa", "Saint-Petersburg", "+7-999-123456"))

val phoneBook = people.associateBy { it.phone }                          // 3
//{+1-888-123456=Person(name=John, city=Boston, phone=+1-888-123456), +49-777-789123=Person(name=Sarah, city=Munich, phone=+49-777-789123), +7-999-456789=Person(name=Svyatoslav, city=Saint-Petersburg, phone=+7-999-456789), +7-999-123456=Person(name=Vasilisa, city=Saint-Petersburg, phone=+7-999-123456)}

val cityBook = people.associateBy(Person::phone, Person::city)           // 4
//{+1-888-123456=Boston, +49-777-789123=Munich, +7-999-456789=Saint-Petersburg, +7-999-123456=Saint-Petersburg}

val cityBooks = people.groupBy(Person::phone, Person::city)              // 4-1
//{+1-888-123456=[Boston], +49-777-789123=[Munich], +7-999-456789=[Saint-Petersburg], +7-999-123456=[Saint-Petersburg]}

val peopleCities = people.groupBy(Person::city, Person::name)            // 5
//{Boston=[John], Munich=[Sarah], Saint-Petersburg=[Svyatoslav, Vasilisa]}

val peopleCity = people.associateBy(Person::city, Person::name)          // 5-1
//{Boston=John, Munich=Sarah, Saint-Petersburg=Vasilisa}
  1. Person 데이터 클래스를 정의합니다.
  2. 컬렉션을 정의합니다.
  3. Person 객체의 phone number를 기준(it.phone = keySelector)으로 Map을 만듭니다. valueSelector 를 별도로 지정하지 않았으므로 Person객체 자신이 value로 지정됩니다.
  4. Person 객체의 phone number를 기준으로 Map을 만듭니다. valueSelector로 Person객체의 city를 지정했으므로 <key,value> = <phone, city>로 생성됩니다.
    • 4-1 : associateBy를 groupBy로 바꿨습니다. key가 중복되는게 없어서 비슷하게 만들어졌지만 groupBy는 value가 리스트로 만들어진 것을 확인할 수 있습니다.
  5. Person 객체의 city를 기준으로 Map을 만듭니다. value는 Person의 name입니다.
    • 5-1 : associateBy로도 만들어봤습니다. key가 Saint-Petersburg로 중복되었을 때는 마지막에 나온 원소로 덮어씌워지는 것을 확인할 수 있습니다.

partition

partition 함수는 주어진 predicate 를 사용하여 쌍(pair)로 나눠줍니다. true인 것과 false인 것을 나눠줍니다.

val numbers = listOf(1, -2, 3, -4, 5, -6)                // 1

val evenOdd = numbers.partition { it % 2 == 0 }           // 2
val (positives, negatives) = numbers.partition { it > 0 } // 3
//evenOdd = ([-2, -4, -6], [1, 3, 5])
//positives = [1, 3, 5]
//negatives = [-2, -4, -6]
  1. 숫자 컬렉션을 정의합니다.
  2. numbers 를 짝수, 홀수 쌍로 나눕니다.
  3. numbers 를 (양수와 음수) 두 개의 리스트로 나눕니다. Pair destructuring이 여기에 적용되어 쌍으로 나뉜 것을 확인할 수 있습니다.

flatMap

flatMap 은 컬렉션의 각 요소를 iterable 객체로 변환하고 변환 결과를 단일 리스트로 만듭니다.

어떻게 변환할지는 사용자가 정의합니다.

val numbers = listOf(1, 2, 3)                        // 1

val tripled = numbers.flatMap { listOf(it, it, it) } // 2
//[1, 1, 1, 2, 2, 2, 3, 3, 3]
  1. 숫자 컬렉션을 정의합니다.
  2. 모든 컬렉션 요소가 세 번씩 반복되는 리스트를 만듭니다. 중요한건 리스트의 리스트를 만든게 아니라 9개의 요소를 갖는(플랫하게 펼친) 리스트 하나를 만들어내는 것입니다.

min, max

min 과 max 함수는 요소에서 최소 값과 최대 값을 리턴합니다. 컬렉션이 비어있다면 null을 리턴합니다.

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

println("Numbers: $numbers, min = ${numbers.min()} max = ${numbers.max()}") // 1
//Numbers: [1, 2, 3], min = 1 max = 3
println("Empty: $empty, min = ${empty.min()}, max = ${empty.max()}")        // 2
//Empty: [], min = null, max = null
  1. 비어 있지 않은 컬렉션은 각각 최소값과 최대값을 리턴합니다.
  2. 비어 있는 컬렉션은 각각 null 을 리턴합니다.

sorted

sorted 함수는 정렬 순서(기본은 오름차순(ascending))에 따라 컬렉션의 리스트를 리턴합니다.

sortedBy 함수는 지정된 selector 함수에 의해서 정렬 순서(기본 오름차순)에 따라 컬렉션의 리스트를 리턴합니다.

val shuffled = listOf(5, 4, 2, 1, 3)     // 1
val natural = shuffled.sorted()          // 2  [1,2,3,4,5]
val inverted = shuffled.sortedBy { -it } // 3  [5,4,3,2,1]
  1. 섞인 숫자 컬렉션을 정의합니다.
  2. 기본 정렬 순서로 정렬을 수행합니다.
  3. -it 라는 selector 함수를 이용하여 정렬을 수행합니다.

Map Element Access

Map에 접근할 때, 대괄호([])는 주어진 키(key)에 해당하는 값을 리턴합니다. 만약 Map에 키(key)가 없다면 null 을 리턴합니다.

getValue 함수는 주어진 키에 일치하는 값을 리턴합니다. 만약 Map에 키(key)가 없다면 Exception 을 발생시킵니다.

withDefault 로 생성된 Map의 getValue 함수는 Exception 을 발생시키는 대신 기본 값(default value)를 리턴합니다.

val map = mapOf("key" to 42)

val value1 = map["key"]                                     // 1
val value2 = map["key2"]                                    // 2

val value3: Int = map.getValue("key")                       // 1

val mapWithDefault = map.withDefault { k -> k.length }
val value4 = mapWithDefault.getValue("key2")                // 3

try {
    map.getValue("anotherKey")                              // 4
} catch (e: NoSuchElementException) {
    println("Message: $e")
}
  1. "key"라는 key가 존재해서 해당하는 값(42)을 리턴합니다.
  2. "key2"라는 key가 존재하지 않아 null 을 리턴합니다.
  3. "key2"라는 key가 존재하지 않아 기본 값을 리턴합니다. withDefault 의 조건식에 의해 길이가 4인 문자열인 "key2"에 의해 4가 리턴됩니다.
  4. NoSuchElementException 을 발생시킵니다. "anotherKey"라는 키가 없기 때문입니다.

zip

zip 함수는 두 개의 주어진 컬렉션을 새로운 컬렉션으로 합칩니다.

기본적으로 결과로 리턴된 컬렉션에는 동일한 인덱스를 가진 소스 컬렉션의 Pairs가 포함됩니다.

그렇지만 결과 컬렉션의 구조를 원하는 대로 정의할 수 있습니다.

결과 컬렉션의 최소 크기는 호출하는 원본 컬렉션의 최소 크기와 같습니다.

val A = listOf("a", "b", "c")                  // 1
val B = listOf(1, 2, 3, 4)                     // 1

val resultPairs = A zip B                      // 2 [(a,1), (b,2), (c,3)]
val resultReduce = A.zip(B) { a, b -> "$a$b" } // 3 [a1, b2, c3]
  1. 두 개의 컬렉션을 정의합니다.
  2. pair를 갖는 리스트로 합칩니다. 중위 표기법으로 zip 이 사용되었습니다.
  3. 주어진 변화식에 의해 문자열을 갖는 리스트로 합칩니다.

getOrElse

getOrElse 는 컬렉션에 요소에 접근할때 안전한 접근을 제공한다.

getOrElse 는 인덱스와 인덱스가 범위를 넘어가는 경우에 기본 값을 제공하는 함수를 파라미터로 받습니다.

val list = listOf(0, 10, 20)
println(list.getOrElse(1) { 42 })    // 1
println(list.getOrElse(10) { 42 })   // 2
  1. 인덱스(1)에 해당하는 값 10을 출력합니다.
  2. 10은 인덱스에서 벗어나기 때문에 기본 값을 제공하는 함수에서 리턴하는 42를 출력합니다.

getOrElse 는 Map에서 값을 가져오는데 쓸 수도 있습니다.

val map = mutableMapOf<String, Int?>()
println(map.getOrElse("x") { 1 })       // 1

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

map["x"] = null
println(map.getOrElse("x") { 1 })       // 3
  1. "x"가 map에 존재하지 않기 때문에 기본 값을 출력합니다.
  2. 3을 출력합니다. "x"(key)에 해당하는 값입니다.
  3. 기본 값을 출력합니다. "x"(key)가 정의되지 않았기 때문입니다.
반응형