Javascript Set과 Map

fgStudy·2022년 5월 9일
1

자바스크립트

목록 보기
15/26
post-thumbnail

자바스크립트로 해시를 구현하기 위해 자료구조 컬렉션인 Set과 Map이 사용된다. 본문은 Set과 Map 컬렉션의 특징과 문법을 설명하고자 한다.


1. Set

Set 객체의 특징

Set 객체는 중복되지 않는 유일한 값들의 집합이다.

Set 객체는 배열과 다음과 같은 차이점이 있다.

구분배열Set 객체
동일한 값을 중복해서 포함할 수 있다.OX
요소 순서에 의미가 있다.OX
인덱스로 요소에 접근할 수 있다.OX

Set은 수학적 집합을 구현하기 위한 자료구조이다. 따라서 Set을 통해 교집합, 합집합, 차집합, 여집합 등을 구현할 수 있다.


Set 문법

1. Set 객체 생성 및 중복 제거

Set 객체는 Set 생성자 함수로 생성한다.
Set 생성자 함수에 인수를 전달하지 않으면 빈 Set 객체가 생성된다.

const set = new Set();
console.log(set); // Set(0) {}

Set 생성자 함수는 이터러블(Array, String, Map, Set, TypedArray, arguments, DOM 컬렉션)을 인수로 받아 Set 객체를 생성한다. 이 때 이터러블의 중복된 값은 Set 객체에 요소로 저장되지 않는다.

// 1. 이터러블이 아닌 값을 인수로 전달했을 때 에러
const set1 = new Set(1,2,3) // TypeError: number 1 is not iterable (cannot read property Symbol(Symbol.iterator))

// 2. 이터러블 Array 인수
const set2 = new Set([1,2,3]);
console.log(set2); // Set(3) {1, 2, 3}

// 3. 이터러블 String 인수
// 중복된 요소인 'l'은 Set 객체의 요소로 저장되지 않는다.
const set3 = new Set('hello');
console.log(set3); // Set(4) {'h', 'e', 'l', 'o'}

중복을 허용하지 않는 Set 객체의 속성을 이용해 배열에서 중복된 값을 제거할 수 있다.

// 방법1. 배열의 중복 요소 제거
// indexOf가 일치하는 첫번째 요소만을 반환하는 것을 이용해 중복된 요소는 반환하지 않는다.
const uniq = array => array.filter((v,i,self) => self.indexOf(v) === i);
console.log(uniq([2,1,2,3,4,3,4]));

// 방법 2. Set을 사용한 배열의 중복 요소 제거
const uniq = array => [...new Set(array)];
console.log(uniq([2,1,2,3,4,3,4])); // (4) [2, 1, 3, 4]

2. 요소 개수 확인 - Set.prototype.size 프로퍼티

Set 객체의 요소 개수를 확인할 때는 Set.prototype.size 프로퍼티를 사용한다.

const {size} = new Set([1,2,3,3]);
console.log(size); // 3

3. 요소 추가 - Set.prototype.add 메서드

Set 객체에 요소를 추가할 때는 Set.prototype.add 메서드를 이용한다.

const set = new Set();
console.log(set); // Set(0) {size: 0}

set.add(1);
console.log(set); // Set(1) {1}

add 메서드는 새로운 요소가 추가된 Set 객체를 반환한다. 따라서 add 메서드를 메서드 체이닝할 수 있다.

const set = new Set();
set.add(1).add(2);
console.log(set); // Set(2) {1, 2}

자바스크립트에서 일치 비교 연산자 ===을 사용하면 NaN과 NaN은 다르다고 평가한다. 하지만 Set 객체는 NaN과 NaN은 같다고 평가하여 중복 추가를 허용하지 않는다. +0과 -0은 일치 비교 연산자 ===와 마찬가지로 같다고 평가하여 중복 추가를 허용하지 않는다.

const set = new Set();

console.log(NaN === NaN); // false
console.log(+0 === -0); // true

// Set 객체는 NaN과 NaN은 같다고 평가하여 중복 추가를 허용하지 않는다.
set.add(NaN).add(NaN);
console.log(set); // Set(1) {NaN}

// +0과 -0을 같다고 평가하여 중복 추가를 허용하지 않는다.
set.add(+0).add(-0);
console.log(set); // Set(2) {NaN, 0}

Set 객체는 객체나 배열과 같이 자바스크립트의 모든 값을 요소로 지정할 수 있다.


