본문 바로가기

Study/Kotlin

Kotlin / 컬렉션 알아보기

반응형

1. 리스트, 세트, 맵 알아보기

컬렉션의 가변과 불변

  • 가변(mutable) : 보통 컬렉션 객체는 추가, 수정, 삭제를 할 수 있다
  • 불변(immutable) : 컬렉션 객체 중에 한 번 만들어지면 추가, 수정, 삭제를 할 수 없다.

리스트 클래스

  • ArrayList : 고정 길이로 리스트를 만든다. listOf, mutableListOf로 객체 생성
  • LinkedList : 가변 길이로 리스트를 만든다. 각 원소가 가지는 주소를 별도로 보관. LinkedList 클래스로 리스트 객체 생성

 

val list1 = listOf(1,2,3) // 불변 리스트 객체 생성
val list2 = mutableListOf(1,2,3) // 가변 리스트 객체 만들기

list2.add(10)  // 원소 추가
list2.removeAt(1) // 원소 삭제
list2[0] = 0 // 원소 대체

 

가변리스트는 원소를 추가하거나 삭제 가능하다.

변수에 자료형을 지정하지 않으면 타입추론으로 자료형을 확장한다.

 

val linkedList = LinkedList<String>()
linkedList.addAll(listOf("지구","금성","화성")) // 링크드리스트에 원소 추가

println(linkedList) // [지구, 금성, 화성]
println("First planet = ${linkedList.first}") // First planet = 지구

val joinedPlanets = linkedList.plus(linkedList)
println(joinedPlanets) // [지구, 금성, 화성, 지구, 금성, 화성]

 

빈 링크드리스트를 객체로 만들고 리스트 객체를 addAll 메서드로 추가했다.

링크드 연산자나 메서드로 객체를 결합할 수 있다.

불변 리스트처럼 원본을 바꾸지 않고 새로운 리스트 객체를 만들어 반환할 수 있다.

 

val emptyList: List<String> = emptyList<String>() // 아무것도 없는 리스트 생성
val nonNullsList = listOfNotNull(2, 45, null, 5, null) // null 제거 리스트 생성

println(emptyList) // []
println(nonNullsList) // [2, 45, 5]

 

아무것도 없는 리스트는 emptyList 함수로 만든다.

널 값이 없는 리스트를 만들때는 listOfNotNull 함수를 사용한다.

 

 

집합(Set) 클래스

순서가 없고 모든 원소는 유일한 값을 가진다. (집합의 종류도 해시를 기반으로 처리)

  • HashSet : HashMap을 이용하여 구현되고, value는 dummy object로 사용된다.
  • LinkedHashSet : LinkedHashMap을 이용하여 구현되고, value는 dummy object로 사용된다.
  • TreeSet : TreeMap으로 구현되고, value는 dummy object로 사용된다.

 

val intHSet: HashSet<Int> = hashSetOf(1, 2, 6, 3) // 해시 집합 생성
intHSet.add(5) // 원소 추가
intHSet.remove(1)

val intMSet: MutableSet<Int> = mutableSetOf(3, 5, 6, 2, 0) // 가변집합 생성
intMSet.add(8) // 원소 추가
intMSet.remove(3) // 원소 삭제

val intLSet: LinkedHashSet<Int> = linkedSetOf(5,2,7,2,5)
intLSet.add(4) // 원소 추가
intLSet.remove(2) // 원소 삭제
intLSet.clear() // 원소 전체 삭제
 
val treeSet = TreeSet<String>()
treeSet.addAll(listOf("화성","금성"))

val treeSet2 = TreeSet<String>()
treeSet2.addAll(listOf("화성","지구"))

treeSet.retainAll(treeSet2) // 교집합된 원소만 유지
println(treeSet) // [화성]

 

가변 집합 (hashSetOf, mutableSetOf, linkedSetOf)를 생성하고 add, remove 등의 메서드로 원소를 추가,삭제할 수 있다.

clear는 전체 삭제 메서드

TreeSet은 클래스로 빈 집합을 만든다. addAll 메서드로 집합을 추가할 수 있다.

retainAll 메서드는 두 집합의 교집합으로만 현재 집합을 갱신한다.

 

맵 클래스

유일 값을 갖는 키와 이 키에 대응하는 값으로 구성된다.

Key는 문자열 등의 불변 객체만 올 수 있고, Value는 모든 객체를 다 저장할 수 있다.

 

val tomcat = "Tom" to "cat" // 두 개의 원소를 가진 튜플 만들기
val m = hashMapOf( // 튜플로 맵 생성
    Pair("Tom", "cat"),
    Pair("Jerry", "Mouse")
)
val map1 = linkedMapOf("1" to "one") // 튜플 연산자로 맵 생성

 

