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}
- Person 데이터 클래스를 정의합니다.
- 컬렉션을 정의합니다.
- Person 객체의 phone number를 기준(
it.phone
=keySelector
)으로 Map을 만듭니다.valueSelector
를 별도로 지정하지 않았으므로 Person객체 자신이 value로 지정됩니다. - Person 객체의 phone number를 기준으로 Map을 만듭니다. valueSelector로 Person객체의 city를 지정했으므로 <key,value> = <phone, city>로 생성됩니다.
- 4-1 : associateBy를 groupBy로 바꿨습니다. key가 중복되는게 없어서 비슷하게 만들어졌지만 groupBy는 value가 리스트로 만들어진 것을 확인할 수 있습니다.
- 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]
- 숫자 컬렉션을 정의합니다.
numbers
를 짝수, 홀수 쌍로 나눕니다.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]
- 숫자 컬렉션을 정의합니다.
- 모든 컬렉션 요소가 세 번씩 반복되는 리스트를 만듭니다. 중요한건 리스트의 리스트를 만든게 아니라 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
- 비어 있지 않은 컬렉션은 각각 최소값과 최대값을 리턴합니다.
- 비어 있는 컬렉션은 각각
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]
- 섞인 숫자 컬렉션을 정의합니다.
- 기본 정렬 순서로 정렬을 수행합니다.
-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")
}
- "key"라는 key가 존재해서 해당하는 값(42)을 리턴합니다.
- "key2"라는 key가 존재하지 않아
null
을 리턴합니다. - "key2"라는 key가 존재하지 않아 기본 값을 리턴합니다.
withDefault
의 조건식에 의해 길이가 4인 문자열인 "key2"에 의해 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]
- 두 개의 컬렉션을 정의합니다.
- pair를 갖는 리스트로 합칩니다. 중위 표기법으로
zip
이 사용되었습니다. - 주어진 변화식에 의해 문자열을 갖는 리스트로 합칩니다.
getOrElse
getOrElse
는 컬렉션에 요소에 접근할때 안전한 접근을 제공한다.
getOrElse
는 인덱스와 인덱스가 범위를 넘어가는 경우에 기본 값을 제공하는 함수를 파라미터로 받습니다.
val list = listOf(0, 10, 20)
println(list.getOrElse(1) { 42 }) // 1
println(list.getOrElse(10) { 42 }) // 2
- 인덱스(1)에 해당하는 값 10을 출력합니다.
- 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
- "x"가 map에 존재하지 않기 때문에 기본 값을 출력합니다.
- 3을 출력합니다. "x"(key)에 해당하는 값입니다.
- 기본 값을 출력합니다. "x"(key)가 정의되지 않았기 때문입니다.