37. Set과 Map

2023. 1. 31. 16:24기록/Modern JavaScript Deep Dive

Set

중복되지 않는 유일한 값들의 집함
수학적 집합을 구현하기 위한 자료구조
교집합, 합집합, 차집합, 여집합 등 구현 가능
배열과 유사하지만 차이가 있음
구분 배열 Set 객체
동일한 값을 중복하여 포함할 수 있음 O X
요소 순서에 의미가 있음 O X
인덱스로 요소에 접근 가능 O X

 

1. Set 객체의 생성

  • Set 생성자 함수로 생성함
  • 생성자 함수에 인수를 전달하지 않으면 빈 Set 객체가 생성됨
  • Set 생성자 함수는 이터러블을 인수로 전달받아 Set 객체를 생성함
  • 이때 이터러블의 중복된 값은 Set 객체에 요소로 저장되지 않음
const set = new Set();
console.log(set); // Set(0) {}

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

const set2 = new Set('hello');
console.log(set2); // Set(4) {"h", "e", "l", "o"}

 

중복을 허용하지 않는 Set 객체의 특성을 활용해 배열에서 중복된 요소 제거 가능

// 배열의 중복 요소 제거
const uniq = array => array.filter((v, i, self) => self.indexOf(v) === i);
console.log(uniq([2, 1, 2, 3, 4, 3, 4])); // [2, 1, 3, 4]

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

 

2. 요소 개수 확인

  • Set.prototype.size 프로퍼티 사용
  • size 프로퍼티는 getter 함수만 존재하는 접근자 프로퍼티이기 때문에
    size 프로퍼티에 숫자를 할당하여 Set 객체의 요소 개수 변경 불가능
const { size } = new Set([1, 2, 3, 3]);
console.log(size); // 3

 

3. 요소 추가

  • Set.prototype.add 메서드 사용
  • add 메서드는 새로운 요소가 추가된 Set 객체를 반환하기 때문에 연속 호출할 수 있음
  • 중복된 요소 추가 시 에러가 발생하지 않고 무시됨
const set = new Set();
console.log(set); // Set(0) {}

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

 

4. 요소 존재 여부 확인

  • 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 메서드 사용
  • delete 메서드는 삭제 성공 여부를 나타내는 불리언 값을 반환함 (연속 호출 불가능)
  • 인덱스가 아니라 삭제하려는 요소값을 인수로 전달해야 함 (인덱스를 갖지 않음)
  • 존재하지 않는 Set 객체의 요소를 삭제하려 하면 에러 없이 무시됨
const set = new Set([1, 2, 3]);

// 요소 2 삭제
set.delete(2);
console.log(set); // Set(2) {1, 3}

// 존재하지 않는 요소 0을 삭제하면 에러 없이 무시됨
set.delete(0);
console.log(set); // Set(3) {1, 2, 3}

 

6. 요소 일괄 삭제

  • Set.prototype.clear 메서드 사용
  • clear 메서드는 항상 undefined를 반환
const set = new Set([1, 2, 3]);

set.clear();
console.log(set); // Set(0) {}

 

7. 요소 순회

  • Set.prototype.forEach 메서드 사용
  • forEach 메서드의 콜백 함수 내부에서 this로 사용될 객체를 인수로 전달함
  • 콜백 함수는 3개의 인수를 전달받음
    1. 첫 번째 인수: 현재 순회 중인 요소값
    2. 두 번째 인수: 현재 순회 중인 요소값
    3. 세 번째 인수: 현재 순회 중인 Set 객체 자체

 

첫 번째 인수와 두 번째 인수는 같은 같

  • Array.prototype.forEach 메서드와 인터페이스를 통일하기 위함
  • Array.prototype.forEach 메서드의 콜백 함수는 두 번째 인수로 현재 순회 중인 요소의 인덱스를 전달받음
  • Set 객체는 순서에 의미가 없어 배열과 같이 인덱스를 갖지 않음

 

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

set.forEach((v, v2, set) => console.log(v, v2, set));
// 1 1 Set(3) {1, 2, 3}
// 2 2 Set(3) {1, 2, 3}
// 3 3 Set(3) {1, 2, 3}

 

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 value of set) {
  console.log(value); // 1 2 3
}

// 이터러블인 Set 객체는 스프레드 문법의 대상이 될 수 있음
console.log([...set]); // [1, 2, 3]

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

 