맵의 원소는 키와 값으로 구성해야 한다. -> to 메서드로 키와 값을 한 쌍으로 묵는다. (Pair 클래스의 객체)

 

val map1 = linkedMapOf("1" to "one") // 튜플 연산자로 맵 생성

val callingMap: Map<Int, String> = mapOf(82 to "한국", 1 to "USA")
for ((key, value) in callingMap) {
    println("$key 코드는 어느나라 $value")
}

val currencyMap: MutableMap<String, String> = mutableMapOf("원" to "한국")
println("국가 = ${currencyMap.values}") // 국가 = [한국]

currencyMap["엔"] = "일본"

println(currencyMap.remove("원")) // 한국
println(currencyMap.values) // [일본]

 

mapOf 함수로 불변인 맵을 생성할 수 있다.

변경 가능한 맵은 mutableMapOf 함수로 객체를 만든다.

 

스택(Stack)

하나의 출입구만 있으므로 차례로 스택에 쌓이고 마지막에 쌓인 것을 꺼낸다.

  • pupsh : 마지막 스택에 데이터를 가져온다. (스택에 쌓음)
  • pop : 마지막 스택에 쌓인 데이터를 꺼내온다. (스택에서 제거)
  • peek : 스택의 상태를 알려준다. (스택의 최상단에 있는 데이터를 알려줌)

코틀린에서는 별도의 스택을 제공하지 않지만, 자바에 있는 스택을 활용할 수 있다.

큐(queue)

입구와 출구가 있는 자료구조. 데이터를 저장하는 입구와 데이터를 꺼내는 출구가 다르다.

FIFO(First In First Out) 선입선출 방식이다.

 

자바의 큐 인터페이스와 링크드리스트로 큐를 만들 수 있다.

 

2. 컬렉션 메서드 알아보기

컬렉션 상속구조 알아보기

코틀린의 컬렉션의 메서드는 상속관계에 따라서 제공된다.

모두 kotlin.collections 패키지 내부에 존재하며, Iterable을 기본적으로 상속받는다.

 

https://kotlinlang.org/docs/collections-overview.html#collection-types

 

불변 컬렉션의 경우 Iterable > Collection > List 순으로 구조화

가변 컬렉션의 경우 Iterable > MutableIterable > MutableCollection > MutableList 순으로 구조화

 

검색과 조건검사

컬렉션 상태 확인

 

val list = listOf("a","b","c") //리스트 객체 생성
val set1 = setOf(1,2,3)
val map = mapOf("a" to 100, "b" to 300)

println(list.size) // 원소 개수
println(list.isEmpty()) // 원소가 있는지 확인
println(list.contains("B")) // 포함 여부
println(list.containsAll(list)) // 전체가 다 포함되었는지

// 3
// false
// false
// true

 

컬렉션 내부 순환

  • forEach 메서드로 처리. 인자로 람다 표현식을 받는다.
  • 인덱스와 값 가져와서 처리 -> forEachIndexed메서드

 

list.forEach { print("$it,") } // 순환 처리
println()
list.forEachIndexed { index, value -> print("$index = $value, ") } // 인덱스 값과 같이 순환
println()
map.forEach{(key,value) -> print("$key = $value")} // 키와 밸류에 대한 순환처리

// a,b,c,
// 0 = a, 1 = b, 2 = c,
// a = 100b = 300

 

조건 검사

  • 람다 표현식으로 인자를 받아서 리스트나 맵의 모든 원소를 비교해서 논리값으로 반환한다.

 

println("list any : "+ list.any{it.length > 3}) // list any : false
println("list all : "+ list.all{it>"a"}) // list all : false
println("list any : "+ list.none{it > "a"}) // list any : false
println("list any : "+ map.any(){it.key.length>3}) // list any : false

 

정렬

  • sorted, reversed : 정렬 후 새로운 객체로 반환
  • sort, reverse : 정렬 -> 내부 변경

 

val mList = mutableListOf("나","가","다")
println("mList : $mList")
println("정렬해서 새객체 : ${mList.sorted()}")
println("반대로 정렬 새객체 : ${mList.reversed()}")
println("mList : $mList")
/*--------------*/
val mCopyList = mList.toMutableList() // 객체를 다시 처리하면서 복사
println("mCopyList : $mCopyList")

mCopyList.sort()
println("정렬 후 내부 변경 : $mCopyList")

mCopyList.reverse()
println("반대로 정렬 후 내부 변경 : $mCopyList")

// mList : [나, 가, 다]
// 정렬해서 새객체 : [가, 나, 다]
// 반대로 정렬 새객체 : [다, 가, 나]
// mList : [나, 가, 다]

// mCopyList : [나, 가, 다]
// 정렬 후 내부 변경 : [가, 나, 다]
// 반대로 정렬 후 내부 변경 : [다, 나, 가]

 

 

맵 리듀스 처리

