
Codewars 7kyu 문제 TV Remote.
Codewars: Achieve mastery through challenge
Codewars is where developers achieve code mastery through challenge. Train on kata in the dojo and reach your highest potential.
www.codewars.com
문자열 입력값이 주어짐. 아래 키보드를 다루는 리모컨을 몇 번 조작해야 입력값을 만들어낼 수 있는지 구하는 문제. 리모컨은 위, 아래, 왼쪽, 오른쪽, OK 버튼이 있음. a에서 시작. 각 글자에서 OK 눌러줘야 함. 3 위치에서 오른쪽 버튼을 눌러도 a로 넘어가지 않음. 왼쪽 버튼을 7번 눌러야 함.

키보드 각 칸을 어떻게 접근할지 생각하는 데 시간이 좀 걸림. HTML CSS로 만든 grid면 querySelectorAll, forEach 등을 쓸텐데..
그러다가 객체가 키를 문자열로 받아들인다는 점이 생각나서 리모컨을 객체로 정리해봄.
const keyboard = {
a: [0,0], b: [0,1], c: [0,2], d: [0,3], e: [0,4], 1: [0,5], 2: [0,6], 3: [0,7],
f: [1,0], g: [1,1], h: [1,2], i: [1,3], j: [1,4], 4: [1,5], 5: [1,6], 6: [1,7],
k: [2,0], l: [2,1], m: [2,2], n: [2,3], o: [2,4], 7: [2,5], 8: [2,6], 9: [2,7],
p: [3,0], q: [3,1], r: [3,2], s: [3,3], t: [3,4], ".": [3,5], "@": [3,6], 0: [3,7],
u: [4,0], v: [4,1], w: [4,2], x: [4,3], y: [4,4], z: [4,5], "_": [4,6], "/": [4,7]
};
이제 함수 입력값의 각 글자를 keyboard 키로 넣어주면 위치를 파악할 수 있음.
keyboard[word[i]][0];
이렇게 쓰면 입력값(word)의 i번째 글자가 위치한 행을 표현한 것.
이제 리모컨을 처음에 조작하기 시작할 때 커서가 a에 놓여 있다는 걸 설정해야 하는데 나는 그냥 입력값 제일 앞에 "a"를 붙임. 입력값을 조작하는 게 좋지는 않은 것 같아서 뒤에서 다른 방법들을 살펴볼 예정.
word = "a" + word;
이제 i번째 글자와 i-1번째 글자 사이의 거리를 구해야 함. 즉 행, 열을 각각 몇 칸씩 움직여야 하는지 계산하는 과정. 커서가 움직이는 방향에 따라 결과가 음수로 나올 수도 있으므로 절대값으로 처리하기로.
let row, column, press = 0;
for (let i = 0; i < word.length - 1; i++) {
row = Math.abs(keyboard[word[i]][0] - keyboard[word[i + 1]][0]);
column = Math.abs(keyboard[word[i]][1] - keyboard[word[i + 1]][1]);
press = press + row + column;
}
row에 위아래로 몇 칸을 움직여야 하는지, column에 좌우로 얼마나 움직여야 하는지 담음. press는 리모컨을 몇 번 눌렀는지 저장하는 변수로 0에서 시작해서 누적함.
종합하면
const tvRemote = function(word) {
const keyboard = {
a: [0,0], b: [0,1], c: [0,2], d: [0,3], e: [0,4], 1: [0,5], 2: [0,6], 3: [0,7],
f: [1,0], g: [1,1], h: [1,2], i: [1,3], j: [1,4], 4: [1,5], 5: [1,6], 6: [1,7],
k: [2,0], l: [2,1], m: [2,2], n: [2,3], o: [2,4], 7: [2,5], 8: [2,6], 9: [2,7],
p: [3,0], q: [3,1], r: [3,2], s: [3,3], t: [3,4], ".": [3,5], "@": [3,6], 0: [3,7],
u: [4,0], v: [4,1], w: [4,2], x: [4,3], y: [4,4], z: [4,5], "_": [4,6], "/": [4,7]
}
word = "a" + word;
let row, column, press = 0;
for (let i = 0; i < word.length - 1; i++) {
row = Math.abs(keyboard[word[i]][0] - keyboard[word[i + 1]][0]);
column = Math.abs(keyboard[word[i]][1] - keyboard[word[i + 1]][1]);
press = press + row + column;
}
return press + word.length - 1;
}
각 글자에서 OK를 누르는 횟수도 포함해야 하므로 마지막에 press가 아닌 press + word.length - 1을 반환. 입력값 앞에 "a"를 덧붙였기 때문에 1 차감.
이렇게 힘겹게 풀고 나름 뿌듯하게 다른 답변들을 보니 역시 공부할 내용이 많았음.
map()과 reduce()를 사용한 방법부터 키보드를 객체에 일일이 담지 않고 수학적으로 접근한 방법, 키보드를 여러 배열로 지정한 방법 등..
우선 Array.prototype.map()
Array.prototype.map()
The map() method creates a new array populated with the results of calling a provided function on every element in the calling array.
developer.mozilla.org
콜백함수에 기존 배열 값들을 차례차레 입력한 다음 결과값들을 새로운 배열로 찍어냄.
이 Codewars 문제에서
word.split("").map(letter => keyboard[letter]);
이렇게 쓰면 입력값을 각 글자의 위치로 표현한 배열로 바꿔줌. 즉,
const keyboard = {
a: [0,0], b: [0,1], c: [0,2]
};
const word = "abc";
const result = word.split("").map(letter => keyboard[letter]);
console.log(result); // [[0,0], [0,1], [0,2]]
여기에 reduce()를 이용하면 각 위치로 움직이는 과정을 가상으로 구현할 수 있음.
Array.prototype.reduce()는 배열 요소들을 콜백함수의 입력값으로 받아 하나의 값을 반환함.
Array.prototype.reduce()
The reduce() method executes a reducer function (that you provide) on each element of the array, resulting in single output value.
developer.mozilla.org
const keyboard = {
a: [0,0], b: [0,1], c: [0,2]
};
const word = "abc";
const result = word.split("").map(letter => keyboard[letter]);
let row, column, press = 0;
const answer = result.reduce((pressed, next) => {
row = Math.abs(pressed[0] - next[0]);
column = Math.abs(pressed[1] - next[1]);
press += row + column + 1;
return next;
}, [0,0]);
console.log(press); // 5
종합하면
const tvRemote = function(word) {
const keyboard = {
a: [0,0], b: [0,1], c: [0,2], d: [0,3], e: [0,4], 1: [0,5], 2: [0,6], 3: [0,7],
f: [1,0], g: [1,1], h: [1,2], i: [1,3], j: [1,4], 4: [1,5], 5: [1,6], 6: [1,7],
k: [2,0], l: [2,1], m: [2,2], n: [2,3], o: [2,4], 7: [2,5], 8: [2,6], 9: [2,7],
p: [3,0], q: [3,1], r: [3,2], s: [3,3], t: [3,4], ".": [3,5], "@": [3,6], 0: [3,7],
u: [4,0], v: [4,1], w: [4,2], x: [4,3], y: [4,4], z: [4,5], "_": [4,6], "/": [4,7]
}
const position = word.split("").map(letter => keyboard[letter]);
let row, column, press = 0;
position.reduce((pressed, next) => {
row = Math.abs(pressed[0] - next[0]);
column = Math.abs(pressed[1] - next[1]);
press += row + column + 1;
return next;
}, [0,0]);
return press;
}
다음은 수학적으로 접근한 방법. 리모컨 각 글자에 수동으로 일일이 자리를 부여하지 않고 나눗셈과 나머지를 활용해서 자리를 지정.
항상 for만 쓰다가 이 김에 for..of도 공부함.
for...of
The for...of statement creates a loop iterating over iterable objects, including: built-in String, Array, array-like objects (e.g., arguments or NodeList), TypedArray, Map, Set, and user-defined iterables. It invokes a custom iteration hook with statement
developer.mozilla.org
for과 마찬가지로 iterable들을 쭉 훑음.
for (var variable of iterable) {
statement
}
이런 식으로 씀.
for...of vs for...in Loops in JavaScript
Forget about traditional for loops, use for...of and/or for...in to iterate over arrays, strings and objects.
alligator.io
const keys = "abcde123fghij456klmno789pqrst.@0uvwxyz_/";
for (const letter of word) {
const i = keys.indexOf(letter);
const position = [Math.floor(i / 8), i % 8];
}
어쨌든 위 코드는 word의 글자마다 letter에 담아서 for...of 안쪽의 과정을 거침.
Math.floor(i / 8)과 i % 8은 각각 입력값의 i번째 글자가 리모컨에 위치한 행과 열을 표현함.
아 그리고 Math.floor(i / 8)은
i / 8 | 0
이렇게 표현해도 되나보다.
|를 찾아보니 Bitwise OR operator라고 한다. 아직 정확하게 이해하진 못했는데 특성상 Math.floor()나 Math.ceil()과 비슷한 효과를 낼 수 있는듯.
이리저리 실험을 해보니 n.x | 0은 n을, n.x | 1은 n+1을 돌려주는듯?
press += Math.abs(position[0] - previous[0]) + Math.abs(position[1] - previous[1]) + 1;
거리를 구하는 방법은 기존과 똑같고
previous = position;
previous 값을 새로운 좌표로 설정하면 반복 1회 끝.
종합하면
const tvRemote = function(word) {
const keys = "abcde123fghij456klmno789pqrst.@0uvwxyz_/";
let previous = [0, 0];
let press = 0;
for (const letter of word) {
const i = keys.indexOf(letter);
const position = [Math.floor(i / 8), i % 8];
press += Math.abs(position[0] - previous[0]) + Math.abs(position[1] - previous[1]) + 1;
previous = position;
}
return press;
}
이런 해결법을 스스로 생각해낼줄 알아야 하는 듯. 열심히 익히자.
'1일1알고리즘' 카테고리의 다른 글
| Two Sum, Hash Table | Leetcode [자바스크립트] (0) | 2020.08.13 |
|---|---|
| Regular Expressions, throw | Codewars [자바스크립트] (0) | 2020.07.27 |
| Simple Fun #265: The Janitor And His Mop, lastIndexOf(), new Array(n), fill() | Codewars [자바스크립트] (0) | 2020.07.08 |
| Calculator: Coin Combination, while for Math.floor() | Codewars [자바스크립트] (0) | 2020.07.08 |
| L2: Triple X, indexOf(), &&로 조건 설정 | Codewars [자바스크립트] (0) | 2020.07.07 |