8. 집합 연산

더보기

교집합

Set.prototype.intersection = function (set) {
  const result = new Set();
  
  for (const value of set) {
    // 2개의 set의 요소가 공통되는 요소이면 교집합의 대상임
    if (this.has(value)) result.add(value);
  }
  
  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}

// setB와 setA의 교집함
console.log(setB.intersection(setA)); // Set(2) {2, 4}
// 또는
Set.prototoype.intersection = function (set) {
  return new Set([...this].filter(v => set.has(v)));
};

 

합집합

Set.prototype.union = function (set) {
  // this(Set 객체)를 복사
  const result = new Set(this);

  for (const value of set) {
    // 합집합은 2개의 Set 객체의 모든 요소로 구성된 집함 (중복 요소 미포함)
    result.add(value);
  }
  
  return result;
};

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

console.log(setA.union(setB)); // Set(4) {1, 2, 3, 4}
console.log(setB.union(setA)); // Set(4) {2, 4, 1, 3}
// 또는
Set.prototype.union = function (set) {
  return new Set([...this, ...set]);
};

 

차집합

Set.prototype.difference = function (set) {
  // this(Set 객체)를 복사
  const result = new Set(this);
  
  for (const value of set) {
    // 차집합은 어느 한쪽 집합에는 존재하지만 다른 한쪽 집함에는 존재하지 않는 요소로 구성됨
    result.delete(value);
  }
  
  return result;
};

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

// setA에 대한 setB의 차집함
console.log(setA.difference(setB)); // Set(2) {1, 3}
// setB에 대한 setA의 차집함
console.log(setB.difference(setA)); // Set(0) {}
// 또는
Set.prototype.difference = function (set) {
  return new Set([...this].filter(v => !set.has(v)));
};

 

부분 집합과 상위 집합

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

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

// setA가 setB의 상위 집합인지 확인함
console.log(setA.isSuperset(setB)); // true
// setB가 setA의 상위 집합인지 확인함
console.log(setB.isSuperset(setA)); // false
// 또는
Set.prototype.isSuperset = function (subset) {
  const supersetArr = [...this];
  return [...subset].every(v => supersetArr.includes(v));
};

Map

키와 값의 쌍으로 이루어진 컬렉션
객체와 유사하지만 차이가 있음
구분 객체 Map 객체
키로 사용할 수 있는 값 문자열 / 심벌 값 객체를 포함한 모든 값
이터러블 X O
요소 개수 확인 Object.keys(obj).length map.size

 

1. Map 객체의 생성

  • Map 생성자 함수로 생성함
  • 인수를 전달하지 않으면 빈 Map 객체가 생성됨
  • 이터러블을 인수로 전달받아 Map 객체를 생성함
  • 인수로 전달되는 이터러블은 키와 값의 쌍으로 이루어진 요소로 구성되어야 함
  • 중복된 키를 갖는 요소가 존재하면 갚이 덮어씌워짐 (중복된 키를 갖는 요소 존재 불가능)
const map = new Map();
console.log(map); // Map(0) {}

// 키와 값의 쌍으로 이루어진 요소로 구성되어야 함
const map1 = new Map([['key1', 'value1'], ['key2', 'value2']]);
console.log(map1); // Map(2) {"key1" => "value1", "key2" => "value2"}

// 중복된 키를 갖는 요소가 존재하면 값이 덮어씌워짐
const map2 = new Map([['key1', 'value1'], ['key1', 'value2']]);
console.log(map2); // Map(1) {"key1" => "value2"}

 

2. 요소 개수 확인

  • Map.prototype.size 프로퍼티 사용
  • size 프로퍼티는 getter 함수만 존재하는 접근자 프로퍼티이기 때문에
  • size 프로퍼티에 숫자를 할당하여 Map 객체의 요소 개수 변경 불가능
const { size } = new Map([['key1', 'value1'], ['key2', 'value2']]);
console.log(size); // 2

 

3. 요소 추가

  • Map.prototype.set 메서드 사용
  • set 메서드는 새로운 요소가 추가된 Map 객체를 반환하기 때문에 연속 호출 가능
  • 중복된 키를 갖는 요소를 추가하면 값이 덮어씌워짐 (에러 발생X)
const map = new Map();

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

 

객체는 문자열/심벌 값만 키로 사용 가능하지만 Map 객체는 키 타입에 제한이 없음

