본문 바로가기

Programming Language/JavaScript

[Javascript] 반복문 STOP! split과 join, 그리고 Map 자료구조

적절한 메서드가 있음에도 나는 또 반복문을 남발해버렸다.

 

그리고 Map 자료구조에 익숙치 않아 결국 문제를 틀려버렸다.

 

내가 풀었던 문제는 다음과 같다.

 


 

문제: 애너그램 걸러내기

애너그램(어구전철)은 단어나 문장을 구성하고 있는 문자의 순서를 바꾸어 다른 단어나 문장을 만드는 놀이입니다.

예시:

nap - pan
ear - are - era
cheaters - hectares - teachers

애너그램으로 만든 단어를 걸러내는 함수 aclean(arr)을 만들어보세요.

예시:

let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];

alert( aclean(arr) ); // "nap,teachers,ear"나 "PAN,cheaters,era"이 출력되어야 합니다.

애너그램 그룹에서 한 단어는 남아있어야 합니다.

 


공식 답안

function aclean(arr) {
  let map = new Map();

  for (let word of arr) {
    // 단어를 글자 단위로 쪼갠 후, 알파벳 순으로 정렬한 다음에 다시 합칩니다.
    let sorted = word.toLowerCase().split('').sort().join(''); // (*)
    map.set(sorted, word);
  }

  return Array.from(map.values());
}

let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];

alert( aclean(arr) );

내가 쓴 답안(틀림)

function aclean(words) {
     
  //1. 모두 소문자로 만들어주기
  let lowerCaseArr = [];
  for(let word of words) {
    lowerCaseArr.push(word.toLowerCase());
  }
  
  
  //2. 'nap'을 [n, a, p]로 만들어준다. 그리고 그 배열들이 모인 새로운 배열을 만든다.
  let wordToCharArr = lowerCaseArr.map(item => {
    let tempArr = [];
    for(let char of item){
      tempArr.push(char);
    }
    return tempArr;
  });


 //3. 각 요소 (ex: ['n', 'a', 'p'])를 정렬해준다.
 for(let i = 0; i < wordToCharArr.length; i++){
    wordToCharArr[i].sort();
 }
 
 
 //4. ['a', 'n', 'p']의 각 요소를 붙여서 한 단어로 만들어준다.
 let charToWordArr = wordToCharArr.map(charArr => {
   let word = '';
   for(let char of charArr){
     word += char;
   }
   return word;
 });
 
 
 //5. Set을 만드는데 iterable 객체를 넣어준다.
 let resultSet = Array.from(new Set( charToWordArr));
  
  return resultSet;
}


let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];

console.log(aclean(arr));

출력 결과

내 코드에는 다음과 같은 문제가 있다.

  1. Set 자료구조를 사용해 중복성은 제거했으나, 단어의 원형을 출력해내진 못했다.
  2. 잦은 for문으로 코드가 복잡하다.

 

나는 다음과 같이 정리하려고 한다.

  1. map 자료구조에 익숙치 않아 틀렸기에, map의 개념 정리하기
  2. for문 대신 쓸 수 있었던 splitjoin 메서드 개념 정리
  3. 추가로 Array.from() 정리
  4. 공식 답안 분석

 

 

맵(Map)

맵(Map)은 키가 있는 데이터를 저장한다는 점에서 객체와 유사하지만, 맵은 키에 다양한 자료형을 허용한다는 점에서 차이가 있다.

 

맵의 주요 메서드 및 프로퍼티

  • new Map() – 맵을 만든다
  • map.set(key, value)  key를 이용해 value를 저장한다.
  • map.get(key)  key에 해당하는 값을 반환한다. key가 존재하지 않으면 undefined를 반환한다.
  • map.has(key)  key가 존재하면 true, 존재하지 않으면 false를 반환.
  • map.delete(key)  key에 해당하는 값을 삭제.
  • map.clear()– 맵 안의 모든 요소를 제거.
  • map.size – 요소의 개수를 반환.

 

⭐맵의 가장 중요한 기능: 맵은 키로 객체를 허용한다.

예시:

let john = { name: "John" };

// 고객의 가게 방문 횟수를 세본다고 가정해 봅시다.
let visitsCountMap = new Map();

// john을 맵의 키로 사용하겠습니다.
visitsCountMap.set(john, 123);

alert( visitsCountMap.get(john) ); // 123

 

ℹ️체이닝

map.set을 호출할 때마다 맵 자신이 반환된다. 이를 이용하면 map.set을 '체이닝(chaining)'할 수 있다. 

map.set('1', 'str1')
  .set(1, 'num1')
  .set(true, 'bool1');

 

맵의 요소에 반복 작업하기