4. 요소 존재 여부 확인 - Set.prototype.has 메서드

Set 객체에 특정 요소가 존재하는지 확인하려면 Set.prototype.has 메서드를 이용한다. has 메서드는 특정 요소의 존재 여부를 나타내는 불리언 값을 반환한다.

const set = new Set([1,2,3]);
console.log(set.has(2)); // true
console.log(set.has(4)); // false

5. 요소 삭제 - Set.prototype.delete 메서드

Set 객체의 특정 요소를 삭제하려면 Set.prototype.delete 메서드를 사용해야 한다. delete 메서드는 삭제 성공 여부를 나타내는 불리언 값을 반환한다. 만약 존재하지 않는 Set 객체의 요소를 삭제하려고 할 시 에러 없이 무시된다.

const set = new Set([1,2,3]);

set.delete(2); // true
console.log(set); //Set(2) {1, 3}

// 존재하지 않는 Set 객체의 요소를 삭제하려고 하면 에러 없이 무시된다.
set.delete(2); // false
console.log(set); // Set(2) {1, 3}

delete 메서드는 삭제 성공 여부를 나타내는 불리언 값을 반환하므로 add 메서드와 달리 메서드 체이닝을 할 수 없다.

const set = new Set([1,2,3]);
set.delete(2).delete(1); // TypeError: set.delete(...).delete is not a function

6. 요소 일괄 삭제 - Set.prototype.clear 메서드

Set 객체의 모든 요소를 일괄 삭제하려면 Set.prototype.clear 메서드를 사용한다. clear 메서드는 언제나 undefined를 반환한다.

const set = new Set([1,2,3]);
set.clear(); // undefined
console.log(set); // Set(0) {}

7. 요소 순회 - 이터러블인 Set

Set 객체는 이터러블이며 Set.prototype.forEachfor ... of 메서드로 순회할 수 있다.

1. Set.prototype.forEach

Set.prototype.forEach 메서드는 Array.prototype.forEach 메서드와 유사하게 콜백함수와 forEach 메서드의 콜백함수 내부에서 this로 사용될 객체(옵션)를 인수로 전달한다. 하지만 두 메서드는 콜백함수 내부에서 전달받는 인수에서 차이점이 있다.

Set.prototype.forEach 콜백함수 인수
1. 첫 번째 인수: 현재 순회 중인 요소값
2. 두 번째 인수: 현재 순회중인 요소값
3. 세 번째 인수: 현재 순회중인 Set 객체 자체

Array.prototype.forEach 콜백함수 인수
1. 첫 번째 인수: 현재 순회 중인 요소값
2. 두 번째 인수: 현재 순회중인 요소 인덱스
3. 세 번째 인수: 현재 순회중인 Array 객체 자체

Set.prototype.forEach는 Array.prototype.forEach와 인터페이스를 통일하기 위해 첫 번째 인수와 두 번째 인수가 동일하다. Set 객체는 요소 순서가 없기에 배열과 같이 인덱스를 갖지 않는다.

const set = new Set([1,2,3]);
set.forEach((v1,v2,set) => console.log(v1,v2,set));
/* 
1 1 Set(3) {1, 2, 3}
2 2 Set(3) {1, 2, 3}
3 3 Set(3) {1, 2, 3}
*/

2. 이터러블인 Set

Set 객체는 이터러블이므로 for ... of문으로 순회할 수 있으며, 스프레드 문법과 배열 디스트럭쳐링의 대상이 될 수 있다.

const set = new Set([1,2,3]);

// Set 객체는 Set.prototype의 Symbol.iterator 메서드를 상속받는 이터러블이다.
console.log(Symbol.iterator in set); // true

// 이터러블인 Set 객체는 for ... of 문으로 순회할 수 있다.
for (const v of set) {
	console.log(v);
  	// 1
	// 2
	// 3
}
// 이터러블인 Set 객체는 스프레드 문법 대상이 될 수 있다.
console.log([...set]); // [1,2,3]

// 이터러블인 Set 객체는 배열 디스트럭쳐링 할당의 대상이 될 수 있다.
const [a, ...rest] = set;
console.log(a, rest); // 1, [2,3]


집합 연산

1. 교집합

방법1: has 메서드 이용

Set.prototype.intersection = function (set) {
	const result = new Set();
	for (const v of set) {
      	// 2개의 set 요소가 공통되는 요소일 경우 교집합의 대상
    	if (this.has(v)) result.add(v);
    }
  	return result;
}