객체를 포함한 모든 값을 키로 사용할 수 있음

const map = new Map();

const lee = { name: 'Lee' };
map.set(lee, 'developer');

console.log(map); // Map(1) { {name: "Lee"} => "developer" }

 

4. 요소 취득

  • Map.prototype.get 메서드 사용
  • get 메서드의 인수로 키를 전달하면 Map 객체에서 인수로 전달한 키를 갖는 값을 반환함
  • Map 객체에서 인수로 전달한 키를 갖는 요소가 존재하지 않으면 undefined 반환
const map = new Map();

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

map.set(lee, 'developer').set(kim, 'designer');

console.log(map.get(lee)); // developer
console.log(map.get('key')); // undefined

 

5. 요소 존재 여부 확인

  • Map.prototype.has 메서드 사용
  • has 메서드는 특정 요소의 존재 여부를 나타내는 불리언 값을 반환함
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'developer'], [kim, 'designer']]);

console.log(map.has(lee)); // true
console.log(map.has('key')); // false

 

6. 요소 삭제

  • Map.prototype.delete 메서드 사용
  • delete 메서드는 삭제 성공 여부를 나타내는 불리언 값을 반환함
  • 존재하지 않는 키로 Map 객체의 요소를 삭제하려 하면 에러 없이 무시됨
  • 삭제 성공 여부를 나타내는 불리언 값을 반환하기 때문에 연속 호출 불가능
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'developer'], [kim, 'designer']]);

map.delete(kim);
console.log(map); // Map(1) { {name: "Lee"} => "developer" }

// 존재하지 않는 키로 요소를 삭제하려 하면 에러 없이 무시됨
map.delete('key2');
console.log(map); // Map(1) { {name: "Lee"} => "developer" }

 

7. 요소 일괄 삭제

  • Map.prototype.clear 메서드 사용
  • clear 메서드는 항상 undefined를 반환함
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'developer'], [kim, 'designer']]);

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

 

8. 요소 순회

  • Map.prototype.forEach 메서드 사용
  • forEach 메서드의 콜백 함수 내부에서 this로 사용될 객체를 인수로 전달함
  • 이때 콜백 함수는 3개의 인수를 전달받음
    1. 첫 번째 인수: 현재 순회 중인 요소값
    2. 두 번째 인수: 현재 순회 중인 요소키
    3. 세 번째 인수: 현재 순회 중인 Map 객체 자체
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'developer'], [kim, 'designer']]);

map.forEach((v, k, map) => console.log(v, k, map));

/*
developer {name: "Lee"} Map(2) {
  {name: "Lee"} => "developer",
  {name: "Kim"} => "designer"
}
designer {name: "Kim"} Map(2) {
  {name: "Lee"} => "developer",
  {name: "Kim"} => "designer"
}
*/

 

Map 객체는 이러터블

  • for ... of 문으로 순회 가능
  • 스프레드 문법의 대상이 될 수 있음
  • 배열 디스트럭처링 할당의 대상이 될 수 있음
const lee = { name: 'Lee' };
const kim = { name: 'Kim' };

const map = new Map([[lee, 'developer'], [kim, 'designer']]);

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

// 이터러블인 Map 객체는 for ... of 문으로 순회 가능
for (const entry of map) {
  console.log(entry); // [{name: "Lee"}, "developer"] [{name: "Kim"}, "designer"]
}

// 이터러블인 Map 객체는 스프레드 문법의 대상이 될 수 있음
console.log([...map]);
// [[{name: "Lee"}, "developer"], [{name: "Kim"}, "designer"]]

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

 

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

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

const map = new Map([[lee, 'developer'], [kim, 'designer']]);

// Map.prototype.keys는 Map 객체에서 요소키를 값으로 갖는 이터레이터를 반환함
for (const key of map.keys()) {
  console.log(key); // {name: "Lee"} {name: "Kim"}
}

// Map.prototype.values는 Map 객체에서 요소값을 값으로 갖는 이터레이터를 반환함
for (const value of map.values()) {
  console.log(value); // developer designer
}

// Map.prototype.entries는 Map 객체에서 요소키와 요소값을 값으로 갖는 이터레이터를 반환함
for (const entry of map.entries()) {
  console.log(entry); // [{name: "Lee"}, "developer"] [{name: "Kim"}, "designer"]
}