Kotlin Examples > Collections

HH·2022년 3월 11일
0

Kotlin Examples

목록 보기
3/8

2022-03-10 작성완료

List


list는 아이템의 순서 있는 컬렉션이다(ordered collection). 코틀린에서 리스트는 가변(mutable)이거나 (MutableList) 읽기 전용(List)일 수 있다. 표준 라이브러리 함수 listOf()를 읽기 전용 리스트 생성에 사용하고 mutableListOf()를 가변 리스트 생성에 사용한다. 원하지 않는 수정을 피하기 위해 가변 리스트를 List로 캐스팅해 읽기 전용 뷰를 만들 수 있다.

val systemUsers: MutableList<Int> = mutableListOf(1, 2, 3)        // 1
val sudoers: List<Int> = systemUsers                              // 2

fun addSystemUser(newUser: Int) {                                 // 3
    systemUsers.add(newUser)                      
}

fun getSysSudoers(): List<Int> {                                  // 4
    return sudoers
}

fun main() {
    addSystemUser(4)                                              // 5 
    println("Tot sudoers: ${getSysSudoers().size}")               // 6
    getSysSudoers().forEach {                                     // 7
        i -> println("Some useful info on user $i")
    }
    // getSysSudoers().add(5) <- Error!                           // 8
}
  1. MutableList 를 생성한다.
  2. 리스트의 읽기 전용 뷰를 만든다.
  3. 새 아이템을 MutableList에 더한다.
  4. 불변 List를 리턴하는 함수
  5. MutableList를 업데이트한다. 모든 관련된 읽기 전용 뷰 또한 업데이트된다. 동일한 객체를 가리키고 있기 때문이다.
  6. 읽기 전용 리스트의 사이즈를 인출한다.
  7. 리스트를 순회하고 그 요소를 프린트한다.
  8. 읽기 전용 뷰에 대한 쓰기 시도는 컴파일 에러를 만든다.

Set


set은 순서가 없는 컬렉션이고 중복(duplicates)를 지원하지 않는다. set을 생성하기 위해서는 setOf()mutableSetOf()함수를 사용한다. 가변 셋의 읽기 전용 뷰는 그것을 Set으로 캐스팅해서 얻을 수 있다.

val openIssues: MutableSet<String> = mutableSetOf("uniqueDescr1", "uniqueDescr2", "uniqueDescr3") // 1

fun addIssue(uniqueDesc: String): Boolean {                                                       
    return openIssues.add(uniqueDesc)                                                             // 2
}

fun getStatusLog(isAdded: Boolean): String {                                                       
    return if (isAdded) "registered correctly." else "marked as duplicate and rejected."          // 3
}

fun main() {
    val aNewIssue: String = "uniqueDescr4"
    val anIssueAlreadyIn: String = "uniqueDescr2" 

    println("Issue $aNewIssue ${getStatusLog(addIssue(aNewIssue))}")                              // 4
    println("Issue $anIssueAlreadyIn ${getStatusLog(addIssue(anIssueAlreadyIn))}")                // 5 
}
  1. 주어진 요소로 set을 생성한다.
  2. 요소가 실제로 추가되었는지를 보여주는 불리언 값을 리턴한다.
  3. 함수 입력 파라미터에 따라 문자열을 리턴한다.
  4. 성공 메시지를 프린트한다: 새 요소가 Set에 추가되었다.
  5. 실패 메시지를 프린트한다: 요소가 이미 존재하는 요소와 중복되므로 추가되지 못했다.

Map


map은 키/값 쌍의 컬렉션이다. 각 키는 유니크하고 상응하는 값을 인출하기 위해 사용된다. 맵 생성에는 mapOf()mutableMapOf() 함수를 사용한다. to infix function은 초기화를 덜 noisy하게 만든다. 가변 맵의 읽기 전용 뷰는 Map으로 캐스팅해 얻을 수 있다.

const val POINTS_X_PASS: Int = 15
val EZPassAccounts: MutableMap<Int, Int> = mutableMapOf(1 to 100, 2 to 100, 3 to 100)   // 1
val EZPassReport: Map<Int, Int> = EZPassAccounts                                        // 2

fun updatePointsCredit(accountId: Int) {
    if (EZPassAccounts.containsKey(accountId)) {                                        // 3
        println("Updating $accountId...")                                               
        EZPassAccounts[accountId] = EZPassAccounts.getValue(accountId) + POINTS_X_PASS  // 4
    } else {
        println("Error: Trying to update a non-existing account (id: $accountId)")
    } 
}

fun accountsReport() {
    println("EZ-Pass report:")
    EZPassReport.forEach {                                                              // 5
        k, v -> println("ID $k: credit $v")
    }
}

