[JS] Map & Set 자료형

위영민(Victor)·2021년 8월 10일
0

개요

객체(Object) or 배열(Array) 로도 많은 작업을 할 수 있지만, 현실 세계를 반영하기에, 더욱 효과적인 자료형을 JS에서도 제공한다.
그 중 맵(Map)셋(Set) 자료형을 알아본다.

본론

📍 Map, 기본, 메소드, 프로퍼티

{
  /**
   * 맵(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) 의 요소에 반복 작업하기
   * - 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]
}

📍 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) 자료형
   * - 중복을 허용하지 않는 "값"만을 모아놓은 특별한 "컬렉션" 💡
   *
   * 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, 셋의 값에 반복 작업

{
  /**
   * 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 을 활용해서 프로세스를 최적화 하는데 노력해보자. 👍


참고

profile
🌟 Let the brighter shine the brighter.

0개의 댓글