const setA = new Set([1,2,3,4]);
const setB = new Set([2,4]);

// setA와 setB의 교집합
console.log(setA.intersection(setB)); // Set(2) {2, 4}

방법2: filter 이용

Set.prototype.intersection = function (set) {
	return new Set([...this].filter(v => set.has(v)));
}

const setA = new Set([1,2,3,4]);
const setB = new Set([2,4]);

// setA와 setB의 교집합
console.log(setA.intersection(setB)); // Set(2) {2, 4}

2. 합집합

방법1: add 메서드 이용

Set.prototype.union = function (set) {
	// this(Set 객체)를 복사
  	const result = new Set(this);
  	// 두 Set객체의 중복된 요소는 포함되지 않는다.
  	for (const v of set) {
    	result.add(v);
    }
  	return result;
}

const setA = new Set([1,2,3,4]);
const setB = new Set([2,4]);

// setA와 setB의 합집합
console.log(setA.union(setB)); // Set(4) {1, 2, 3, 4}

방법2: 스프레드 문법 이용

Set.prototype.union = function (set) {
	return new Set([...this, ...set]);
}

const setA = new Set([1,2,3,4]);
const setB = new Set([2,4]);

// setA와 setB의 합집합
console.log(setA.union(setB)); // Set(4) {1, 2, 3, 4}

3. 차집합

방법1: delete 메서드 이용

Set.prototype.difference = function (set) {
	// this(Set 객체)를 복사
  	const result = new Set(this);
  	// 인수로 받은 다른 집합의 원소 제거
  	for (const v of set) {
    	result.delete(v);
    }
  	return result;
}

const setA = new Set([1,2,3,4]);
const setB = new Set([2,4]);

// setA와 setB의 차집합 (setA - setB)
console.log(setA.difference(setB)); // Set(2) {1, 3}
// setB와 setA의 차집합 (setB -setA)
console.log(setB.difference(setA)); // Set(0) {size: 0}

방법2: filter와 has 메서드 이용

Set.prototype.difference = function (set) {
	return new Set([...this]).filter(v => !set.has(v))
}

const setA = new Set([1,2,3,4]);
const setB = new Set([2,4]);

// setA와 setB의 차집합 (setA - setB)
console.log(setA.difference(setB)); // Set(2) {1, 3}
// setB와 setA의 차집합 (setB -setA)
console.log(setB.difference(setA)); // Set(0) {size: 0}

4. 부분집합과 차집합

방법1: has 메서드 이용

Set.prototype.isSuperSet = function (set) {
  	// superset의 모든 요소가 subset의 모든 요소를 포함하는지 확인
	for (const v of subset) {
    	if (!this.has(v)) return false;
    }
  	return true;
}

// setB ⊆ setA
const setA = new Set([1,2,3,4]);
const setB = new Set([2,4]);

console.log(setA.isSuperset(setB)); // true
console.log(setB.isSuperset(setA)); // false

방법2: every와 includes를 이용

// this가 subset의 상위 집합인지 확인한다.
Set.prototype.isSuperSet = function (set) {
	const supersetArr = [...this];
  	// superset의 모든 요소가 subset의 모든 요소를 포함하는지 확인
  	return [...supersetArr].every(v => supersetArr.includes(v));
}

// setB ⊆ setA
const setA = new Set([1,2,3,4]);
const setB = new Set([2,4]);

console.log(setA.isSuperset(setB)); // true
console.log(setB.isSuperset(setA)); // false


2. Map

Map 객체 특징

Map 객체는 키와 값의 쌍으로 이루어진 컬렉션이다. 자바스크립트에서 해시를 구현하기 위해 많이 사용된다.

Map 객체는 객체와 비슷하지만 다음과 같은 차이점이 있다.

구분객체Map 객체
키로 사용할 수 있는 값문자열 또는 심벌 값객체를 포함한 모든 값
이터러블XO
요소 개수 확인Object.keys(obj).lengthmap.size

Map 문법

1. Map 객체 생성 및 중복 제거

Map 객체는 Map 생성자 함수로 생성한다.
Map 생성자 함수에 인수를 전달하지 않으면 빈 Map 객체가 생성된다.

const map = new Map();
console.log(map); // Map(0) {}