fun main() {
    accountsReport()                                                                    // 6
    updatePointsCredit(1)                                                               // 7
    updatePointsCredit(1)                                                               
    updatePointsCredit(5)                                                               // 8 
    accountsReport()                                                                    // 9
}
  1. 가변 Map을 생성한다.
  2. Map의 읽기 전용 뷰를 생성한다.
  3. Map의 키가 존재하는지를 체크한다.
  4. 해당 값을 읽고 상수만큼 증가시킨다.
  5. 불변 Map을 순회하고 키/값 페어를 프린트한다.
  6. 계좌 포인트 잔액을 업데이트 전에 읽는다.
  7. 존재하는 계좌를 두번 업데이트한다.
  8. 존재하지 않는 계좌를 업데이트 시도한다: 에러 메시지를 출력한다.
  9. 업데이트 이후에 계좌 포인트 잔고를 읽는다.

filter


filter 함수는 컬렉션을 필터링한다. 필터 술부를 람다 파라미터로 가진다. 술부는 각 요소에 대해 적용된다. 결과 배열에는 술부를 true로 만드는 요소가 반환된다.

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

val positives = numbers.filter { x -> x > 0 }  // 2

val negatives = numbers.filter { it < 0 }      // 3  
  1. 숫자 컬렉션을 정의한다.
  2. 양수를 얻는다.
  3. 음수를 얻기 위해 더 짧은 it 표기법을 사용한다.

map


map 표현식 함수는 컬렉션의 모든 요소에 변환을 적용한다. 이는 변환함수를 람다 파라미터로 가진다.

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

val doubled = numbers.map { x -> x * 2 }      // 2

val tripled = numbers.map { it * 3 }          // 3
  1. 숫자 컬렉션을 정의한다.
  2. 숫자에 2를 곱한다.
  3. 숫자에 3을 곱히기 위해 더 짧은 it 표현식을 사용한다.

any, all, none


이 세 가지는 주어진 술부와 일치하는 컬렉션 요소가 있는지 체크한다.


함수 any


any는 하나 이상의 요소가 주어진 술부와 일치할 경우 true를 반환한다.

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

val anyNegative = numbers.any { it < 0 }             // 2

val anyGT6 = numbers.any { it > 6 }                  // 3 
  1. 숫자 컬렉션을 정의한다.
  2. 음수인 요소가 있는지 체크힌다.
  3. 6보다 큰 요소가 있는지 체크한다.

함수 all


all은 컬렉션의 모든 요소가 주어진 술부와 일치할 경우 true를 반환한다.

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

val allEven = numbers.all { it % 2 == 0 }            // 2

val allLess6 = numbers.all { it < 6 }                // 3
  1. 숫자 컬렉션을 정의한다.
  2. 모든 숫자가 짝수인지 체크한다.
  3. 모든 숫자가 6보다 작은지 체크한다.

함수 none


none은 컬렉션의 어떤 요소도 주어진 술부와 일치하지 않을 때 true를 반환한다.

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

val allEven = numbers.none { it % 2 == 1 }           // 2
val allLess6 = numbers.none { it > 6 }                // 3
  1. 숫자 컬렉션을 정의한다.
  2. 홀수가 없는지 (모든 숫자가 짝수인지) 확인한다.
  3. 6보다 큰 숫자가 없는지 확인한다.

find, findLast


findfindLast 함수는 주어진 술부와 일치하는 첫 번째 또는 마지막 컬렉션 요소를 반환한다. 일치하는 요소가 없다면 null을 반환한다.

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

val first = words.find { it.startsWith("some") }                                // 2

val last = words.findLast { it.startsWith("some") }                             // 3

val nothing = words.find { it.contains("nothing") }                             // 4
  1. 단어 컬렉션을 정의한다.
  2. "some"으로 시작하는 첫 번째 단어를 찾는다.
  3. "some"으로 시작하는 마지막 단어를 찾는다.
  4. "nothing"을 포함하는 첫 번째 단어를 찾는다.

first, last



first, last


이 함수들은 컬렉션의 처음과 마지막 요소를 반환한다. 술부와 함께 이를 사용할 수도 있다; 이 경우에는 주어진 술부와 일치하는 처음 또는 마지막 요소가 반환된다.

만약 컬렉션이 비었거나 해당 술부와 일치하는 요소를 가지고 있지 않다면 NoSuchElementException을 반환한다.

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

val first = numbers.first()                          // 2
val last = numbers.last()                            // 3

