조각들

 

mutable/immutable

가변(mutable) 컬렉션: 보통 컬렉션 객체는 추가, 수정, 삭제를 할 수 있다.

불변(immutable) 컬렉션: 컬렉션 객체 중에 한 번 만들어지면 추가, 수정, 삭제를 할 수 없다. 하지만 불변 객체의 내부 원소에 대한 추가, 삭제, 수정 메서드를 가지면 기존 객체는 그대로 두고 새로운 객체를 만든다.

 

 

LinkedList

  • 가변 길이로 리스트를 만든다. 각 원소가 가지는 주소를 별도로 보관하므로 LinkedList라고 한다. 보통 LinkedList 클래스로 객체를 생성한다.
  • LinkedList는 연산자나 메서드로 객체를 결합할 수 있다. 불변 리스트처럼 원본을 바꾸지 않고 새로운 리스트 객체를 만들어 반환하는 것을 알 수 있다.

 

Q. ArrayList와 List의 차이가 궁금합니다. List는 인터페이스이자 더 상위 개념이고 이것을 구현할 때 ArrayList로도 할 수 있다는 정도로 알고 있는데 ArrayList에서 되는 게 List로 다 되는 것이면 무조건 List를 쓰는 게 확장성 측면에서 더 좋지 않을까요?

 

Q. List는 ArrayList와 LinkedList를 합친 개념인 것 같은데 일반적으로 list라고 표현하면 linkedList를 생각하면 될까요?

 

Q. listOf, arrayOf, arrayListOf 다 있는데 linkedListOf만 없습니다. listOf를 하면 linkedList로 생성이 되는 것인가요?

 

Q. mutableListOf()는 있는데 mutableArrayOf(), mutableArrayListOf()는 없습니다. array는 수정이 불가하여 mutable 자체가 성립될 수 없고 arrayList는 그 자체가 mutable하므로 따로 mutable 접두사로 붙지 않았다고 이해하고 넘겨도 될까요?

 

 

Set

set은순서가 없도 모든 원소는 유일한 값을 가진다.(중복이 허용되지 않는다.) set에서 가변과 불변 클래스가 별도로 제공해서 처리할 수 있다.

 

집합의 원소는 유일한 값을 처리하기 위해 hash 구성하는 값만 가지고 있다. 그래서 집합의 종류도 해시를 기반으로 처리하는 것을 알 수 있다.

 

  • HashSet: HashMap을 이용하여 구현되고 value는 dummy object로 사용된다.
  • LinkedHashSet: LinkedHashMap을 이용하여 구현되고 value는 dummy object로 사용된다.
  • TreeSet: TreeMap으로 구현되고 value는 dummy object로 사용된다.
  • 집합의 부분집합인지 확인은 containsAll 메서드로 확인할 수 있다.
  • 리스트에서는 distinct 메서드로 유일한 원소만 남길 수 있다.

 

 

Map

Map은 유일한 값을 갖는 키와 이 키에 대응하는 값으로 구성된다. 키를 만들 수 있는 객체는 문자열 등의 불변 객체만 올 수 있다. 하지만 값으로는 모든 객체를 다 저장할 수 있다.

 

Map도 가변(mutableMapOf)과 불변(mapOf)으로 나눌 수 있다.

 

Map도 해시는 기본이지만 트리 자료구조로 구성할 수 있다.

  • TreeMap: Tree라는 구조를 사용해서 만든 Map이다.
  • HashMap: Hash라는 기법을 사용해서 만든 맵이다.
  • LinkedHashMap: Hash 기법과 만들어진 키를 연결해서 만든 Map이다. HashMap과 다른 점은 기본적으로 data를 추가한 순서대로 보관한다는 것이다.
  • SortedMap: 일반적으로 트리 기반의 데이터 구조를 사용하여 키를 정렬하는 맵이다.

 

 

스택/큐/디큐

  • 코틀린에서는 별도의 스택을 제공하지 않지만 자바에 있는 스택을 활용할 수 있다.
  • 자바의 큐 인터페이스와 LinkedList로 큐를 만든다.
  • 양방향으로 처리할 수 있는 qeueue 자료구조가 deque이다.

 

 

컬렉션 상태 확인

맵일 때는 포함 여부를 containsKey, containsValue로 확인한다.

 