컬렉션은 여러 원소를 가진다. 원소를 처리하기 위해 순환 등의 기능을 하는 메서들르 만들고 실제 특정 처리는 별도의 함수를 전달받아서 한다.

  • 맵(map) : 컬렉션을 순환하면서 모든 원소를 변환하고 컬렉션을 반환한다. 변환하는 로직을 람다표현식으로 받는다.
  • 필터(filter) : 컬렉션을 순환하면서 람다표현식으로 전달된 조건을 점검해서 true인 원소를 추출해서 반환한다.
  • 리듀스(reduce) : 컬렉션을 순환하면서 모든 원소를 람다표현식으로 연산해서 최종 결과를 일반적인 자료형 값으로 반환한다.
  • 폴드(fold) : 리듀스와 동일하지만 초깃값 인자를 더 받아서 초깃값부터 람다표현식으로 연산한다.

 

val cities = listOf("Seoul","Tokyo")
cities.map { str: String -> str.uppercase() }.forEach{print(it)} // SEOULTOKYO
println()

cities.filter{it.contains("S")}.forEach{println(it)} // Seoul

val amounts = listOf(1,2,3,4,5)
println(amounts.reduce{total,x -> total+x}) // 15

 

그룹 연산

컬렉션을 처리할 때 특정 원소를 기준으로 동일한 범주로 묶어서 처리할 수 있다.

 

val cities1 = listOf("제주", "서울", "상하이")
val keySelector = { city: String ->
    if (city.length == 3) "A" else "B"
}
cities1.groupBy(keySelector) // 함수 전달해서 그룹화 처리
    .forEach { (key, value) -> // 맵으로 구성된 결과를 출력
        println("$key : $value")
    }

cities1.groupBy { if (it.length == 3) "A" else "B" } // 람다를 바로 전달해서 처리
    .forEach { (key, value) ->
        println("$key : $value")
    }
    
// B : [제주, 서울]
// A : [상하이]
// B : [제주, 서울]
// A : [상하이]

 

시퀀스(Sequence)

컬렉션은 두 가지 방식으로 처리한다. (즉시 실행 / 지연 실행)

지연 처리하는 방식인 시퀀스를 알아보자!

지연 처리의 기준은 특정 액션을 만나면 그때 모든 것을 처리한다.

 

val seq = sequenceOf(1,2,3,4,5) // 시퀀스 생성
println("seq 원소 개수 : ${seq.count()}") // 실행 연산을 해야 처리된 결과를 보여준다
seq.forEach{print("$it , ")} // 실행해서 원소 출력
println()

val numList = listOf(1,2,3,4,5) // 리스트 생성
val seq2 = numList.asSequence() // 시퀀스로 변환
println("두 시퀀스 객체 비교 : ${seq == seq2}") // 두 객체 비교
println("seq2 원소개수 : ${seq2.count()}")

// seq 원소 개수 : 5
// 1 , 2 , 3 , 4 , 5 , 
// 두 시퀀스 객체 비교 : false
// seq2 원소개수 : 5

 

시퀀스는 최종 처리할 수 있는 메서드를 만나지 못하면 시퀀스 객체 상태를 유지한다. -> 실행 메서드를 호출하면 최종 결과를 반환

 

val words = "가나 다라마바 사아자차".split(" ")
iterableCheck(words)

val wordsSeq = words.asSequence()
seqCheck(wordsSeq)

fun iterableCheck(words: List<String>) {
    val lengthsList = words
        .filter {
            println("iterable filter : $it")
            it.length > 3
        }
        .map {
            println("iterable 길이 확인 : ${it.length}")
            it.length
        }
    println("----- iterable 처리된 결과 출력 -----")
    println(lengthsList)
}

fun seqCheck(words: Sequence<String>) {
    val lengthsSequence = words
        .filter {
            println("seq filter : $it")
            it.length > 3
        }
        .map {
            println("seq 길이 확인 : ${it.length}")
            it.length
        }
    println("----- seq 처리된 결과 출력 -----")
    println(lengthsSequence.toList())
}

// iterable filter : 가나
// iterable filter : 다라마바
// iterable filter : 사아자차
// iterable 길이 확인 : 4
// iterable 길이 확인 : 4
// ----- iterable 처리된 결과 출력 -----
// [4, 4]
// ----- seq 처리된 결과 출력 -----
// seq filter : 가나
// seq filter : 다라마바
// seq 길이 확인 : 4
// seq filter : 사아자차
// seq 길이 확인 : 4
// [4, 4]

 

  • 일반 리스트 처리 : 메서드 단위를 모두 처리 후 다음 메서드 처리됨
  • 시퀀스 처리 : toList 메서드가 실행될 때까지 처리하지 않는다. toList 메서드가 실행될 때 전체 로직이 실행된다.