[JS #5] ES6 Map(), Set()

얼마 전부터 회사 업무를 진행할 때 본격적으로, 그리고 의식적으로 ES6 에 도입된 문법을 적용하고 있는데, 그중에서 가장 자주 활용하는 자료구조, Map 과 Set 에 대해 이야기해보려고 합니다. 이 글의 모티브는 상당부분 Mozilla 웹기술블로그에 기반합니다. 사내세미나에서 발표한 내용을 글로 정리했습니다.

Map

  • Map() 은 자바스크립트의 key-value 페어(pair) 로 이루어진 컬렉션
  • key 를 사용해서 value 를 get, set 할 수 있음
  • key 들은 중복될 수 없음: 하나의 key 에는 하나의 value 만
  • key 로 사용할 수 있는 데이터형: string, symbol(ES6), object, function >> number 는 사용할 수 없음에 주의!
// 새로운 map 을 만들고 map 에 key, value 엔트리를 추가
let me = new Map();
me.set('name', 'kevin');
me.set('age', 28);
console.log(me.get('age'); // 28
// 대괄호를 사용해서 map 을 선언하는 방법
const roomTypeMap = new Map(
[
["01", "원룸(오픈형)"],
["02", "원룸(분리형)"],
["03", "원룸(복층형)"],
["04", "투룸"],
["05", "쓰리룸"]
]
);
// 새로운 map 을 만들고 그 데이터를 기존의 [key, value] 페어컬렉션으로 채움
let you = new Map().set('name', 'paul').set('age', 34);
console.log(you.get('name')); // 'paul'
// has(): 주어진 key 가 존재하는지 확인
console.log(me.has('name')); // true
// size: map 에 담겨진 엔트리의 개수를 조회
console.log(you.size); // 2
// delete(): 엔트리를 삭제
me.delete('age');
console.log(me.has('age')); // false
// clear(): 모든 엔트리를 삭제
you.clear();
console.log(you.size); // 0

<참고 1> Map 과 Object 비교

  • Object 의 key 는 string 과 symbol(ES6) 만 가능하지만, map 은 어떤 값도 가능
  • Object 에서는 크기를 추적해서 알 수 있지만, map 은 손쉽게 얻을 수 있음(size)

Set

  • Set() 은 value 들로 이루어진 컬렉션(“집합”이라는 표현이 적절)
  • Array 와는 다르게 Set 은 같은 value 를 2번 포함할 수 없음
  • 따라서 Set 에 이미 존재하는 값을 추가하려고 하면 아무 일도 없음
// 비어있는 새로운 set 을 만듬
let setA = new Set();
// 새로운 set 을 만들고 인자로 전달된 iterable 로 인자를 채움
let setB = new Set().add('a').add('b');
setB.add('c');
console.log(setB.size); // 3
// has(): 주어진 값이 set 안에 존재할 경우, true 를 반환
// indexOf() 보다 빠름. 단, index 가 없음
console.log(setB.has('b')); // true
// set 에서 주어진 값을 제거
setB.delete('b');
console.log(setB.has('b')); // false
// set 안의 모든 데이터를 제거
setB.clear();
console.log(setB.size); // 0
  • <TODO> has() 는 indexOf() 보다 빠르다. 다만, index 이 존재하지 않기때문에 index 로 value 로 접근할 수 없다.

<참고 2> Spread 연산자

  • 이터러블 오브젝트(iterable object)의 엘리먼트를 하나씩 분리하여 전개
// string == iterable object
console.log([...'music']); // ['m', 'u', 's', 'i', 'c']

<참고 3> for 문들

  • for 문
let sampleArr = [1, 2, 3, 4, 5];
for (let i = 0, length = sampleArr.length; i < length; i++) {
console.log(sampleArr[i]);
}
  • forEach: ES5 자바스크립트 배열 메서드
let sampleArr = [1, 2, 3, 4, 5];
sampleArr.forEach(v => console.log(v));
  • for-in: Object 를 순회하기 위한
let sampleObj = {
a: 1,
b: 'hello',
c: [1, 2]
}
for (let key in sampleObj) {
console.log(key);
console.log(sampleObj[key]);
}
  • for-of: 배열의 요소들, 즉 data 를 순회하기 위한(string 도 가능)
let sampleArr = [1, 2, 3, 4, 5];
let (for value of sampleArr) {
console.log(value);
}

  • 일반 객체(Object)는 iterable 하지 않다!
  • for-of 나 …(spread 연산자)를 사용할 수 없다!
  • for-in 으로나 순회할 수 있다.

Map 의 iterable object

  • map.keys(), map.values()
  • map 안의 key 혹은 value 들을 순회할 수 있는 iterable object 를 반환
let me = new Map().set('a', 1).set('b', 2);
console.log([...me.keys()]); // ['a', 'b']
console.log([...me.values()]); // [1, 2]
  • map.entries(), map.next()
  • map 안의 모든 엔트리들을 순회할 수 있는 iterable object 를 반환
let you = new Map().set('Seoul', 28).set('Tokyo', 26);
let iterObj = you.entries();
console.log(iterObj.next()); // {value: ['Seoul', 28], done: false}
console.log(iterObj.next()); // {value: ['Tokyo', 26], done: false}
console.log(iterObj.next()); // {value: undefined, done: true}
  • for-of, map.forEach();
  • forEach 의 경우, 인자 순서가 이상한데(key, value 순서가 반대) Array.prototype.forEach() 구문과 통일성을 유지하기 위함(value, index, array 순서인 것)
let we = new Map().set('car', 30).set('bus', 45);
// for-of 로 map 순회하기
for (let [key, value] of we) {
console.log(key + '^' + value);
}
// 차례대로 'car^30', 'bus^45' 출력
// forEach 로 map 순회하기
we.forEach((value, key, map) => {
console.log(key + '$' + value);
});
// 차례대로 'car$30', 'bus$45' 출력
  • 자바스크립트 배열 메서드에 존재하는 map, filter 메서드는 Map 에 존재하지 않는다. 하지만 아래와 같은 방식으로 우회해서 사용이 가능하다.
let me = new Map().set('a', 1).set('b', 2);
// value 가 1 이상인 엔트리만 filtering 하기
let map1 = new Map(
[...me]
.filter(([k, v]) => v > 1)
);
console.log([...map1.entries()]) // [['b', 2]]
// key 뒤에 'super' 문자열을 붙이고, value 에 1을 더하기
let map2 = new Map(
[...me]
.map(([k, v]) => [k + "super", v + 1])
);
console.log([...map2.entries()]) // [['asuper, 2], [bsuper, 3]]

Set 의 iterable object

  • set.values();
  • 기본적으로 Set 의 prototype 메서드로 keys() 는 존재하지 않고, values() 만 존재하지만, MDN 의 설명에 따르면, map 오브젝트와 동일하게 동작하기 때문에 Set.prototype.keys() 는 Set.prototype.values() 와 같은 결과
let setA = new Set();
setA.add('a');
setA.add('b');
setA.add('a');
console.log([...setA.keys()]); // ['a', 'b']
console.log([...setA.values()]); // ['a', 'b']
  • set.entries();
let setB = new Set();
setB.add('Korea');
setB.add('Japan');
setB.add('China');
let entries = setB.entries();
console.log(entries.next()); 
// {value: ['Korea', 'Korea'], done: false}
console.log(entries.next());
// {value: ['Japan', 'Japan'], done: false}
console.log(entries.next());
// {value: ['China', 'China'], done: false}
console.log(entries.next());
// {value: undefined, done: true}
  • for-of, set.forEach();
let setC = new Set();
setC.add('Korea');
setC.add('Japan');
setC.add('China');
for (let key of setC) {
console.log(key);
}
// 차례대로 'Korea', 'Japan', 'China' 출력
setC.forEach((v, k) => {
console.log(v);
})
// 차례대로 'Korea', 'Japan', 'China' 출력

Set: 집합연산

스위프트 집합연산 — 링크
  • union(합집합), intersection(교집합), difference(차집합)
let setA = new Set([1, 2, 3, 4, 5]);
let setB = new Set([4, 5, 6, 7, 8]);
// 합집합
let unionSet = new Set([...setA, ...setB])
for (let value of unionSet) {
console.log(value);
}
// 차례대로 1, 2, 3, 4, 5, 6, 7, 8 출력
// 교집합
let intersectionSet = new Set(
[...setA].filter(v => setB.has(v))
);
for (let value of intersectionSet) {
console.log(value);
}
// 차례대로 4, 5 출력
// 차집합
let differenceSet = new Set(
[...setA].filter(v => !setB.has(v))
);
for (let value of differenceSet) {
console.log(value);
}
// 차례대로 1, 2, 3 출력
  • symmetricDifference
// Symmetric Difference
var set1 = new Set([1, 2, 3, 4, 5]);
var set2 = new Set([3, 4, 5, 6, 7]);
var symmetricDifferenceSet = new Set(
[...[...set1].filter(x => !set2.has(x)), ...[...set2].filter(x => !set1.has(x))]
)
for (let value of symmetricDifferenceSet) {
console.log(value);
}
// 차례대로 1, 2, 6, 7 출력

지금까지 Map 과 Set 에 대해 자세히 알아보았습니다.

단순히 key 와 value 를 set 하거나 value 를 set 하는 것뿐만 아니라, iterable object 의 특성을 살려서 map 과 set 을 순회하는 것을 알아보았습니다. 더 나아가서는 map 과 set 에서는 지원하지 않는 배열 메서드(Array.prototype) 인 map, filter 를 적용해보고, 집합연산까지도 진행해보았습니다.

억지로 배열의 형태로, 기본 객체형태로 코딩하기 보다는 적재적소의 자료구조의 특성에 맞게 코딩하는 습관을 기르면 좋겠습니다.

다음에는 윗글에서도 잠깐 나왔지만, 왜 배열의 indexOf 메서드보다 Set 의 has 가 더 “빠른지" 알아보겠습니다^^ 읽어주셔서 감사합니다.