내부 순환할 때 인덱스와 값을 가져서 처리할 때는 forEachIndexed 메서드를 사용한다.

맵은 키와 값을 가지므로 forEach 순환일 때도 내부에 두 개의 인자를 처리한다.

 

map.forEach{(key, value) -> println("$key = $value")}

 

 

리스트와 집합 원소 조회

  • 컬렉션원 소 접근은 대괄호 연산자나 get 메서드가 기본이다.
  • 리스트의 첫 번째(first)와 마지막(last) 원소에 접근한다.
  • 원소의 값 조회(indexOf, lastIndexOf), 원소 인덱스로 조회(elemetAt)할 때는 null 값 처리(elementAtOrElse)로 처리한다.
  • 특정 값을 조회(find, findLast), 최솟값/최댓값(minOf, maxOf)으로 처리한다.

 

 

맵 원소 접근

zip 메서드는 2개의 변수를 하나의 쌍으로 만든다. 그리고 toTypedArray로 튜플을 가진 배열을 만든다.

 

맵도 대괄호 연산자로 원소를 조회한다. 리스트와 차이점은 인덱스가 아닌 키를 전달한다. 초깃값 처리는 getOrElse 메서드로 하고 getOrDefault는 없을 때 초깃값을 전달한다. 

 

Q. getOrElse와 getOrDefault의 차이점이 무엇인가요? 둘 다 값이 없을 때 대체값을 전달한다는 점에서 똑같아보입니다.

 

 

조건 검사

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

 

 

정렬

리스트, 집합, 맵에서는 새로운 객체정렬(sorted)역정렬(reversed)을 할 수 있다. 리스트는 새로운 리스트 객체를 반환한다. 집합에서 정렬하면 배열로 반환하므로 다시 집합으로 변경해야 한다. 또한 맵일 경우는 키와 값을 각각 정렬한다. 

val list = mutableListOf("a","b","c","d") 

println("정렬해서 새객체 : " + list.sorted()) //정렬
println("반대로 처리 새객체 : " + list.reversed()) //반대로 처리

//정렬해서 새객체 : [a,b,c,d]
//반대로 처리 새객체 : [d,c,b,a]

리스트일 경우는 내부 변경이 가능정렬(sort)역정렬(reverse)을 사용한다.

 

 

drop으로 원소를 꺼내고 삭제하기

  • 삭제 drop 메서드는 특정 정수 즉 인덱스 이후 삭제 처리한다.
  • 삭제 dropWhile은 람다표현식을 받아서 해당 조건을 만족하면 삭제한다.
  • 삭제 dropLast 메서드는 우측에서 정수 개수만큼 삭제한다.

 

 

take로 원소 꺼내기

drop 메서드는 원본 컬렉션을 유지하지 않지만 take는 특정 원소를 조회할 뿐 삭제하지 않는다.

특정 원소 가져오기는 take 메서드, 마지막 방향부터 처리하는 것은 takeLast로 처리한다.

람다표현식을 전달하는 메서드는 takeWhile과 반대 방향에서 람다표현식을 인자로 전달하는 takeLastWhile이 있다.

 

 

join해서 문자열 처리하기

리스트나 집합을 만들고 이를 joinToString을 통해 접두사와 접미사로 붙여서 꾸밀 수 있다.

StringBuilder로 처리할 때는 joinTo를 사용한다.

