본문 바로가기

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가 됩니다.)

associateBygroupBy 의 차이

  • 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

minmax 함수는 요소에서 최소 값과 최대 값을 리턴합니다. 컬렉션이 비어있다면 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)가 정의되지 않았기 때문입니다.
반응형