다음 세 가지 메서드를 이용해 맵의 각 요소에 반복 작업 할 수 있다.

  • map.keys() - 각 요소의 키를 모은 반복 가능한(iterable, 이터러블) 객체를 반환한다.
  • map.values() - 각 요소의 값을 모은 이터러블 객체를 반환한다.
  • map.entries() - 요소의 [키, 값]을 한 쌍으로 하는 이터러블 객체를 반환한다. 이 이터러블 객체는 for..of 반복문의 기초로 쓰인다.  

예시:

let recipeMap = new Map([
  ['cucumber', 500],
  ['tomatoes', 350],
  ['onion',    50]
]);

// 키(vegetable)를 대상으로 순회합니다.
for (let vegetable of recipeMap.keys()) {
  alert(vegetable); // cucumber, tomatoes, onion
}

// 값(amount)을 대상으로 순회합니다.
for (let amount of recipeMap.values()) {
  alert(amount); // 500, 350, 50
}

// [키, 값] 쌍을 대상으로 순회합니다.
for (let entry of recipeMap) { // recipeMap.entries()와 동일합니다.
  alert(entry); // cucumber,500 ...
}

 

그 외 정보는 링크에서 확인: https://ko.javascript.info/map-set#ref-773

 

 


split과 join

str.split(delim)

str.split(delim) 메서드는 구분자(delimiter) delim을 기준으로 문자열을 쪼갠다. 

예시:

let names = 'Bilbo, Gandalf, Nazgul';

let arr = names.split(', ');

for (let name of arr) {
  alert( `${name}에게 보내는 메시지` ); // Bilbo에게 보내는 메시지
}

 

ℹ️문자열을 글자 단위로 분리하기

split(s)의 s를 빈 문자열로 지정하면 문자열을 글자 단위로 분리할 수 있다.

let str = "test";

alert( str.split('') ); // t,e,s,t

 

arr.join(glue)

arr.join(glue) 메서드는 인수 glue를 접착제처럼 사용해 배열 요소를 모두 합친 후 하나의 문자열을 만들어준다.

예시:

let arr = ['Bilbo', 'Gandalf', 'Nazgul'];

let str = arr.join(';'); // 배열 요소 모두를 ;를 사용해 하나의 문자열로 합칩니다.

alert( str ); // Bilbo;Gandalf;Nazgul

Array.from()

범용 메서드 Array.from()은 이터러블이나 유사 배열을 받아 '진짜' Array로 만들어준다.

예시:

let arrayLike = {
  0: "Hello",
  1: "World",
  length: 2
};

let arr = Array.from(arrayLike); // (*)
alert(arr.pop()); // World (메서드가 제대로 동작합니다.)

(*)로 표시한 줄에 있는 Array.from은 객체를 받아 이터러블이나 유사 배열인지 조사합니다. 넘겨 받은 인수가 이터러블이나 유사 배열인 경우, 새로운 배열을 만들고 객체의 모든 요소를 새롭게 만든 배열로 복사합니다.

 


공식 답안 분석

자 이제 위에서 공부한 개념으로 공식 답안을 분석해보자.

 

공식 답안

function aclean(arr) {
  let map = new Map();

  for (let word of arr) {
    // 단어를 글자 단위로 쪼갠 후, 알파벳 순으로 정렬한 다음에 다시 합칩니다.
    let sorted = word.toLowerCase().split('').sort().join(''); // (*)
    map.set(sorted, word);
  }

  return Array.from(map.values());
}

let arr = ["nap", "teachers", "cheaters", "PAN", "ear", "era", "hectares"];

alert( aclean(arr) );

 

분석

(*) 로 표시한 줄을 여러 줄에 나눠서 작성하면 아래와 같은 코드가 된다.

let sorted = word // PAN
  .toLowerCase() // pan
  .split('') // ['p','a','n']
  .sort() // ['a','n','p']
  .join(''); // anp
  • str.split('')을 사용하면 문자열을 글자 단위로 분리할 수 있고, str.split('')의 결과는 배열이 된다. 그래서 뒤에 배열 메서드인 .sort()를 사용할 수 있는 것이다. 
  • arr.join('')을 사용하여  배열 요소를 모두 합친 후 하나의 문자열을 만들어줬다. 

 

아래 코드는 단어를 맵에 저장한다.

map.set(sorted, word);

정렬 이후의 글자 구성이 같은 단어를 또다시 만나게 되면, 키가 동일하므로 값이 덮어씌워진다. 따라서 맵엔 글자 구성이 같은 단어는 단 한 번만 저장됩니다.

 

return Array.from(map.values());
  • map.values()는 각 요소의 값을 모은 이터러블 객체를 반환한다.
  • Array.from()이터러블 객체를 배열로 바꿔준다.  

 


추가 참고

반복 가능한(iterable, 이터러블) 객체

  • 배열을 일반화한 객체이다.
  • 메서드 Symbol.iterator가 구현된 객체이다.

더 자세한 내용은 링크 참고: https://ko.javascript.info/iterable


 

앞으로 메서드를 잘 사용하길 바라며 글을 여기서 마친다! 

반복문! 이제 그만!