[JavaScript] 자료구조 Set, map

Olivia·2023년 4월 1일
0

[JavaScript]

목록 보기
2/6
post-thumbnail

set은 자주 사용해서 어느정도 익숙한 편인데, map은 좀처럼 사용해지질 않아서 익숙하지가 않다. 그래서 map을 다시 공부할겸 set도 같이 정리하려고 한다.

Set이란?

중복을 허용하지 않는 유일한 값들의 집합.

set()의 특징

  1. 동일한 값을 중복할 수 ❌
  2. 인덱스로 요소에 접근 ❌
  3. 요소 순서에 의미 ❌
  4. set()을 통해 교집합, 합집합, 차집합, 여집합 등 구현 ⭕️

set객체 생성

const set = new Set();
console.log(set); // Set(0){}
  • set객체는 set생성자를 사용해서 집합을 생성할 수 있다.
  • 인수가 없으면 빈 set객체가 생성된다.
const set1 = new Set([1, 2, 3]);
console.log(set1); // Set(3){1, 2, 3}

const set2 = new Set('123');
console.log(set2); //Set(3) { '1', '2', '3' }
  • set생성자 함수는 인수로 iterable(배열, 맵, 집합, 문자열)을 받는다.
삽입
const set2 = new Set([1, 2, 3]);
set2.add(4); // Set(4) { 1, 2, 3, 4 }
  • add() 메소드를 통해 삽입할 수 있다.
삭제
const set3 = new Set([1, 2, 3, 4]);
set3.delete(4); // Set(3) { 1, 2, 3 }
  • delete() 메소드를 통해 삭제할 수 있다.
일괄 삭제
const set3 = new Set([1, 2, 3, 4]);
set3.clear();
console.log(set3); // Set(0){}
포함여부 확인
const set4 = new Set([1, 2, 3]);
console.log(set4.has(3)); // true
console.log(set4.has(6)); // false
  • has()메소드를 통해 포함 여부를 확인할 수 있는데 결과 값은 true/false로 나온다
중복요소 제거
const set5 = array => [...new Set(array)];
console.log(set5([1, 1, 2, 2, 2, 3, 3, 3, 3])) // [ 1, 2, 3 ]
  • set객체는 중복된 값을 저장하지 않기 때문에 위의 예시처럼 추가해도 중복된 값은 추가되지 않는다.
요소 순회
const set6 = 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객체의 요소를 순회하기 위해선 Set.prototype.forEach를 사용해야한다.
  • Array.prototype.forEach메서드와 유사하게 콜백함수forEach메서드의 콜백함수 내부에서 this로 사용할 객체를 전달할 3가지 객체를 인수로 전달한다.
    • 현재 순회중인 요소값, 현재 순회중인 요소 값, 현재 순회중인 set객체 자체
    • 1, 2번째 인수가 같은 이유 : Array.prototype.forEach메서드와 인터페이스를 통일하게 하기 위해서다.

집합 연산

교집합
Set.prototype.intersection = function(set){
  return new Set([...this].filter(v=>set.has(v)));
}
const set7 = new Set([1, 2, 3, 4, 5]);
const set8 = new Set([2, 5]);

// set7과 set8의 교집합 
console.log(set7.intersection(set8)); // Set(2) {2, 5}
// set8과 set7의 교집합
console.log(set8.intersection(set7)); // Set(2) {2, 5}
  • 여기서 this는 메서드를 호출한 객체다.
  • ... spread 문법으로 새로운 집합을 만든 뒤, filter를 통해서 set.has()와함께 나머지 집합들이 해당 원소를 가졌는지 체크해준다.
합집합
Set.prototype.union = function (set) {
  return new Set([...this, ...set]);
};

const set9 = new Set([1, 2, 3, 4, 5]);
const set10 = new Set([2, 5]);
 
// set9과 set10의 합집합 
console.log(set9.union(set10)); // Set(5) {1, 2, 3, 4, 5}
// set10과 set9의 합집합 
console.log(set10.union(set9)); // Set(5) { 2, 5, 1, 3, 4 }
  • ... spread 문법을 통해 두 set를 합쳐서 새로운 set를 리턴한다.
  • 여기서 주목해야할 점은, 아래 콘솔을 보면 먼저 나오는 set을 부르고, 그 값을 뺀 나머지를 뒤로 출력하는 것을 볼 수 있다.
차집합
Set.prototype.difference = function (set) {
  return new Set([...this].filter(v => !set.has(v)));
};

const set11 = new Set([1, 2, 3, 4, 5]);
const set12 = new Set([2, 5]);