val firstEven = numbers.first { it % 2 == 0 }        // 4
val lastOdd = numbers.last { it % 2 != 0}            // 5
  1. 숫자 컬렉션을 정의한다.
  2. 첫 요소를 가져온다.
  3. 마지막 요소를 가져온다
  4. 첫 짝수 요소를 가져온다.
  5. 첫 음수 요소를 가져온다.

firstOrNull, lastOrNull


이 함수들은 한 가지 차이점을 빼면 위와 거의 동일하게 동작한다: 일치하는 요소가 없을 경우 null을 반환한다.

val words = listOf("foo", "bar", "baz", "faz")         // 1
val empty = emptyList<String>()                        // 2

val first = empty.firstOrNull()                        // 3
val last = empty.lastOrNull()                          // 4

val firstF = words.firstOrNull { it.startsWith('f') }  // 5
val firstZ = words.firstOrNull { it.startsWith('z') }  // 6
val lastF = words.lastOrNull { it.endsWith('f') }      // 7
val lastZ = words.lastOrNull { it.endsWith('z') }      // 8
  1. 단어 컬렉션을 정의한다.
  2. 빈 컬렉션을 정의한다.
  3. 빈 컬렉션에서 첫 요소를 가져온다. 이것은 null이 되어야 한다.
  4. 빈 컬렉션에서 마지막 요소를 가져온다. 이것 또한 null이 될 것이다.
  5. 'f'로 시작되는 첫 단어를 가져온다.
  6. 'z'로 시작되는 첫 단어를 가져온다.
  7. 'f'로 시작되는 마지막 단어를 가져온다.
  8. 'z'로 시작되는 마지막 단어를 가져온다.

count


count 함수는 컬렉션 요소의 총 수 또는 주어진 술부와 일치하는 요소의 수를 반환한다.

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

val totalCount = numbers.count()                     // 2
val evenCount = numbers.count { it % 2 == 0 }        // 3
  1. 숫자 컬렉션을 정의한다.
  2. 요소의 전체 수를 카운트한다.
  3. 짝수 요소의 수를 카운트한다.

associateBy, groupBy


함수 associateBygroupBy는 지정된 키로 인덱싱된 컬렉션의 요소들로부터 맵을 빌드한다. 키는 keySelector 파라미터에 정의된다. 또한 선택적인 valueSelector를 지정해 맵 요소의 value에 무엇이 저장될지를 지정할 수 있다.

associateBygroupBy의 차이점은 동일한 키를 가지고 객체를 처리하는 방법이다:

  • associateBy는 마지막 적합 요소 (last suitable element)를 값으로 사용한다.
  • groupBy는 모든 적합한 요소들의 리스트를 빌드하고 그것을 값에 집어넣는다.

반환된 맵은 원 컬렉션의 entry iteration order를 유지한다.

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
val cityBook = people.associateBy(Person::phone, Person::city)           // 4
val peopleCities = people.groupBy(Person::city, Person::name)            // 5
val lastPersonCity = people.associateBy(Person::city, Person::name)      // 6
  1. 사람을 묘사하는 데이터 클래스를 정의한다.
  2. 사람 컬렉션을 정의한다.
  3. 전화번호가 그 소유주의 정보로 이어지는 맵을 만든다. it.phonekeySelector이고, valueSelector는 정의되지 않았으므로 Person객체가 맵의 값이 된다.
  4. 전화번호로부터 그 소유주가 사는 도시로 이어지는 맵을 만든다. Person::cityvalueSelector이므로 맵의 값에는 도시만이 담기게 된다.
  5. 도시와 그 도시에 사는 사람들을 가진 맵을 만든다. 맵의 값은 각 도시 거주민 이름 리스트다.
  6. 도시와 그곳에 사는 마지막 사람을 가진 맵을 만든다. 맵의 값은 각 도시에 사는 마지막 사람의 이름이다.

partition


partition 함수는 원본 컬렉션을 주어진 술부를 사용해 다음과 같은 리스트 쌍으로 분리한다:

  1. 술부가 true인 요소들
  2. 술부가 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
  1. 숫자 컬렉션을 정의한다.
  2. numbers를 짝수와 홀수 리스트 Pair로 나눈다.
  3. numbers를 양수와 음수 리스트로 나눈다. Pair의 멤버들을 얻기 위해 Pair 구조 분해(Pair destructuring)가 이루어졌다.

flatMap


flatMap은 컬렉션의 각 요소를 iterable 객체로 변환하다. 그리고 변환 결과의 단일 리스트를 만든다. 변환은 유저가 정의한다.

