객체(Object) or 배열(Array) 로도 많은 작업을 할 수 있지만, 현실 세계를 반영하기에, 더욱 효과적인 자료형을 JS에서도 제공한다.
그 중 맵(Map) 과 셋(Set) 자료형을 알아본다.
{
/**
* 맵(Map) 자료형
* - "키"가 있는 데이터를 저장한다는 것은 "Object"와 유사함, 다만 맵은 키에 "다양한 자료형"을 허용한다. 💡
*
* 맵 프로퍼티s
* - new Map() : 맵 생성
* - map.set(key, value) : key를 이용해 value 저장
* - map.get(key) : key에 해당하는 값을 반환, key가 존재하지 않으면, undefinde
* - map.has(key) : key 가 존재하면 true, 아니면 false
* - map.delete(key) : key 에 해당하는 "값을 삭제"
* - map.clear() : 맵 안에 모든 요소를 제거
* - map.size : 맵 요소의 개수를 반환
*
* 맵 사용시, 유의점 🔍
* - map[key] 형식을 사용하지 않도록 한다.
* - 사용할 수 있긴 하나, 이는 곧 map을 "일반 객체"처럼 취급하게 된다. 고로 여러 제약이 발생한다.
* - map을 사용할 때는, map 전용 메서드 set, get 등을 사용할 것 💡
*/
let map = new Map();
console.log(map); // Map(0) {}
map.set("100", "문자 백");
map.set(100, "숫자 백");
map.set(true, "불리언 true");
console.log(map); // Map(3) { '100' => '문자 백', 100 => '숫자 백', true => '불리언 true' }
console.log(map.get("100")); // 문자 백
console.log(map.get(100)); // 숫자 백
console.log(map.get(true)); // 불리언 true
console.log(map.get(false)); // undefined 🔍
console.log(map.size); // 3
console.clear();
/**
* Map 은 키로 "객체"를 허용한다. 🔍
* - "객체"를 키로 사용할 수 있다는 점은 map 의 가장 중요한 기능 중 하나이다. 💡
* - "객체" 자료형은 "문자열 키"를 사용하지, 객체 키는 사용할 수 없다.
* - "객체" 자료형에 객체를 키로 사용할 경우, 문자열로 변환되어 "[object Object]" 로 저장된다. 🔍
* - 이 경우, 객체 자료형에 객체를 연속으로 넣을 경우, 가장 마지막 데이터로만 덮어 저장된다. 💡
*/
let man = { name: "min" };
let myMap = new Map();
myMap.set(man, 100);
console.log(myMap.get(man)); // 100
console.log(myMap); // Map(1) { { name: 'min' } => 100 }
let myObj = {};
myObj[man] = 100;
console.log(myObj[man]); // 100 << 100이 나오긴 함
console.log(myObj); // { '[object Object]': 100 } << 키가 "[object Object]" 문자열임 🔍
console.clear();
/**
* map 이 키를 비교하는 방식
* - SameValueZero 라고 불리는 알고리즘을 사용해 값의 등가 여부를 확인한다.
* - 이 알고리즘은 ===(일치 연산자)와 "거의 유사"하지만, NaN 과 NaN 을 같다고 취급한다.(원래 일치연산자에서 NaN 과 NaN 은 같지 X) 💡
* - 이 알고리즘은 일반화되어 있어, 커스터마이징 하는 것은 불가능
*
* 체이닝
* - map.set 을 호출할 때마다 맵 자신이 반환된다.
* - 이를 이용해 map.set 을 "체이닝(Chaining)"할 수 있다.
*/
let myMap2 = new Map();
myMap2.set(1, "1").set(2, "2").set(3, "3");
console.log(myMap2); // Map(3) { 1 => '1', 2 => '2', 3 => '3' }
console.clear();
}
{
/**
* 맵(Map) 의 요소에 반복 작업하기
* - map.keys() : 각 요소의 "키"만 모은 "반복 가능한(iterable, 이터러블) 객체"를 반환
* - map.values() : 각 요소의 "값"만 모은 이터러블 객체를 반환
* - map.entries() : 각 요소의 "[키, 값]을 한 쌍"으로 하느 이터러블 객체를 반환, 해당 이터러블은 for - of 문을 기초롤 쓰인다. 💡
*
* map 은 삽입 순서를 기억한다. 🔍
* - 맵은 값이 삽입된 순서대로 순회를 실시한다.
* - 객체가 프로퍼티 순서를 기억하지 못하는 것과는 다르다.
*/
let myFruitAndPriceMap = new Map([
["banana", 100],
["apple", 200],
["kiwi", 300],
]);
for (let fruit of myFruitAndPriceMap.keys()) {
console.log(fruit);
}
// banana
// apple
// kiwi
for (let price of myFruitAndPriceMap.values()) {
console.log(price);
}
// 100
// 200
// 300
for (let item of myFruitAndPriceMap) {
// == myFruitAndPriceMap.entries()
console.log(item);
}
// [ 'banana', 100 ]
// [ 'apple', 200 ]
// [ 'kiwi', 300 ]
console.clear();
/**
* 맵은 "배열"과 유사하게 forEach 문을 지원한다. 💡
* - map.forEach((value, key, map)) 순서를 지켜줄 것
*/
myFruitAndPriceMap.forEach((value, key, map) => {
console.log(`${value} ${key} ${map}`);
});
// 100 banana [object Map]
// 200 apple [object Map]
// 300 kiwi [object Map]
}
{
/**
* 객체 => 맵으로 변형
* - Object.entries() : 각 요소가 키-값 쌍인 "배열"이나 "이터러블 객체를 초기화 용도"로 map에 전달해 새로운 "맵(map)"을 만든다. 🔍
*
* "평범한 객체"를 가지고 map을 만들고 싶다면, 내장 메소드 Object.entries(obj)를 활용할 것
* - 이 메소드는 객체의 "키-값" 쌍을 요소([key, value])로 가지는 "배열"을 반환한다. 💡
*
*/
let map = new Map([
["1", "문자 1"],
[1, "숫자 1"],
[true, "불리언 true"],
]);
console.log(map.get(1)); // 숫자 1
let obj = {
name: "min",
age: 100,
};
let fromObjToMap = new Map(Object.entries(obj));
console.log(fromObjToMap.get("name")); // min
console.clear();
/**
* 맵 => 객체로 변형
* - Object.fromEntries() : 각 요소가 [키,값] 쌍은 "배열"을 "객체"로 바꿔준다.
*
* - map.entries()를 호출하면, 맵의 [키,값]을 요소로 가지는 "이터러블"을 반환한다고 했다.
* - 이터러블(or 배열형태) 형태는, Object.fromEntries() 를 사용하기 위해 딱 맞는 형태이다.
*
*/
let fruitAndPrice = Object.fromEntries([
["banana", 100],
["apple", 200],
["berry", 300],
]);
console.log(fruitAndPrice); // { banana: 100, apple: 200, berry: 300 }
let myMap = new Map();
myMap.set("banana", 100).set("apple", 200).set("berry", 300);
let fromMyMapToObj = Object.fromEntries(myMap.entries()); // myMap 을 인자로 넘기면 더 간결하다. (for - of 문에서 동일하게 사용한 것과 같은 개념)
console.log(fromMyMapToObj); // banana: 100, apple: 200, berry: 300 }
}
{
/**
* 셋(Set) 자료형
* - 중복을 허용하지 않는 "값"만을 모아놓은 특별한 "컬렉션" 💡
*
* set 주요 메소드, 프로퍼티
* - new Set(iterable) : 셋 생성, "이터러블" 객체를 전달받으면(대게 "배열"을 전달받음) 그 안의 값을 복사해 셋에 넣어준다.
* - set.add(value) : 값을 추가하고, "셋 자신을 반환"
* - set.delete(value) : 값을 제거한다. 호출 시점에 셋 내에 "값이 있어서 제거에 성공하면 true, 아니면 false 반환"
* - set.clear() : 셋을 비운다.
* - set.size : 셋에 몇 개의 값이 존재하는지 반환
*
* 셋(Set) 과 배열(Array) 에 중복 여부 판단에서의 차이점 🔍
* - 중복 값 여부는 Array 메소드인 arr.find 를 이용해 확인 할 수도 있음
* - 하지만, arr.find 는 "배열 내 요소 전체"를 뒤져 중복 값을 찾는다.
* - 때문에, Set 보다 성능면에서 떨어진다.
* - Set 이 값의 중복을 허용하지 않는다는 성질 덕분에 중복 값 여부를 판단하는데 최적화 되어있다.
*/
let mySet = new Set();
let man1 = { name: "one" };
let man2 = { name: "two" };
let man3 = { name: "three" };
mySet.add(man1);
mySet.add(man2);
mySet.add(man2);
mySet.add(man3);
mySet.add(man3);
mySet.add(man3);
console.log(mySet.size); // 3
for (let man of mySet) {
console.log(man.name);
}
// one
// two
// three
console.clear();
}
{
/**
* set 자료형, 반복 작업하기
* - for - of 문, forEach 문을 지원한다.
*
* set.forEach((value, valueAgain, set)) 에 형태를 가진다.
* - 순서대로, (값, 값, 목표하는 객체)
* - 동일한 "값" 인자가 반복되는 것을 볼 수 있다. 🔍
* - 이것은 "맵(Map)"과의 호환성 때문이다. 💡
* - map 에서 map.forEach((value, key, map)) 인 콜백이 3개의 인자를 받을 때를 위해서라고 한다.
* - 그냥, 일반화되어 사용하기때문에 받아들일 것
*
* set 에서도 반복 작업 메서드 지원한다.
* - set.keys() : 셋 내의 모든 "값"을 포함하는 이터러블 객체를 반환
* - set.values() : set.keys() 와 동일한 작업을 한다. 단지 맵(Map)과의 호환성을 위해 만들어진 메소드
* - set.entries() : 셋 내의 각 값을 이용해 만든 "[value, value] 배열"을 포함하는 "이터러블 객체"를 반환, 역시 맵(Map)과의 호환성을 위해 만들어졌다.
*/
let set = new Set(["one", "two", "three"]);
for (let value of set) {
// == set.entries()
console.log(value);
}
// one
// two
// three
for (let value of set.values()) {
console.log(value);
}
// one
// two
// three
for (let value of set.keys()) {
console.log(value);
}
// one
// two
// three
}
{
// 1. 배열에서 중복 요소 제거하기
function unique(arr) {
return Array.from(new Set(arr));
}
let values = ["Hare", "Krishna", "Hare", "Krishna", "Krishna", "Krishna", "Hare", "Hare", ":-O"];
console.log(unique(values)); // 얼럿창엔 `Hare, Krishna, :-O`만 출력되어야 합니다.
console.clear();
// 2. 애너그램 걸러내기
// "애너그램" : 단어나 문장을 구성하고 있는 문자의 순서를 바꾸거나, 다른 단어나 문장을 만드는 놀이
function aclean(arr) {
let result = [];
let test = new Set();
for (let item of arr) {
let lowerCaseItem = item.toLowerCase().split("").sort().join("");
if (!test.has(lowerCaseItem)) {
test.add(lowerCaseItem);
result.push(item);
}
}
return result;
}
let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];
console.log(aclean(arr)); // "nap,teachers,ear"나 "PAN,cheaters,era"이 출력되어야 합니다.
console.clear();
// 3. 반복 가능한 객체의 키
let map = new Map();
map.set("name", "John");
let keys = map.keys();
keys.push("more"); // Error: keys.push is not a function
/**
* 마지막 keys.push 하려고 하면, 에러가 발생한다. 왜 그럴까 ?
* - map.keys() 의 반환값이 "이터러블 객체"여서 그렇다. "배열이 아니다." 💡
* - 이를 해결하기 위해서는
* - let keys = Array.from(map.keys()) 형식을 취해서, iterable 객체 -> Array 로 변형해야 할 것
*/
}
다른 건 몰라도 개발을 하면서 중복 제거라는 이슈는 빈번하게 발생한다고 생각한다. 그럴 때 이제 흔히 데이터를 저장하는 Array이나 Object에서가 아니라, Map 이나 Set 을 활용해서 프로세스를 최적화 하는데 노력해보자. 👍