Map 생성자 함수는 이터러블(Array, String, Map, Set, TypedArray, arguments, DOM 컬렉션)을 인수로 받아 Map 객체를 생성한다. 이 때 인수로 전달되는 이터러블은 키와 값의 쌍으로 이루어진 요소로 구성되어야 한다.

const map1 = new Map([['key1', 'value1'], ['key2', 'value2']]);
console.log(map1); // Map(2) {'key1' => 'value1', 'key2' => 'value2'}

const map2 = new Map([1, 2]); // TypeError: Iterator value key1 is not an entry object

Map 생성자 함수 인수로 전달된이터러블에 중복된 키를 갖는 요소가 존재하면 값이 덮어씌워진다. 따라서 Map 객체에는 중복된 키를 갖는 요소가 존재할 수 없다.

const map1 = new Map([['key1', 'value1'], ['key1', 'value2']]);
console.log(map1); // Map(1) {'key1' => 'value2'}

2. 요소 개수 확인 - Map.prototype.size 프로퍼티

Map 객체의 요소 개수를 확인할 때는 Map.prototype.size 프로퍼티를 사용한다.

const {size} = new Map([['key1', 'value1'], ['key2', 'value2']]);
console.log(size); // 2

3. 요소 추가 - Map.prototype.set 메서드

Map 객체에 요소를 추가할 때는 Map.prototype.set 메서드를 사용한다.

const map = new Map();
console.log(map); // Map(0) {}

map.set('key1', 'value1'); // Map(1) {'key1' => 'value1'}

set 메서드는 새로운 요소가 Map 객체를 반환한다. 따라서 set 메서드를 메서드체이닝할 수 있다.

const map = new Map();
map
	.set('key1', 'value1')
	.set('key2', 'value2');

console.log(map); // Map(2) {'key1' => 'value1', 'key2' => 'value2'}

Map 객체에는 중복된 키를 갖는 요소가 존재할 수 없기 때문에 중복된 키를 갖는 요소를 추가하면 값이 덮어씌워진다. 이 때 에러는 발생하지 않는다.

const map = new Map();
map
	.set('key1', 'value1')
	.set('key1', 'value2');

console.log(map); // Map(1) {'key1' => 'value2'}

자바스크립트에서 일치 비교 연산자 ===을 사용하면 NaN과 NaN은 다르다고 평가한다. 하지만 Map 객체는 Set 객체와 마찬가지로 NaN과 NaN은 같다고 평가하여 중복 추가를 허용하지 않는다. +0과 -0은 일치 비교 연산자 ===와 마찬가지로 같다고 평가하여 중복 추가를 허용하지 않는다.

const map = new Map();

console.log(NaN === NaN); // false
console.log(+0 === -0); // true

// Map 객체는 NaN과 NaN은 같다고 평가하여 중복 추가를 허용하지 않는다.
map.set(NaN, 'value1').set(NaN, 'value2');
console.log(map); // Map(1) {NaN => 'value2'}

// +0과 -0을 같다고 평가하여 중복 추가를 허용하지 않는다.
map.set(+0, 'value1').set(-0, 'value2');
console.log(map); // Map(2) {NaN => 'value2', 0 => 'value2'}

일반 객체는 문자열 또는 심벌 값만을 키로 사용할 수 있다. 반면 Map 객체는 객체를 포함한 모든 값을 키로 사용할 수 있다.

const map = new Map();

const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

// 객체도 키로 사용할 수 있다.
map
	.set(lee, 'value1')
	.set(kim, 'value2');

console.log(map);
// Map(2) {{name: 'Lee'} => 'value1', {name: 'Kim'} => 'value2'}

4. 요소 취득 - Map.prototype.get 메서드

Map 객체에서 특정 요소를 취득하려면 Map.prototype.get 메서드를 사용한다. get 메서드의 인수로 키를 전달하면 Map 객체에서 인수로 전달한 키를 갖는 값을 반환한다. Map 객체에서 인수로 전달한 키를 갖는 요소가 존재하지 않으면 undefined를 반환한다.

const map = new Map();

const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

map
	.set(lee, 'value1')
	.set(kim, 'value2');

console.log(map.get(lee)); // value1
console.log(map.get('hong')); // undefined

5. 요소 존재 여부 확인 - Map.prototype.has 메서드

Map 객체의 특정 요소가 존재하는지 확인하려면 Map.prototype.has 메서드를 사용한다. has 메서드는 특정 요소의 존재 여부를 나타내는 불리언 값을 반환한다.

