목표
- Set API에 대한 이해를 높인다.
- HashSet, LinkedHashSet을 생성하는 방법과 다루는 방법을 익힌다.
개요
Set은 중복을 허용하지 않는 자료구조로, 중복된 값이 들어오면 값이 입력이 되지 않는다.
Java에서 제공하는 HashSet의 종류는 두 가지이다. 하나는 HashSet이고, 다른 하나는 LinkedHashSet이다. 둘의 차이점은 HashSet은 요소의 순서를 저장하지 않고, LinkedHashSet은 요소의 순서를 저장한다는 점이다. Kotlin은 자바의 HashSet과 LinkedHashSet을 그대로 가져오는 형태로 만들어졌으며, Mutable Interface와 Immutable Interface를 제공하여 이 Set들을 조작할 수 있는지를 코드 단에서 결정한다.
아래서 HashSet에 값을 추가하는 예제를 살펴보자.
val hashSet = hashSetOf(1, 2, 3, 4, 5)
hashSet.add(-1)
hashSet.add(0)
hashSet.add(-3)
print(hashSet) // [-1, 0, 1, 2, -3, 3, 4, 5]
HashSet에서 -1, 0, -3을 요소로 add 했지만 뒤에 붙는 것이 아닌 어떤 것은 앞 쪽에 -3은 중간에 붙었다. 즉, 순서가 보장되지 않았다.
다음으로 LinkedHashSet에 값을 추가하는 예제를 살펴보자. 위와 똑같이 진행하였다.
val linkedHashSet = linkedSetOf(1, 2, 3, 4, 5)
linkedHashSet.add(-1)
linkedHashSet.add(0)
linkedHashSet.add(-3)
print(linkedHashSet) // [1, 2, 3, 4, 5, -1, 0, -3]
LinkedHashSet에서 -1, 0, -3 을 요소로 add 했고, 순서대로 뒤에 붙은 것을 볼 수 있다.
생성
개요
코틀린에서 Set은 setOf 메서드를, MutableSet은 mutableSetOf 메서드를 사용해서 생성한다. 하지만, Set과 MutableSet은 인터페이스일 뿐이다. 이 인터페이스를 사용하기 위해서는 setOf나 mutableSetOf가 인터페이스를 구현하는 클래스의 인스턴스를 만들어야 한다.
val set: Set<Int> = setOf(1,2,3)
val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3)
그러면 setOf 메서드와 mutableSetOf 메서드의 내부를 살펴보자.
먼저 setOf 메서드를 살펴보자. setOf는 vararg에 대한 toSet메서드를 이용해 Set을 생성한다. toSet메서는 LinkedHashSet을 이용하여 Set을 생성한다. 이를 통해 우리는 setOf에서 생성되는 Set은 LinkedHashSet이라는 것을 알 수 있다.
public fun <T> setOf(vararg elements: T): Set<T> = if (elements.size > 0) elements.toSet() else emptySet()
public fun <T> Array<out T>.toSet(): Set<T> {
return when (size) {
0 -> emptySet()
1 -> setOf(this[0])
else -> toCollection(LinkedHashSet<T>(mapCapacity(size))) // LinkedHashSet 으로 생성!
}
}
*setOf와 toSet은 다른 패키지에 있지만, 편의상 하나로 합쳐두었다
다음은 mutableSetOf 메서드를 살펴보자. mutableSetOf 또한 LinkedHashSet을 이용해 Set을 생성하는 것을 확인 할 수 있다.
public fun <T> mutableSetOf(vararg elements: T): MutableSet<T> = elements.toCollection(LinkedHashSet(mapCapacity(elements.size)))
즉, 코틀린의 setOf 메서드와 mutableSetOf는 순서가 보장되는 Set인 LinkedHashSet을 생성한다.
그렇다면 HashSet으로 Set을 생성하려면 어떻게 해야할까? 바로 위에서 나왔던 hashSetOf 메서드를 이용해야 한다. 이외의 모든 방법으로는 LinkedHashSet이 생성된다.
val hashSet = hashSetOf(1, 2, 3, 4, 5)
public fun <T> hashSetOf(vararg elements: T): HashSet<T> = elements.toCollection(HashSet(mapCapacity(elements.size)))
MutableSet 생성
자료구조가 LinkedHashSet인 MutableSet 생성
가장 대표적인 방법은 두가지가 있다.
- mutableSetOf() 메서드를 이용해 생성
val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3)
- Array를 toMutableSet() 메서드를 이용해 MutableSet으로 변환.
intArrayOf(1, 2, 3, 4, 5).toMutableSet()
Array(5) { it }.toMutableSet()
자료구조가 HashSet인 MutableSet 생성
HashSet을 만들기 위해서는 직접 hashSet임을 지정해주어야 한다.
- hashSetOf() 메서드를 이용해 생성
val hashSet : MutableSet<Int> = hashSetOf(1, 2, 3, 4, 5)
- Array를 toHashSet() 메서드를 이용해 MutableSet으로 변환
val intSet : MutableSet<Int> = intArrayOf(1, 2, 3, 4, 5).toHashSet()
val hashSet : MutableSet<Int> = Array(5) { it }.toHashSet()
Set 생성
자료구조가 LinkedHashSet인 Set 생성
MutableSet을 만드는 방법과 비슷하다. 대표적으로 두가지 방법으로 생성이 가능하다.
- setOf() 메서드를 이용해 생성
val set: Set<Int> = setOf(1, 2, 3)
- Array를 toSet() 메서드를 이용해 Set으로 변환
intArrayOf(1, 2, 3, 4, 5).toSet()
Array(5) { it }.toSet()
자료구조가 HashSet인 Set 생성
HashSet을 만들기 위해서는 직접 hashSet임을 지정해주어야 한다.
- hashSetOf() 메서드를 이용해 생성
val intSet : Set<Int> = intArrayOf(1, 2, 3, 4, 5).toHashSet()
val hashSet : Set<Int> = Array(5) { it }.toHashSet()
여기서 짚고 넘어가야 할 점은, Set이든 MutableSet이든 인터페이스에 불과하다는 사실이다. 구현된 자료 구조의 조작 방법만을 정의하는 인터페이스이며, 같은 방식으로 생성하더라도 변수의 타입만 Set, MutableSet으로 바꾸면 둘 사이를 왔다갔다할 수 있다.
값 접근 및 변경
Set은 Immutable 한 Interface이므로, 값에 대한 접근만 가능하다. 따라서 여기서는 Mutable Set의 값을 접근하고 변경하는 방법만 설명하겠다.
값 접근하기
Set은 순서가 없으므로, 특정 Index로 접근하는 것이 불가능하다. 따라서 iterator을 돌려 값에 접근해야 한다. Iterator가 구현되어 있으므로, 모든 Iterator 관련된 연산들의 연산이 가능하다.
val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3)
mutableSet.forEach {
print(it)
}
//123
for (i in mutableSet)
print(i)
//123
값 변경하기
값 추가
- 특정 element 추가하기 : 중복되는 값이 있는지 확인 후 값이 없는 경우에만 추가된다.
val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3)
mutableSet.add(3)
mutableSet.add(4)
print(mutableSet) // [1, 2, 3, 4]
- Set 간에 plus 연산하기 : Set 간에 plus연산이 가능하다.
val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3)
val mutableSet1 : MutableSet<Int> = mutableSetOf(3, 4, 5)
mutableSet += mutableSet1
print(mutableSet) // [1, 2, 3, 4, 5]
값 제거
- 특정 element를 제거하기 : 중복되는 값이 없으므로 제거하려는 element에 바로 접근하여 제거가 가능하다.
val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3)
mutableSet.remove(2)
print(mutableSet) // [1, 3]
- Set간에 minus 연산하기 : Set 간에 operator function인 minus(-)연산으로 제거가 가능하다.
val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3)
val mutableSet1 : MutableSet<Int> = mutableSetOf(3, 4, 5)
mutableSet -= mutableSet1
print(mutableSet) // [1, 2]
교집합
- 집합이므로 교집합(intersect) 또한 가능하다.
val mutableSet: MutableSet<Int> = mutableSetOf(1, 2, 3)
val mutableSet1 : MutableSet<Int> = mutableSetOf(3, 4, 5)
val intersectSet = mutableSet.intersect(mutableSet1)
print(intersectSet) // [3]