val sb = StringBuilder()
val numbers = setOf(1,2,3)
numbers.joinTo(sb, prefix = "[" , postfix = "]"
println(sb)

// [1, 2, 3]

* prefixpostfix는 문자열 요소를 결합할 때 결과 문자열의 시작과 끝에 추가되는 문자열을 지정하는 매개변수입니다. 이 매개변수를 사용하여 결과 문자열을 원하는 형식으로 래핑하거나 감싸는데 유용합니다

 

 

중복 data 없애기

  • 리스트는 distinct로 중복을 삭제할 수 있다.
  • 람다표현식을 받아서 중복을 삭제할 때는 distinctBy를 사용한다.
val list = listOf('a', 'A', 'b', 'B', 'A', 'a')
println(list.distinct()) //리스트 중복 제거
println(list.distinctBy { it.uppercaseChar() }) //문자를 대문자로 변경한 후 중복 제거

// [a, A, b, B]
// [a, b]

 

 

맵 리듀스

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

 

 

Context

메서드가 실행할 때 하나의 인자 처리인 it과 실제 객체인 this로 처리하는 컨텍스트를 구성한다.

  • 하나의 매개변수만 지정할 경우는 보통 it 예약어를 받아서 사용한다. 보통 map 메서드는 하나의 인자를 받으므로 it을 사용한다.
  • 확장함수로 지정하고 별도의 인자를 전달하지 않을 경우는 리시버 객체인 this를 사용한다.

 

 

맵과 필터 사용

var name = animals.filter { it.name.length == 3 }.map { it.name } //이름이 길이가 3인 경우만 추출하고 이름도 추출
println(name)

val filteredMap = numbersMap.filter { (key,value) -> key.endsWith("1") && value > 10 }
println(filteredMap)

// [지미미]
// {key11=11}

 

 

그룹 연산

특정값을 묶을 때는 맵(Map) 객체로 묶어서 반환하는 groupby 메서드와 그룹화하고 바로 연산으로 처리할 수 있는 groupBy 메서드를 사용한다. 

 

그룹화하면 기존 객체와 다른 그룹화된 객체를 반환한다. 그리고 이 그룹화된 객체로 연산을 처리한다.

 

그룹화에 필요한 람다표현식을 keySelector라고 한다. 그룹화할 대상을 키로 하고 실제 컬렉션의 원소를 값으로 처리하도록 만들기 때문이다. 

val cities = listOf("제주", "서울", "상하이", "베를린")

val keySelector = { city: String -> if(city.length ==3) "A" else "B"}

cities.groupBy(keySelector).forEach{ (key, value) -> println("$key : $value")}

// B : [제주, 서울]
// A : [상하이, 베를린]

 

그룹화 한 것을 맵에 저장할 수 있다. groupByTo 첫 번째 인자에 가변 맵을 지정하고 람다표현식으로 그룹화 로직을 전달하면 새로운 맵 객체에 그룹화된 것이 저장된다.

val grouped = animals.groupByTo(mutableMapOf()) { it.species } //맵에 그룹화시키기

 

 

시퀀스(Sequence)

컬렉션은 두 가지 방식 즉시 실행과 지연 실행으로 처리한다. 지연 처리의 기준은 특정 액션을 만나면 그때 모든 것을 처리한다. 시퀀스는 일반적인 컬렉션과 작동하는 방식에도 차이가 있다.

 

  • sequenceOf 함수로 시퀀스를 만들고 이 시퀀스의 건수를 확인해서 개별 원소를 출력한다.
  • 하나만 처리하고 현재 시퀀스를 중단하는 함수가 yield이고, 여러 원소를 가진 리스트 등을 받아서 이 원소가 끝날 때까지 처리하는 것이 yieldAll이다. yieldAll 내에 시퀀스 발생 함수를 지정해도 이 시퀀스 발생 함수가 종료할 때까지 처리한다.

 

 

일반 컬렉션과 시퀀스 처리방식 차이

  • 일반적인 컬렉션은 정적 처리이며 시퀀스는 동적 처리이다.
  • 리스트와 시퀀스를 인자로 받아 처리하는 함수를 두 개 정의한다.
  • 이 두 함수에 문자열을 전달해서 실행하면 처리된 결과는 동일하지만 처리하는 순서가 다르다는 것을 알 수 있다.
  • 일반 리스트를 처리할 때는 메서드 단위를 모두 처리한 후에 다음 메서드가 처리된다.

 


etc

  • 스프레이드 연산
  • any/all/none
  • 컬렉션이 동일한 메서드를 제공하지 않는 이유는 컬렉션의 상속 구조가 다르기 때문이다.
  • null 값이 없는 리스트를 만들 때는 listOfNotNull 함수를 사용한다. 인자로 들어온 null을 없애고 리스트를 만든다.
  • Array는 메모리 상에 데이터가 연속적으로 저장되고, List는 메모리 상에 데이터가 비연속적으로 저장된다. https://ongveloper.tistory.com/403

 

'Kotlin > 스터디' 카테고리의 다른 글

코틀린 스터디 - 10장  (0) 2023.09.17
코틀린 스터디 - 9장  (1) 2023.09.10
코틀린 스터디 - 6장  (0) 2023.08.20
코틀린 스터디 - 5장  (0) 2023.08.13
코틀린 스터디 - 4장  (0) 2023.08.06