const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'v1'], [kim, 'v2']]);
console.log(map.has(lee)); // true
console.log(map.has('key')); // false

6. 요소 삭제 - Map.prototype.delete 메서드 사용

Map 객체의 요소를 삭제하려면 Map.prototype.delete 메서드를 사용한다. delete 메서드는 삭제 성공 여부를 나타내는 불리언 값을 반환한다. 만약 존재하지 않는 키로 Map 객체의 요소를 삭제하려고 하면 에러 없이 무시된다.

const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'v1'], [kim, 'v2']]);

map.delete(lee); // true
map.delete('key'); // false
console.log(map); // Map(1) { {name: 'Kim'} => 'v2' }

delete 메서드는 삭제 성공 여부를 나타내는 불리언 값을 반환하므로 메서드 체이닝을 할 수 없다.


7. 요소 일괄 삭제 - Map.prototype.clear 메서드

Map 객체의 요소를 일괄 삭제하려면 Map.prototype.clear 메서드를 사용한다. clear 메서드는 언제나 undefined를 반환한다.

const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'v1'], [kim, 'v2']]);

map.clear(); // undefined
console.log(map); // Map(0) {}

8. 요소 순회 - 이터러블인 Map

1. Map.prototype.forEach 메서드

Map.prototype.forEach 메서드는 Array.prototype.forEach 메서드와 유사하게 콜백함수와 forEach 메서드의 콜백함수 내부에서 this로 사용될 객체(옵션)를 인수로 전달한다. 하지만 두 메서드는 콜백함수 내부에서 전달받는 인수에서 차이점이 있다.

Map.prototype.forEach 콜백함수 인수
1. 첫 번째 인수: 현재 순회 중인 요소값
2. 두 번째 인수: 현재 순회 중인 요소키
3. 세 번째 인수: 현재 순회중인 Map 객체 자체

const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'v1'], [kim, 'v2']]);

map.forEach((v,k,map) => console.log(v,k,map));
/*
v1 {name: 'Lee'} Map(2) {{ name: 'Lee' } => 'v1', { name: 'Kim' } => 'v2'}
v2 {name: 'Kim'} Map(2) {{ name: 'Lee' } => 'v1', { name: 'Kim' } => 'v2'}
*/

2. 이터러블인 Map

Map 객체는 이터러블이므로 for ... of문으로 순회할 수 있으며, 스프레드 문법과 배열 디스트럭쳐링의 대상이 될 수 있다.

const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'v1'], [kim, 'v2']]);

// Map 객체는 Map.prototype의 Symbol.iterator 메서드를 상속받는 이터러블이다.
console.log(Symbol.iterator in map); // true

// 이터러블인 Set 객체는 for ... of 문으로 순회할 수 있다.
for (const entry of map) {
	console.log(entry);
	// (2) [{ name: 'Lee' }, 'v1']
  	// (2) [{ name: 'Kim' }, 'v2']
}

// 이터러블인 Map 객체는 스프레드 문법 대상이 될 수 있다.
console.log([...set]); // [[{ name: 'Lee' }, 'v1'], [{ name: 'Kim' }, 'v2']]

// 이터러블인 Map 객체는 배열 디스트럭쳐링 할당의 대상이 될 수 있다.
const [a, b] = map;
console.log(a, b);
// (2) [{ name: 'Lee' }, 'v1']
// (2) [{ name: 'Kim' }, 'v2']

3. 이터러블을 반환하는 메서드 - key, value, entries 메서드

Map은 이터러블이면서 동시에 이터레이터인 객체를 반환하는 메서드를 제공한다.

Map 메서드설명
Map.prototype.keysMap 객체에서 요소키를 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체를 반환한다.
Map.prototype.valuesMap 객체에서 요소값을 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체를 반환한다.
Map.prototype.entriesMap 객체에서 요소키와 요소값을 값으로 갖는 이터러블이면서 동시에 이터레이터인 객체를 반환한다.
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'v1'], [kim, 'v2']]);

console.log(map.keys()); // MapIterator {{ name: 'Lee' }, { name: 'Kim' }}
console.log(map.values()); // MapIterator {'v1', 'v2'}
console.log(map.entries()); // MapIterator {{ name: 'Lee' } => 'v1', { name: 'Kim' } => 'v2'}


(docs) 모던 자바스크립트 딥다이브 37강 Set과 Map

profile
지식은 누가 origin인지 중요하지 않다.

0개의 댓글