[프로그래머스] 해시 > 의상
나의 풀이:
// 더 깔끔히 정리한 버전
function solution3(clothes) {
const map = {}
for (const [name, type] of clothes) {
map[type] = (map[type] ?? 0) + 1;
// map[type] = (map[type] || 0) + 1; // -> 이것도 된다.
}
let count = 1;
for (const key in map) {
count *= (map[key] + 1);
}
return count - 1;
}
// Map을 이용하기
function solution4(clothes) {
const map = new Map();
for (const [name, type] of clothes) {
map.set(type, (map.get(type) ?? 0) + 1);
}
let count = 1;
map.forEach((value) => count *= value + 1)
return count - 1;
}
테스트 코드:
//
const { solution } = require('./201-해시-의상');
describe('Clothes (Hash)', () => {
[
{ clothes: [['yellow_hat', 'headgear']], result: 1 },
// 전부 같은 종류
{ clothes: [['yellow_hat', 'headgear'], ["green_turban", "headgear"]], result: 2 },
{ clothes: [["crow_mask", "face"], ["blue_sunglasses", "face"], ["smoky_makeup", "face"]], result: 3 },
// 전부 다른 종류인데 하나씩
{ clothes: [["crow_mask", "face"], ["blue_sunglasses", "eyewear"], ["green_turban", "headgear"]], result: 7 },
// 2개, 1개
{ clothes: [['yellow_hat', 'headgear'], ["green_turban", "headgear"], ["crow_mask", "face"], ], result: 5 },
// 2개, 1개, 1개
{ clothes: [['yellow_hat', 'headgear'], ["green_turban", "headgear"], ["smoky_makeup", "face"], ["blue_sunglasses", "eyewear"], ], result: 11 },
// // 2개, 2개, 1개
{ clothes: [['yellow_hat', 'headgear'], ["green_turban", "headgear"], ["crow_mask", "face"], ["smoky_makeup", "face"], ["blue_sunglasses", "eyewear"], ], result: 17 },
]
.forEach(({ clothes, result }) => {
it(`should return ${result} when given array is ${clothes}`, () => {
expect(solution(clothes)).toBe(result);
})
})
})
알게된 것:
- Map에 대하여
- 이터러블(Iterable) 객체에 대하여 (→ Map 객체는 이터러블이다)
- 종류별로 나눌 수 있는 상태들을 조합할 땐 ‘없다’까지 한 상태로 쳐서 곱해주면 된다.
Map에 대하여
: 키와 값의 쌍으로 이루어진 컬렉션.
- Map의 메소드: size, has(), set(), get(), forEach(), keys(), values(), entries(), delete(), clear()
- 객체와 유사하나 차이점은,
객체 | Map 객체 | |
---|---|---|
키로 사용할 수 있는 값 | 문자열 또는 심벌 값 | 객체를 포함한 모든 값 |
이터러블 | X | O |
요소 개수 확인 | Object.keys(obj).length | map.size |
- Map 객체는 이터러블이다. (따라서 for …of 문으로 순회가 가능하고 스프레드 문법과 배열 디스트럭처링 할당의 대상이 될 수 있음)
이터러블 (Iterable)
: 이터러블 프로토콜을 준수한 객체.
: Symbol.iterator를 프로퍼티 키로 사용하는 메소드를 가지고 있는 객체. 이는 직접 구현하거나 프로토타입 체인을 통해 상속받을 수 있다.
(참고) Symbol.iterator 프로퍼티 직접 구현 예시 (= 사용자 정의 이터러블 만들기):
// 피보나치 수열을 구현한 사용자 정의 이터러블 const fibonacci = { // Symbol.iterator 메소드를 구현하여 이터러블 프로토콜을 준수한다. [Symbol.iterator]() { let [pre, cur] = [0, 1]; const max = 10; // Symbol.iterator 메소드는 next 메소드르 ㄹ소유한 이터레이터를 반환해야 하고 // next 메소드는 이터레이터 리절트 객체를 반환해야 한다: return { next() { [pre, cur] = [cur, pre + cur]; return { value: cur, done: cur >= max }; } }; } }; console.log([...fibonacci]) // [ 1, 2, 3, 5, 8 ]
- 이터러블인지 확인하는 함수 만들기:
// v가 null이 아니고 Symbol.iterator 프로퍼티가 (존재하고) 함수인가를 체크 const isIterable = v => v !== null && typeof v[Symbol.iterator] === 'function' isIterable([]); // true isIterable(''); // true isIterable({}); // false // 혹은 이렇게도 체크해볼 수 있다: const array = [1, 2, 3]; console.log(Symbole.iterator in array); // true
- 표준 빌트인 객체 중 이터러블인 것들: 배열, 문자열, Map, Set, TypedArray, arguments, DOM 컬렉션(NodeList, HTMLCollection)
- 이터러블 객체가 할 수 있는 일
- for … of 문으로 순회할 수 있다.
- 스프레드 문법의 대상이 된다.
- 배열 디스트럭처링 할당의 대상이 된다.
const array = [1, 2, 3]; // 1. for ... of 문으로 순회 가능 for (const item ofo array) { console.log(item); } // 2. 스프레드 문법의 대상으로 사용 가능 console.log([...array]) // [1, 2, 3] // 3. 배열 디스트럭처링 할당의 대상으로 사용 가능 const [a, ...rest] = array; console.log(a, rest); // 1, [2, 3]
이터레이터 (Iterator)
: 이터러블의 Symbole.iterator 메소드를 호출하면 반환받는 객체.
: 한 마디로, next 메소드를 갖는 객체. next 메소드는 { value: OO, done: XX } 형식의 이터레이터 리절트 객체를 반환하면 된다.
유사 배열 객체
: (이터러블이 아닌) 일반 객체인데 length 프로퍼티를 가지고 있고 키들이 숫자 형식의 문자열인 것. 그래서 length 프로퍼티를 이용해 for 문으로 순회할 수 있고 배열처럼 인덱스로 프로퍼티 값에 접근할 수 있는 객체를 말한다.
예시:
// 유사 배열 객체 const arrayLike = { 0: 1, 1: 2, 2: 3, length: 3 };
즉, for 문으로 순회 가능하고 인덱스로 프로퍼티 값에 접근할 수 있어서 배열과 매우 유사하지만 Symbol.iterator 메소드가 없어서 이터러블은 아닌, 그래서 for … of 문으로 순회가 불가능한 (그리고 스프레드 문법과 배열 디스트럭처링 사용이 불가능한) 객체이다.
- arguments, NodeList, HTMLCollection은 원래 유사 배열 객체였는데 ES6 이후 Symbol.iterator 메소드를 구현하여 이터러블이 됨.
- 배열 자체도 ES6 이후 Symbol.iterator 메소드를 구현하여 이터러블이 됨.
- 유사 배열 객체 또는 이터러블을 배열로 변환하고 싶을 땐(일단 이터러블이 아닌 유사 배열 객체를 이터러블로 만들 수 있다는 장점이 있으므로) :
Array.from() 사용하기.
// 유사 배열 객체 const arrayLike = { 0: 1, 1: 2, 2: 3, length: 3 }; const arr = Array.from(arrayLike); console.log(arr); // [1, 2, 3]
정리: 이터러블과 이터레이터
이터러블은 Symbol.iterator 메소드를 가지고 있는 객체이다. Symbol.iterator()는 이터레이터를 반환하면 된다.
이터레이터는 next 메소드를 가지고 있는 객체이다. next 메소드는 이터레이터 리절트 객체를 반환하면 된다.
따라서 Symbol.iterator 메소드와 next 메소드를 둘 다 가지도록 만들면 이터러블이면서 이터레이터인 객체가 된다. next를 명시적으로 호출할 수 있는 이터레이터의 역할과 for … of 문(과 스프레드 문법, 배열 디스트럭처링)에 넣을 수 있는 이터러블의 역할을 동시에 할 수 있다.
이터러블이면서 이터레이터인 피보나치 함수 예시:
// 이터러블이면서 이터레이터인 객체를 반환하는 함수 const fibonacciFunc = function (max) { let [pre, cur] = [0, 1]; return { // Symbol.iterator 메소드는 this를 반환도록 한다. [Symbol.iterator]() { return this; }, // next 메소드는 똑같이 이터레이터 리전트 객체를 반환하도록 한다. next() { [pre, cur] = [cur, pre + cur]; return { value: cur, done: cur >= max }; } }; }; let iter = fibonacciFunc(10); console.log(iter.next()); // { value: 1, done: false } console.log([...iter]); // [ 2, 3, 5, 8 ]
이터러블이나 이터레이터를 만드는 입장에서,
next에는 언제 순회를 멈출 것인지의 조건을 표현하고,
Symbol.iterator() { return this }를 넣음으로 이터러블의 이득도 전부 챙기게 만들 수 있다.
next 메소드가 반환하는 객체(이터레이터 리절트 객체)에 done 프로퍼티를 제거하면 무한 이터러블을 만들 수 있다.
이터러블이면서 이터레이터인 무한 피보나치 함수 예시:
// 무한 이터러블을 생성하는 함수 const fibonacciFunc = function (max) { let [pre, cur] = [0, 1]; return { [Symbol.iterator]() { return this; }, next() { [pre, cur] = [cur, pre + cur]; return { value: cur, }; // done이 없는 것이 핵심. } }; }; // Max 값을 전달하지 않고 무한 이터러블을 생성한다. for (const num of fibonacciFunc()) { if (num > 10000) break; console.log(num); }
⇒ ’무한’을 조작하기 위해 done 프로퍼티를 조작하기 위해 next 메소드를 조작하기 위해 객체는 이터레이터여야 하고, 또 for … of 문 등으로 편하게 사용하기 위해 Symbol.iterator()를 가지는 이터러블로 구현한다.
지연 평가 (Lazy evaluation)
: 평가 결과가 필요할 때까지 평가를 늦추는(=값을 생성하지 않는) 기법.
: 데이터가 필요한 시점 이전까지는 미리 데이터를 생성하지 않다가 데이터가 필요한 시점이 되면 그때야 비로소 데이터를 생성하는 기법.
모든 데이터를 메모리에 미리 확보한 다음 데이터를 공급하는 배열이나 문자열 등과 달리,
- 불필요한 메모리를 소비하지 않는다.
- 실행 속도가 빠르다.
- 무한도 표현할 수 있다.
는 장점이 있다.
‘데이터가 필요한 시점’ = next 메소드가 호출될 때 (=for … of 문이나 배열 디스트럭처링 할당 등이 실행될 때)
마무리: 이터레이션 프로토콜
이터레이’션’ 프로토콜은 이터러블 프로토콜과 이터레이터 프로토콜을 말한다.
아래의 데이터 소스는 모두 이터레이션 프로토콜을 준수하는 이터러블이다.
Array
, String
, Map
, Set
, TypedArray
(Int8Array, Uint8Array, Uint8ClampedArray, Int16Array, Uint16Array, Int32Array, Uint32Array, Float32Array, Float64Array), DOM 컬렉션(NodeList
, HTMLCollection
), arguments
ES6에서는 순회 가능한 데이터 컬렉션을 이터레이션 프로토콜을 준수하는 이터러블로 통일하여 for … of 문, 스프레드 문법, 배열 디스트럭처링 할당의 대상으로 사용할 수 있도록 일원화했다.
(참고: 모던 자바스크립트 Deep Dive - 이웅모, 위키북스)
※ 위 내용은 스스로 공부하며 적어둔 노트이며 불확실한 내용이 있을 수 있습니다. 학습용으로 적합하지 않음을 유념해주세요. ※
Uploaded by N2T