// set11에 대한 set12의 차집합 
console.log(set11.difference(set12)); // Set(3) { 1, 3, 4 } -> set12가 안가지고 있는것만 출력
// set12에 대한 set11의 차집합 
console.log(set12.difference(set11));// Set(0) {} // set11이 안가지고 있는 것만 출력 => 다 가지고 있으니 {}

위와 동일

function differenceSet(set11, set12){
  let difference = new Set(set11)
  set12.forEach(e => {
    difference.delete(e)
  })
  return difference
}
let set11 = new Set([1, 2, 3]);
let set12 = new Set([3, 4, 5]);

console.log(differenceSet(set11, set12)); // Set(2) {1, 2}
console.log(differenceSet(set12, set11)); // Set(2) {4, 5}
  • 위의 예시와 동일하다.
  • 역시 합집합의 경우 순서가 바뀌어도 상관없지만, 차집합의 경우 순서에따라 경과가 다르기때문에 순서에 신경을 써야한다.
부분집합
Set.prototype.isSuperset = function (subset) {
  const supersetArr = [...this];
  return [...subset].every(v => superSetArr.includes(v));
};

cosnt set13 = new Set([1, 2, 3, 4, 5]);
const set14 = new Set([2, 5]);

// set13이 set14의 상위 집합인가 
console.log(set13.isSuperset(set14)); // true
// set14가 set13의 상위 집합인가
console.log(set14.isSuperset(set13)); // false
  • 여기서 thissubset의 상위 집합인지 확인한다.
  • every를 이용하여 모든 [...subset]의 요소들이 superSetArr에 존재하는지 확인한 뒤, true/false 반환
  • 역시 합집합의 경우 순서가 바뀌어도 상관없지만, 차집합의 경우 순서에따라 경과가 다르기때문에 순서에 신경을 써야한다.

정리

add(value) : 값을 추가 & set 반환
delete(value) : 값을 제거
has(value) : set 내에 값 존재하면 = true, 없으면 = false 반환
clear() : set 안의 모든 값 제거
size : set이 가지고 있는 값 개수.

주로 `for`문 돌릴 때 배열의 경우 
`for(let i = 0; i<arr.length; i++)` 이렇게 돌렸는데, 
 `set`으로 돌릴려면 `for(let i=0; i < set.size; i++)`이렇게 돌렸다. 

Map이란?

Map이란 Key-Value 형태의 집합 데이터.

key가 있는 데이터를 저장하고 있기 때문에 객체와 유사하다.
다만, map은 key에 다양한 자료형을 허용, 삽입된 순서를 기억하고 있다.
map()callbackFunction을 실행한 결과를 가지고 새로운 배열을 만들 때 사용한다.

array.map(callbackFunction(currenValue, index, array), thisArg)

callbackFunction, thisArg 두 개의 매개변수가 있다.

callbackFunction :  currentValue, index, array 3개의 매개변수를 갖는다

currentValue : 배열 내 현재 값
index : 배열 내 현재 값의 인덱스
array : 현재 배열
thisArg : callbackFunction 내에서 this로 사용될 값

map 예시

const emotions = ["happy", "sad", "blue"];

map을 이용하면, 배열의 요소를 하나 하나씩 출력할 수 있다.

const emotions = ["happy", "sad", "blue"];
const face = emotions.map(emotion=>console.log(emotion)); 
// happy
// sad
// blue

그렇기 때문에 각 요소에다 다른 값을 추가할 수도 있다.

const emotions = ["happy", "sad", "blue"];
const face = emotions.map(emotion => console.log(`😊 ${emotion}`));
// 😊 happy
// 😊 sad
// 😊 blue

아래처럼 더 간결하게 표현할 수 있다.

const emotions = ["happy", "sad", "blue"];
const addFace = emotion => `😊 ${emotion}`;
const emotionsWithFace = emotions.map(addFace);
console.log(emotionsWithFace);
// (3) ['😊 happy', '😊 sad', '😊 blue']

index가 뭔지도 알아낼 수 있다.

const emotions = ["happy", "sad", "blue"];
const indexWithEmotions = emotions.map((emotion, index)=> `${index} => ${emotion}`);
console.log(indexWithEmotions);
// ['0 => happy', '1 => sad', '2 => blue']

여기서, map((1, 2)=>)의 1번째는 배열의 각 요소를 말하며, 2번째 요소는 index값이다.

값을 더해보자!

let numbers = [2, 4, 6, 8, 10];
let addResults = numbers.map(number => number + 1);
console.log(addResults);
// (5) [3, 5, 7, 9, 11]

