FrontEnd/Deep Dive

[JS] DeepDive(37) Set과 Map

728x90

Set

 

Set 객체는 중복되지 않는 유일한 값들의 집합이다. Set객체는 배열과 유사하지만 약간의 차이가 있다.

 

 

1. Set객체는 동일한 값을 중복하여 갖지 않는다.

2. 요소에 순서가 없다.

3. 인덱스로 요소에 접근할 수 없다.

 

 

Set 객체는 수학에서의 집합과 일치한다!!

 

 

 

 

 

 

Set 객체의 생성

 

Set객체는 Set 생성자 함수로 생성한다.

 

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

 

Set 생성자 함수는 이터러블을 인수로 전달받아 Set 객체를 생성하며 이터러블의 중복된 값은 Set객체에 저장되지 않는다.

 

 

const set1 = new Set([1, 2, 3, 3]);
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]

 

 

요소개수 확인

 

Set 객체의 요소 개수를 확인하려면 Set.prototype.size 프로퍼티를 활용하면 된다.

 

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

 

 

size 프로퍼티는 setter함수가 없어서 숫자를 할당하는 것은 불가능하다.

 

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

console.log(Object.getOwnPropertyDescriptor(Set.prototype, 'size'));
// {set: undefined, enumerable: false, configurable: true, get: ƒ}

set.size = 10; // 무시된다.
console.log(set.size); // 3

 

 

 

요소 추가

 

Set 객체에 요소를 추가할때는 Set.prototype.add 프로퍼티를 활용한다.

 

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

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

 

add 메서드는 새로운 요소가 추가된 Set 객체를 반환하기에 연속해서 사용하는것이 가능하다.

 

const set = new Set();

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

 

 

객체에 중복된 요소가 있다면 추가되지않으며 이 경우 에러가 발생하지는 않는다.

 

const set = new Set();

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

 

 

Set객체에선 NaN과 NaN을 같다고 생각한다.

 

const set = new Set();

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

// 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객체는 객체,배열 등 JS의 모든 값을 저장할 수 있다.

 

const set = new Set();

set
  .add(1)
  .add('a')
  .add(true)
  .add(undefined)
  .add(null)
  .add({})
  .add([]);

console.log(set); // Set(7) {1, "a", true, undefined, null, {}, []}

 

 

 

요소 확인

 

SEt객체의 요소가 있는지 확인하려면 Set.prototype.has 프로퍼티를 활용하면 된다.

 

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

console.log(set.has(2)); // true
console.log(set.has(4)); // false

 

 

 

요소 삭제

 

Set.prototype.delete 메서드를 활용해서 지울 수 있다.

 

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

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

// 요소 1을 삭제한다.
set.delete(1);
console.log(set); // Set(1) {3}

 

 

만약 존재하지 않는 값을 지우려고 해도 에러가 나지는 않는다.

 

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

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

 

 

delete 메서드는 삭제 성공 여부를 나타내는 boolean 값을 반환하기 때문에 add처럼 연속적으로 사용하는 것은 불가능하다.

 

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

// delete는 불리언 값을 반환한다.
set.delete(1).delete(2); // TypeError: set.delete(...).delete is not a function

 

 

 

요소 일괄 삭제

 

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

 

해당 메서드는 항상 undefined를 반환한다.

 

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

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

 

 

요소 순회

 

Set객체의 요소를 순회하려면 Set.prototype.forEach 메서드를 활용한다.

 

배열과 다르게 두번재 인수 값으로 인덱스를 갖지 않고 현재 순회중인 요소값을 가지게 된다. 이는 배열의 forEach와 동작 순서를 맞추기 위함이다.

 

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]

 

 

Set 객체 자체는 순서를 가지지 않지만 순회하는 순서는 요소가 추가된 순서이다.

 

 

집합 연산

 

Set 객체는 수학적 집합을 구현하기 위한 자료구조이며 다양한 집합 연산이 가능하다.

 

 

교집합

 

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.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]);

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

 

 

 

차집합

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) {}

 

 

 

부분 집합과 상위 집합

 

// 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

 

 

 

 

 

Map

 

Map 객체는 키와 값의 쌍으로 이루어진 컬렉션이며 객체와 유사하지만 약간의 차이가 있다.

 

1. Map 객체는 키로 객체를 포함한 모든값을 사용가능하다.

2. 객체와 다르게 이터러블이다.

3. 요소 개수를 확인하는 프로퍼티가 다르다.

 

 

 

Map 객체의 생성

 

Map 객체는 Map 생성자 함수로 생성한다.

 

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

 

 

 

Map 생성자 함수는 이터러블을 인수로 받아 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 1 is not an entry object

 

 

요소 개수 확인

Map.prototype.size 프로퍼티를 활용하여 개수를 확인할 수 있다.

 

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

 

 

size 프로퍼티 역시 setter 함수 없이 getter만 존재하기에 요소 개수를 임의로 변경할 수 없다.

 

const map = new Map([['key1', 'value1'], ['key2', 'value2']]);

console.log(Object.getOwnPropertyDescriptor(Map.prototype, 'size'));
// {set: undefined, enumerable: false, configurable: true, get: ƒ}

map.size = 10; // 무시된다.
console.log(map.size); // 2

 

 

요소 추가

 

Map.prototype.set 프로퍼티를 활용하여 요소를 추가할 수 있다.

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

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

 

set 메서드는 추가된 Map 객체를 반환하여 연속적으로 호출하는 것이 가능하다.

 

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"}

 

 

 

객체와 다르게 키 타입에 제한이 없다.

 

const map = new Map();

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

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

console.log(map);
// Map(2) { {name: "Lee"} => "developer", {name: "Kim"} => "designer" }

 

 

요소 취득

 

Map.prototype.get 메서드를 활용해서 키를 활용해 값을 가져올 수 있다. 만약 값이 없다면 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

 

 

 

요소 존재 여부 확인

 

Map.prototype.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

 

 

요소 삭제

 

Map.prototype.delete 메서드를 활용하여 삭제할 수 있다.

 

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" }

 

 

존재하지 않는 키를 삭제하려 하면 ㅜㅁ시된다.

 

const map = new Map([['key1', 'value1']]);

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

 

 

 

요소 순회

 

Map 객체의 요소를 순회하려면 Map.prototype.forEach를 활용할 수 있다.

 

이때 첫번재 인수는 요소값 , 두번째 인수는 요소키, 세번째 인수는 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 객체는 이터러블이면서 이터레이터인 객체를 반환하는 메서드를 제공한다.

 

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"]
}

 

 

 

 

 

 

 

728x90