val fruitsBag = listOf("apple","orange","banana","grapes")  // 1
val clothesBag = listOf("shirts","pants","jeans")           // 2
val cart = listOf(fruitsBag, clothesBag)                    // 3
val mapBag = cart.map { it }                                // 4
val flatMapBag = cart.flatMap { it }                        // 5
  1. 과일 이름 문자열 컬렉션을 정의한다.
  2. 의류명 문자열 컬렉션을 정의한다.
  3. fruitsBagclothesBagcart 리스트에 넣는다.
  4. cart 요소들의 map을 만든다. 이것은 두 리스트의 리스트(두 리스트를 요소로 가지는)가 된다.
  5. cart 요소들의 flatMap을 만든다. 이것은 두 리스트에서 온 요소들로 구성된 단일 리스트가 된다.

minOrNull, maxOrNull


minOrNullmaxOrNull 함수는 컬렉션의 최소/최대 요소를 반환한다. 컬렉션이 비어 있다면 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
println("Empty: $empty, min = ${empty.minOrNull()}, max = ${empty.maxOrNull()}")        // 2
println("Only: $only, min = ${only.minOrNull()}, max = ${only.maxOrNull()}")            // 3
  1. 비어 있지 않은 컬렉션에서 함수는 최소/최대 요소를 반환한다.
  2. 비어 있는 컬렉션에서 두 함수는 모두 null을 반환한다.
  3. 요소가 하나 밖에 없는 컬렉션에서 두 함수는 같은 값을 반환한다.

sorted


sorted는 자연 정렬 순서(natural sort order) (오름차순)으로 정렬된 컬렉션 요소 리스트를 반환한다.

sortedBy는 지정된 선택자 함수(selector function)에 의해 반환된 값의 자연 정렬 순서에 따라 요소를 정렬한다.

val shuffled = listOf(5, 4, 2, 1, 3, -10)                   // 1
val natural = shuffled.sorted()                             // 2
val inverted = shuffled.sortedBy { -it }                    // 3
val descending = shuffled.sortedDescending()                // 4
val descendingBy = shuffled.sortedByDescending { abs(it)  } // 5
  1. 뒤섞인 숫자 컬렉션을 정의한다.
  2. 자연 순서로 정렬한다.
  3. 선택자 함수로 -it을 사용해 뒤집어진 자연 순서(inverted natural order)로 정렬한다.
  4. sortedDescending을 사용해 뒤집어진 자연 순서로 정렬한다.
  5. 선택자 함수로 abs(it)을 사용해 아이템 절대값의 뒤집어진 자연 순서로 정렬한다.

Map Element Access


맵에 적용하면 [] 연산자는 주어진 키에 대응하는 값을 반환한다. 맵에 해당 키가 없다면 null을 반환한다.

getValue 함수는 주어진 키로 대응하는 값을 반환한다. 키를 찾을 수 없다면 예외를 던진다. withDefault로 생성된 맵에는 getValue는 예외 대신 기본 값을 반환한다.

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"에 대응되는 값이 42이므로 42를 반환한다.
  2. "key2"가 맵에 없으므로 null을 반환한다.
  3. "key2"가 부재하므로 기본 값을 반환한다. 이 키에게 기본 값은 4이다.
  4. "anotherKey"가 맵에 없으므로 NoSuchElementException을 반환한다.

zip


zip 함수는 주어진 두 컬렉션을 새 컬렉션으로 병합한다(Merge). 기본적으로 결과 컬렉션은 동일한 인덱스로 소스 컬렉션 요소들의 Pair를 가진다. 하지만 결과 컬렉션 요소 구조를 정의할 수 있다.

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

val resultPairs = A zip B                      // 2
val resultReduce = A.zip(B) { a, b -> "$a$b" } // 3
  1. 두 컬렉션을 정의한다.
  2. pair 리스트로 둘을 병합한다. infix notation이 사용되었다.
  3. 주어진 변환에 따라 문자열 값 리스트로 병합된다.

getOrElse


getOrElse는 컬렉션 요소에 대한 안전한 접근을 제공한다. 이것은 인덱스와, 인덱스가 out of bound일 경우 기본 값을 제공하는 함수를 가지고 있다.

val list = listOf(0, 10, 20)
println(list.getOrElse(1) { 42 })    // 1
println(list.getOrElse(10) { 42 })   // 2
  1. 인덱스 1에 있는 요소를 프린트한다.
  2. 인덱스 10은 out of bounds이므로 42를 프린트한다.

getOrElse는 주어진 키로 값을 얻기 위해 맵에도 사용할 수 있다.

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"가 없으므로 기본 값을 출력한다.
  2. 키 "x"에 대한 값 3을 출력한다.
  3. 키 "x"에 대한 값이 정의되지 않았으므로 기본 값을 출력한다.

0개의 댓글