코딩 테스트를 준비하면서 생각보다 자바스크립트에 무지함을 깨달아 버렸다…
그래서 오늘은 머리를 한대 맞은 것 같은 JavScript의 Map과 해시에 대해서 한번 알아보기로 했다.
이번에 푼 문제는 두 배열의 차집합의 요소를 찾아야 하는데
문제는 비교 대상 배열 중 첫 번째 배열에 중복 요소가 있다는 것이고, 해당 중복 요소까지 고려해서 차집합을 구하는 문제였다. 단순한 차집합은 indexOf를 사용하면 문제가 없지만, 문제는 반복적으로 indexOf를 사용할 경우 복잡도 및 효율성이 떨어지는 이슈가 있었다.
1. 자바스크립트 Map 너는 뭐 하는 녀석이니?
Map은 자바스크립트에서 Key-Value를 쌍으로 가지는 자료구조이다.
흠..JSON과 같은 오브젝트를 우리는 이미 많이 쓰고 있는데 Map을 그럼 왜 써야 할까?
Map과 Object를 비교해보자.
G-Man이 정리해준 표를 보면서 중요한 내용을 집어보면.
비교 항목 | Object {} | Map |
🔑 Key 타입 | 문자열( "key" )과 심볼만 사용 가능 | 모든 타입 가능 (문자열, 숫자, 객체, 배열 등) |
⏱ 성능 | 작은 데이터에는 괜찮지만, 해시 용도에는 비효율적 | 해시 처리에 더 빠르고 효율적 |
📐 키 순서 보장 | 보장되지 않음 (ES6 이후 일부 엔진에서 순서 보장됨) | 입력한 순서 보장 |
📏 크기 확인 | Object.keys(obj).length 등 간접적 확인 필요 | map.size 으로 직접 확인 가능 |
🔄 순회 방식 | for...in , Object.keys() , Object.entries() | map.entries() , for...of 지원 |
🔐 key 충돌 위험 | 내장 속성( __proto__ , toString )과 충돌 가능성 있음 | 충돌 없음. 안전한 키 사용 가능 |
📦 유연성/확장성 | 프로토타입 체인이 존재하여 key 관리에 신중해야 함 | 독립적인 자료구조로 관리되며, 더 유연함 |
🧪 기본 자료구조 용도 | 간단한 속성 저장용으로 적합 | 복잡한 키-값 관리, 해시맵용으로 적합 |
🧠 실전 사용 예시 | 설정 객체, 기본 속성 저장 등 | 등장 횟수 세기, 캐싱, 순서 있는 데이터 관리 등 |
1. key를 문자열 외의 타입으로 사용하는 경우
여러 예제를 좀 보면 key 값을 기준으로 연산, 조건 분기, 빈도를 나타내는 유형에서 사용에 유리하다.
예를 들어 지도에서 Map의 특정 좌표를 저장하고 위치의 방문 여부를 true/false로 지정하는 데이터가 있다고 가정해보자.
const pos_school = {x:90, y:130}; const pos_home = {x:170, y:90}; const userVisiteMap = new Map(); userVisiteMap.set(pos_school ,false); userVisiteMap.set(pos_school ,true);
이러한 경우는 Key타입이 문자열로 고정된 Object를 이용해서 추가 구현을 하기보단 한번에 정의가 가능하다.
2. 해시 처리를 위한 성능에 용이하다.
해시가 우선 뭔지를 정확히 집고 넘어가 본다면,
해시란 어떤 값을 고정된 크기의 고유한 값(해시 값)으로 바꾸는 방식으로, key를 빠르게 찾기 위해 고유한 인덱스로 만드는 것이다.
특정한 배열이 주어지고 해당 배열에 내가 찾고자 하는 요소를 찾는 코드를 만들어보면
//배열 순회 O(n) 찾을 대상이 맨 뒤인경우 다 열어봐야함. const people = ['철수', '태헌', '영희']; console.log(people.includes('태헌')); //맵에 등록된 데이터는 키를 통해서 한번에 접근이 가능함 O(1) const people = new Map(); people.set('철수', true); people.set('태헌', true); people.set('영희', true); console.log(people.has('태헌'));
보통 알고리즘 문제에서 다음과 같은 문제해결 과정이 나온다면 Map을 통한 해시에 적합하다.
- 중복 체크
- 빠른 검색
- 등장 횟수 세기
- 누락 찾기
3. key 충돌 위험 과 확장 유연성
이는 결국 프로토타입 속성에 있는 값들을 개발자들이 실수로 덮어 씌워질 때를 이야기한다.
//obejct일때 실수로 씌우는경우 const obj = {}; obj['toString'] = '위험'; console.log(obj['toString']); // '위험' console.log(obj.toString()); // [object Object] ❗내장 메서드 호출됨 const map = new Map(); map.set('toString', '안전'); console.log(map.get('toString')); // '안전'
이러다 보니 Objcet의 키값이 프로토타입에 있는 상속자들과 겹치지 않는 안전한 경우에 사용이 가능하다.
보통 constructor , value 와 같은 key를 생성할 경우 충돌이 날수있지…
2. Map을 야무지게 쓸 수 있는 함수들.
알고리즘 문제는 결국 효율성 테스트도 통과해야 하기에 Map을 피할 수는 없다.
고로 이참에 Map을 야물딱지게 쓸 수 있게 함수를 알아보도록 하자.
1.읽고, 쓰고,지우기
const myMap = new Map(); //값 추가 수정 myMap.set('userClass', "Guest"); //값 가져오기 myMap.get('userClass'); //특정키 삭제 myMap.delete('userClass'); //전체삭제 myMap.clear();
2. 존재 유무, 크기 확인
//존재 여부 true/false로 반환 myMap.has('userClass'); //Map의 크기 반환 myMap.size;
3. 이터러블 객체 반환 함수
const myMap = new Map(); myMap.set(1,"a"); myMap.set(2,"b"); myMap.set(3,"c"); myMap.set(4,"d"); //keys의 const iteratorKey = myMap.keys(); console.log(iteratorKey) //Iterator {} for(const key of iteratorKey){ console.log(key) //순회시 key들이 참조됨 } //value의 const iteratorValue = myMap.values(); console.log(iteratorValue); //Iterator {} for (const value of iteratorValue){ console.log(value); //순회시 value들이 참조됨 } //key와 value 쌍의 const iteratorMap = myMap.entries(); console.log(iteratorMap); //Iterator {} for (const [key,value] of iteratorMap){ console.log(key); //key들을 순회 console.log(value); //value들을 순회 }
4. 콜백을 통한 순회 및 기타 사용팁 ⭐
//콜백 함수로key value를 다루는 방법 myMap.forEach((k,v)=>{ console.log(`key is ${k}`) console.log(`value is ${v}`) })
- 만약 key를 조회 했을때 값이 없다면 초기화 값 넣기
//숫자 1을 키로 가진 값이 없다면 "d"로 매칭한다. myMap.set(1, myMap.get(1)|| "d"); //age 키를 가진 값이 없다면 0으로 설정하고 아니면 값을 가지고 온다음 +1 해서 새로 설정 myMap.set("age",(myMap.get("age")||0) + 1)
살짝 설명을 추가하면
A || B ⇒ A가 거짓이면 B를 해라
A &&B ⇒ A가 참이면 B를 해라
//main.jsx <div> {modalIsOpen&& <Modal/>} </div>
4. 실전에서 사용하는 방법
25.05.05 배열을 value로 가질 때 추가 하는 법
//배열의 push는 끝날때 배열의 길이를 반환 하기에 잘못된 값이 들어감. const clothesMap = new Map(); clothes.forEach((item)=>{ clothesMap.set(item[1],(clothesMap.get(item[1])||[]).push(item[0])); }) //임시 배열을 별도로 선언하고 거기어 추가. const clothesMap = new Map(); clothes.forEach(([item, type])=>{ const arrtemp = clothesMap.get(type)||[]; arrtemp.push(item); clothesMap.set(type,arrtemp); })