PS 문제를 하나씩 풀다 보니, 공부가 필요하다고 느낀 문법을 정리한 글입니다.
Kotlin의 Collection은 여러 데이터를 편하게 다루기 위해 제공되는 컬렉션 라이브러리입니다.
여러 요소를 하나의 그룹으로 묶어서 관리할 수 있도록 도와주며,
요소 조회, 반복, 포함 여부 확인 같은 공통 작업을 수행할 수 있는 인터페이스를 제공합니다.
Kotlin 컬렉션은 크게 읽기 전용 컬렉션과 변경 가능한 컬렉션으로 나뉩니다.
예를 들어 Collection<T>는 읽기 전용 컬렉션의 기본 인터페이스이고,
MutableCollection<T>는 여기에 쓰기 기능을 추가한 인터페이스입니다.
fun printAll(strings: Collection<String>) {
for (s in strings) print("$s ")
println()
}
fun main() {
val stringList = listOf("one", "two", "one")
printAll(stringList)
val stringSet = setOf("one", "two", "three")
printAll(stringSet)
}
위 예시는
Collection<T>가 컬렉션의 공통 동작을 제공한다는 점을 보여줍니다.
리스트와 셋은 서로 다른 자료구조이지만, 둘 다Collection의 형태로 다룰 수 있기 때문에
같은 함수에 전달하여 사용할 수 있습니다.
또한 컬렉션은 Iterable<T>를 상속하므로 for 문으로 순회할 수 있고,
MutableCollection<T>는 add, remove처럼 값을 변경하는 작업도 가능합니다.
List는 순서가 있는 컬렉션입니다.
각 요소는 인덱스(위치를 나타내는 정수)로 접근할 수 있으며, 같은 값이 여러 번 들어갈 수도 있습니다.
즉, List는 다음과 같은 특징을 가집니다.
fun main() {
val numbers = listOf("one", "two", "three", "four")
println("리스트 크기 : ${numbers.size}")
println("세 번째 요소 : ${numbers.get(2)}")
println("네 번째 요소 : ${numbers[3]}")
println("\"two\"의 인덱스는? ${numbers.indexOf("two")}")
}
위 예제를 보면 인덱스가 0부터 시작하기 때문에
세 번째 요소를 가져오려면2를 사용해야 한다는 것을 알 수 있습니다.
또한size는 현재 리스트에 들어 있는 전체 요소 개수를 반환합니다.
data class Person(var name: String, var age: Int)
fun main() {
val bob = Person("Bob", 31)
val people1 = listOf(Person("Adam", 28), bob, bob)
val people2 = listOf(Person("Adam", 28), Person("Bob", 31), bob)
println(people1 == people2)
bob.age = 32
println(people1 == people2)
}
이 예제는
List가 순서와 각 위치의 값을 기준으로 비교된다는 점을 보여줍니다.
리스트는 같은 객체를 여러 번 담을 수 있으므로 중복도 허용됩니다.
그리고 people1 == people2는 단순히 참조가 같은지를 보는 것이 아니라,
각 위치의 요소가 구조적으로 같은지를 비교합니다.
따라서 리스트에서는 무엇이 들어 있는지뿐 아니라 어떤 순서로 들어 있는지도 중요합니다.
Set은 중복을 허용하지 않는 컬렉션입니다.
수학에서의 집합과 비슷하게, 같은 값은 한 번만 저장됩니다.
즉, Set의 특징은 다음과 같습니다.
fun main() {
val numbers = setOf(1, 2, 3, 4)
println("요소 개수: ${numbers.size}")
if (numbers.contains(1)) {
println("1은 set 안에 포함되어 있습니다.")
}
val numbersBackwards = setOf(4, 3, 2, 1)
println("두 set가 같은가? : ${numbers == numbersBackwards}")
}
Set<T>는 고유한 요소만 저장하기 때문에,
순서가 다르더라도 같은 요소를 가지고 있다면 같은 집합으로 판단됩니다.
그래서 위 예제에서는true가 출력됩니다.
println(numbers.first() == numbersBackwards.first())
println(numbers.first() == numbersBackwards.last())
다만 여기서는 한 가지 주의할 점이 있습니다.
Set의 동등성 비교는 순서를 보지 않지만,first()나last()는 순회 순서의 영향을 받습니다.
즉, numbers == numbersBackwards는 true일 수 있지만,
first()와 last()의 결과는 실제 저장 순서나 구현 방식에 따라 달라질 수 있습니다.
그래서 Set에서는 같은 요소를 가지고 있는가와 어떤 순서로 순회되는가를 구분해서 봐야 합니다.
Map은 키(key)와 값(value)의 쌍으로 이루어진 컬렉션입니다.
각 키는 고유해야 하며, 하나의 키는 하나의 값에 대응됩니다.
즉, Map의 특징은 다음과 같습니다.
fun main() {
val numbersMap = mapOf(
"key1" to 1,
"key2" to 2,
"key3" to 3,
"key4" to 1
)
println("모든 키들 : ${numbersMap.keys}")
println("모든 값들 : ${numbersMap.values}")
if ("key2" in numbersMap) {
println("key2의 값은? ${numbersMap["key2"]}")
}
if (1 in numbersMap.values) {
println("값 1은 map 안에 있습니다!")
}
if (numbersMap.containsValue(1)) {
println("값 1은 map 안에 있습니다!")
}
}
위 예제를 보면
key1과key4의 값이 모두1인 것을 확인할 수 있습니다.
즉, 키는 고유해야 하지만 값은 중복될 수 있습니다.
또한 Map은 특정 키에 대응하는 값을 조회하는 데 적합하기 때문에,
예를 들어 직원 ID와 직원 정보, 상품 코드와 가격처럼
서로 연결된 데이터를 다룰 때 유용합니다.
val numbersMap = mapOf("key1" to 1, "key2" to 2, "key3" to 3, "key4" to 1)
val anotherMap = mapOf("key2" to 2, "key1" to 1, "key4" to 1, "key3" to 3)
println("두 맵들은 같은가? : ${numbersMap == anotherMap}")
위 예제를 보면 키와 값은 같지만 입력한 순서가 다릅니다.
그럼에도true가 출력되는 이유는Map의 동등성 비교가
키-값 쌍이 같은지를 기준으로 하기 때문입니다.
즉,Map역시 비교 자체에서는 순서가 중요하지 않습니다.
fun main() {
val numbersMap = mutableMapOf("one" to 1, "two" to 2)
numbersMap.put("three", 3)
numbersMap["one"] = 11
println(numbersMap)
}
위 예제는
mutableMapOf를 사용하여 수정 가능한Map을 만드는 예제입니다.
put()으로 새로운 키-값 쌍을 추가할 수 있고,
이미 존재하는 키에 새로운 값을 넣으면 기존 값이 변경됩니다.
이번 글에서는 Kotlin의 Collection에 대해 정리해보았습니다.
프로그램에서는 단순한 변수 하나만 다루는 경우보다, 여러 값을 묶어서 관리해야 하는 경우가 훨씬 많습니다.
이때List,Set,Map같은 컬렉션을 상황에 맞게 선택하면 데이터를 더 효율적으로 다룰 수 있습니다.
다음 글에서는 이러한 Collection을 실제로 다룰 때 자주 사용하는 함수들도 함께 정리해보려고 합니다.