`Set` 객체
이 문서에서는 Set 객체에 대해 설명합니다.
실용적인 예시와 함께 Set 객체를 설명하겠습니다.
YouTube Video
Set 객체
Set은 고유하고 중복되지 않는 값의 집합을 다루기 위해 사용되는 내장 객체입니다. 배열보다 더 간단하게 중복 제거와 존재 여부 확인을 할 수 있으며, 합집합과 교집합 같은 집합 연산을 쉽게 구현할 수 있습니다.
기초: Set 생성 및 사용 방법
먼저, Set을 생성하고 요소를 추가/삭제하는 방법, 존재 여부 확인 및 크기 확인 방법을 알아보겠습니다.
아래는 새로운 Set을 만들고, add, has, delete, size를 사용하는 기본적인 예시입니다.
1// Create a Set and demonstrate add, has, delete, and size
2const s = new Set();
3
4s.add(1);
5s.add(2);
6s.add(2); // duplicate, ignored
7
8console.log(s.has(1)); // true
9console.log(s.has(3)); // false
10
11s.delete(2);
12console.log(s.size); // 1
13
14console.log([...s]); // [1]
- 이 코드에서 보듯이,
Set은 기본값의 중복을 자동으로 제거하며,size를 사용하여 요소의 개수를 얻을 수 있습니다.
반복(순회) 방법
Set은 반복(iterable) 가능하므로 for...of나 forEach로 순회할 수 있습니다. 순서는 삽입된 순서를 따릅니다.
for...of와 forEach를 사용하는 대표적인 방법은 다음과 같습니다.
1// Iterate a Set with for...of and forEach
2const s = new Set(['a', 'b', 'c']);
3
4for (const v of s) {
5 console.log('for...of:', v);
6}
7
8s.forEach((value, sameValue, setRef) => {
9 // Note: second arg is same as first for Set API to match Map signature
10 console.log('forEach:', value);
11});forEach의 콜백은(value, value, set)형식(호환성을 위해 Map과 동일)이지만, 실제로는 첫 번째value만 사용하는 경우가 대부분입니다.
배열과 Set 간의 변환(중복 제거에 유용)
여기서는 배열에서 중복을 제거하는 간단한 방법과, Set을 다시 배열로 변환하는 방법을 소개합니다.
아래는 배열을 Set에 통과시켜 중복을 제거하는 예시입니다.
1// Deduplicate an array using Set
2const arr = [1, 2, 2, 3, 3, 3];
3const deduped = [...new Set(arr)];
4console.log(deduped); // [1, 2, 3]
5
6// Convert a Set to an array using Array.from
7const s = new Set([4, 5, 6]);
8const arrFromSet = Array.from(s);
9console.log(arrFromSet); // [4, 5, 6]
- 이 패턴은 짧고 빠르므로, 배열에서 중복을 제거할 때 자주 사용됩니다. 특히 기본형 값에 효과적입니다.
객체와 참조 처리
Set에 있는 객체는 참조로 비교하므로, 내용이 같더라도 다른 인스턴스는 별개의 요소로 취급됩니다.
아래 코드는 객체를 Set에 추가할 때 어떤 일이 일어나는지 보여줍니다.
1// Objects are compared by reference in a Set
2const obj1 = { x: 1 };
3const obj2 = { x: 1 };
4
5const s = new Set();
6s.add(obj1);
7s.add(obj2);
8
9console.log(s.size); // 2 (different references)
10console.log(s.has(obj1)); // true
11console.log(s.has({ x: 1 })); // false (different object)
- 객체의 중복 검사는 참조 일치(동일성)에 기초하므로, 내용만으로 중복을 제거하고 싶다면 직렬화 등 별도의 처리가 필요합니다.
특수 값: NaN 및 -0/+0의 처리
Set은 값의 동등성을 결정할 때 Same-value-zero 비교 규칙을 사용합니다. 이 비교 방법은 숫자에 대해 다음과 같은 특징이 있습니다:.
NaN은NaN과 같다고 간주됩니다.- 양수
+0과 음수-0은 구분되지 않으며 동일한 값으로 처리됩니다.
따라서 이러한 값을 Set에 추가하면 다음과 같은 동작이 나타납니다:.
1// NaN and zero behavior in Set
2const s = new Set();
3
4s.add(NaN);
5s.add(NaN);
6console.log(s.size); // 1 (NaN considered the same)
7
8s.add(+0);
9s.add(-0);
10console.log(s.size); // still 2 (NaN + 0)
11console.log([...s]); // [NaN, 0] (order may vary but only one zero)
- 일반 비교(
NaN === NaN)에서는false가 반환되지만,Set내에서는NaN값이 모두 '같은 값'으로 처리됩니다. - +0과 -0은 수학적으로 구분할 수 있지만,
Set에서는 단순히0으로 간주됩니다. - 결과적으로,
Set에는NaN한 개와0한 개만 남습니다. Set의 비교 규칙은Object.is와 유사하지만 완전히 동일하지는 않습니다.Object.is(+0, -0)는false를 반환하지만,Set에서는 동일한 값으로 처리됩니다. 이 차이에 유의하세요.
일반적인 활용: 집합 연산(합집합, 교집합, 차집합)
Set을 사용하면 집합 연산을 더 쉽게 작성할 수 있습니다. 아래는 일반적인 구현 예시입니다.
union, intersection, difference 함수의 예시는 다음과 같습니다.
1// Set operations: union, intersection, difference
2function union(a, b) {
3 return new Set([...a, ...b]);
4}
5
6function intersection(a, b) {
7 return new Set([...a].filter(x => b.has(x)));
8}
9
10function difference(a, b) {
11 return new Set([...a].filter(x => !b.has(x)));
12}
13
14// Demo
15const A = new Set([1, 2, 3]);
16const B = new Set([3, 4, 5]);
17
18console.log('union', [...union(A, B)]); // [1,2,3,4,5]
19console.log('intersection', [...intersection(A, B)]); // [3]
20console.log('difference A\\B', [...difference(A, B)]); // [1,2]
Set과 배열을 조합하여 필터를 사용하면 집합 연산을 쉽게 구현할 수 있습니다. 대용량 데이터 처리 시,has의 O(1) 성능 덕분에 연산이 더 빨라집니다.
실전 예시: 배열 차이 찾기(추가/삭제된 요소 감지)
아래 예시는 Set을 이용해 두 배열(이전 목록과 새 목록)의 차이를 찾는 방법을 보여줍니다. 이를 통해 어떤 요소가 추가되었고 어떤 요소가 삭제되었는지 확인할 수 있습니다.
1// Find added and removed items between two arrays
2function diffArrays(oldArr, newArr) {
3 const oldSet = new Set(oldArr);
4 const newSet = new Set(newArr);
5
6 const added = [...newSet].filter(x => !oldSet.has(x));
7 const removed = [...oldSet].filter(x => !newSet.has(x));
8
9 return { added, removed };
10}
11
12const oldList = [1, 2, 3];
13const newList = [2, 3, 4, 5];
14
15console.log(diffArrays(oldList, newList));
16// { added: [4,5], removed: [1] }
- 이 방법은 ID 목록, 태그 목록 등에서 차이를 감지할 때 매우 편리합니다. 기본형 값에서 사용하기 가장 간단합니다.
WeakSet과 Set의 차이점(메모리 관리)
WeakSet은 Set과 유사하지만, 약한 참조(weak reference)를 사용하여 내부의 항목들이 가비지 컬렉션될 수 있습니다. 아래는 WeakSet의 기본적인 사용 예시입니다.
1// WeakSet basics (objects only, not iterable)
2const ws = new WeakSet();
3let obj = { id: 1 };
4ws.add(obj);
5
6console.log(ws.has(obj)); // true
7
8obj = null; // Now the object is eligible for GC; WeakSet won't prevent collection
WeakSet은 오직 객체만 저장할 수 있으며, 반복(iteration)이 불가능합니다. 아래는 WeakSet의 제약 예시입니다—객체만 저장할 수 있고, 반복이 불가능합니다.
1// WeakSet basics (objects only, not iterable)
2const ws = new WeakSet();
3
4// --- Only objects can be added ---
5try {
6 ws.add(1); // number
7} catch (e) {
8 console.log("Error: WeakSet can only store objects. Adding a number is not allowed.");
9}
10
11try {
12 ws.add("text"); // string
13} catch (e) {
14 console.log("Error: WeakSet can only store objects. Adding a string is not allowed.");
15}
16
17// --- WeakSet is not iterable ---
18try {
19 for (const value of ws) {
20 console.log(value);
21 }
22} catch (e) {
23 console.log("Error: WeakSet is not iterable. You cannot use for...of to loop over its elements.");
24}
25
26// --- Cannot convert to array ---
27try {
28 console.log([...ws]);
29} catch (e) {
30 console.log("Error: WeakSet cannot be converted to an array because it does not support iteration.");
31}
32
33// The object becomes eligible for garbage collection
34let obj = { id: 1 };
35ws.add(obj);
36obj = null;WeakSet은 객체의 존재 여부를 일시적으로 추적할 때 유용하지만, 요소를 열거하거나 크기를 확인할 수 없습니다.
성능 및 사용 시기 결정
Set을 사용할지 여부를 결정할 때는 성능 특성과 데이터의 특성을 이해하는 것이 중요합니다.
has,add,delete는 대체로 평균적으로 O(1) 수준의 성능을 보입니다. 따라서 존재 여부 확인 또는 중복 제거가 자주 필요한 상황에서는 배열보다Set이 더 유리합니다.- **객체 내용(값)**을 기준으로 중복을 제거하려는 경우 주의해야 합니다.
Set은 참조로 비교하므로, 값 기준 비교가 필요한 경우 ID나 다른 키 사용, 또는 객체를 원시 값으로 직렬화 후 Set 사용이 실용적인 방법입니다. Set은 소규모 또는 중간 규모의 컬렉션에서 코드의 가독성을 높이는 데 특히 유용합니다. 반면, 아주 많은 요소를 처리하거나 배열과 Set 간 변환이 잦다면 실제로 성능 측정과 테스트를 권장합니다.
자주 발생하는 실수
Set은 편리하지만, 사양을 제대로 알지 못하면 예상치 못한 동작에 당황할 수 있습니다. 다음은 주의해야 할 대표적인 점들입니다:.
- 객체는 참조로 비교되므로, 내용이 같아도 다른 객체는 중복으로 간주되지 않습니다.
Set은 삽입 순서를 유지하지만, 배열처럼 인덱스로 요소에 접근할 수는 없습니다. 인덱스로 접근하고 싶다면 먼저Set을 배열로 변환하세요.WeakSet은 열거할 수 없고, 오직 객체만 저장할 수 있습니다. 용도가 제한적이란 점을 유의하세요.NaN은 동일한 값으로 처리되고,+0과-0은 구분되지 않습니다. 이는 Same-value-zero 비교 규칙 때문입니다.
요약
Set은 고유한 값의 집합을 직관적으로 다룰 수 있는 편리한 자료구조입니다. 배열의 중복을 제거하거나, 빠른 존재 여부 검사, 합집합/교집합 등의 집합 연산을 간결하고 가독성 있는 코드로 구현할 수 있습니다.
한편, 객체는 참조로 비교되기 때문에 내용 기준 동등성 검사를 하려면 별도의 조치가 필요합니다.
이러한 특성을 이해하고 적절히 활용하면, Set은 코드의 가독성과 유지보수성을 높여주는 강력한 선택지가 될 수 있습니다.
위의 기사를 보면서 Visual Studio Code를 사용해 우리 유튜브 채널에서 함께 따라할 수 있습니다. 유튜브 채널도 확인해 주세요.