map으로 객체를 다뤄보자!

let profiles = [
	{studentId: '01A', name: "Olivia"},
    {studentId: '02B', name: "Violet"},
    {studentId: '03C', name: "ViVian"}
]
let getStudentId = profiles.map(profile => profile.studentId);
console.log(getStudentId); 
// (3) ['01A', '02B', '03C']

let getStudentName = profiles.map(profile => profile.name);
console.log(getStudentName);
// (3) ['Olivia', 'Violet', 'ViVian']

특정 index번호의 학생 이름을 알고싶다면 index번호를 추가하면 된다.

let profiles = [
	{studentId: '01A', name: "Olivia"},
    {studentId: '02B', name: "Violet"},
    {studentId: '03C', name: "ViVian"}
]

let getStudentName = profiles.map(profile => profile.name);
console.log(getStudentName[1]);
// Violet

숫자를 뒤집어보자!

let numbers = [1, 3, 5, 7, 9];
let reverseNumbers = numbers.reverse().map(number=>number);
console.log(reverseNumbers);
// [9, 7, 5, 3, 1]

revers를 통해 배열을 뒤집을 수 있다.
그러나 이렇게만 처리하면 원본 배열 값도 바뀐다.
원본 값은 바뀌지 말아야하기 때문에 slice를 통해 배열을 복사한 뒤 처리하는 것이 좋다.

const numbers = [1, 3, 5, 7, 9];
const reverseNumbersWithSlice = numbers.slice(0).reverse().map(number => number);
console.log(reverseNumberWithSlice);
// (5) [9, 7, 5, 3, 1]
console.log(numbers);
// (5) [1, 3, 5, 7, 9]

💡 slice 란?

배열 값을 잘라서 새로운 배열로 return해준다.

arr.slice(begin, end)
let fruits = ["apple", "grape", "melon", "orange", "banana"];
let favFruits = fruits.slice(1,2);
console.log(favFruits);
// (2) ['grape', 'melon']
console.log(frutis);
// (5) ['apple', 'grape', 'melon', 'orange', 'banana']

역시 원본 배열값은 살아있다.

여기서 end 처리를 하지 않는다면 begin부터 배열 끝까지 새로운 배열로 return해준다.

let fruits = ["apple", "grape", "melon", "orange", "banana"];
let favFruits = fruits.slice(1);
console.log(favFruits);
// (4) ['grape', 'melon', "orange", "banana"]

배열안에 또 다른 배열이 있을 경우, reverse처리를 해보자!

let levels = [["L1", "L2", "L3"], ["L4", "L5", "L6"], ["L7", "L8", "9"]];
let reverseLevel = levels.map(level => level.slice(0).reverse().map(lev=>lev));
console.log(reverseLevel);
// 0: (3) ['L3', 'L2', 'L1']
// 1: (3) ['L6', 'L5', 'L4']
// 2: (3) ['9', 'L8', 'L7']
let levels = [["L1", "L2", "L3"], ["L4", "L5", "L6"], ["L7", "L8", "9"]];
let reverseLevel = levels.slice(0).reverse().map(level => level.map(lev => lev));
console.log(reverseLevel);
// 0: (3) ['L7', 'L8', '9']
// 1: (3) ['L4', 'L5', 'L6']
// 2: (3) ['L1', 'L2', 'L3']

key, value로 반복해보자

Key로 map 순회

let recipe = new Map([['tomatos', 2], ['onion', 1], ['paper', 0.5]]);
for (const ingredient of recipe.keys()){
	console.log(ingredient);
}
// tomatos, onion, paper

value로 map 순회

let recipe = new Map([['tomatos', 2], ['onion', 1], ['paper', 0.5]]);
for(const amount of recipe.values()){
	console.log(amount);
}
// 2, 1, 0.5

[key, value]로 출력

let recipe = new Map([['tomatos', 2], ['onion', 1], ['paper', 0.5]]);
for(const whatINeed of recipe){
	console.log(whatINeed);
}
// (2) ['tomatos', 2]
// (2) ['onion', 1]
// (2) ['paper', 0.5]

💡 기타 정리

set(key, value) : key를 이용하여 value를 저장
get(key) : key에 해당하는 값을 반환. & key가 ❌면 undefined 반환
delete(key) : key 값 삭제
has(key) : key가 존재하면 true, 아니면 false
size : map 요소 개수

👩🏻‍💻 참고 자료
https://www.w3schools.com/js/js_sets.asp
https://www.w3schools.com/js/js_maps.asp
https://ko.javascript.info/map-set

profile
👩🏻‍💻

0개의 댓글