Given a number n, draw stairs using the letter "I", n tall and n wide, with the tallest in the top left.
I
I
I
I
I
I
I
I
I
I
그냥 별찍기 문제이다. 처음 프로그래밍을 배울 때 누구나 한번쯤은 해봤던 그 별찍기 문제의 약간 업그레이드 문제랄까. 문제 자체는 어렵지 않다. 특히 자바스크립트에는 repeat()라는 메소드가 있어서 좀 더 편한 것 같다.
repeat(count) 메서드는 문자열을 주어진 횟수만큼 반복해 붙인 새로운 문자열을 반환합니다.
function drawStairs(n) {
let cnt = 0;
let result = '';
while(n > cnt){
result += ' '.repeat(cnt) + 'I';
cnt++;
if(n > cnt) result += '\n';
}
return result;
}
이 풀이가 내가 이 문제를 정리하기로 마음먹은 이유이다.
const drawStairs = n => [...Array(n)].map((_, i) => " ".repeat(i) + "I").join("\n");
풀이를 보면 기발하다. 하나의 문자열을 배열로 생각하여서 실행한 후 그 배열을 다시 문자열로 만들어주었다. 자바스크립트에서는 문자열과 배열 사이의 변환이 자바보다는 더 자연스러운 것 같다. 그런데 이 코드에서 질문이 생겼다.
const a = Array(3);
console.log(a); //[ <3 empty items> ]
console.log(a.length); //3
const _a = new Array(3);
console.log(_a); //[ <3 empty items> ]
console.log(_a.length) //3
const b = [...Array(3)];
console.log(b); //[ undefined, undefined, undefined ]
console.log(b.length); //3
const c = Array(3).fill();
console.log(c); //[ undefined, undefined, undefined ]
console.log(c.length); //3
출력해본 결과를 보면 알 수 있듯이, 배열이 생성자 함수로 생성될 경우는 초기값을 갖지 않는 배열을 생성한다. 그렇기 때문에 각각의 item(배열의 요소)에 정적인 값을 채워주기 위해서 fill()을 사용한다.
fill() 메서드는 배열의 시작 인덱스부터 끝 인덱스의 이전까지 정적인 값 하나로 채워서 변형된 배열을 리턴한다. arr.fill(value[, start[, end]]) 구문으로 사용된다.
만약에 위의 코드처럼 value값이 없다면 undefined로 값을 채우게 된다. 결과적으로 Array(3).fill()과 [...Array(3)]이 같아지게 된다. 즉 이 fill()과 같은 역할을 Spread Operator가 하는 것이다.
const a = [1,2,3];
function myFunction(a,b,c){ }
myFunction(...a); // === myFunction(1,2,3)
위의 코드처럼 요소 하나씩 분리해서 들어가게 만들어 준다. 이와 같은 개념으로 [...Array(3)]으로 하면 요소 3개가 존재하기 때문에 그 요소를 하나씩 분리시켜서 들어가게 하는데, 여기서 변수의 선언에 대해서 조금 알아보면 이해가 된다. 기본적으로 자바스크립트는 var a;로 선언하게 되면 값을 갖지 않는다. 엄밀하게 말하면 자동적으로 기본값이 undefined이 된다. 그 후 값이 할당되면 그 할당된 값을 갖게 된다. 이것을 [...Array(3)] 에 적용시켜보면, ...Array(3)은 배열이긴 배열이지만 내부(요소들)에 값이 없는 배열이다. 즉 길이만 있고 기본값은 없는 배열이다. 그렇기 때문에 forEach(), map()을 이 상태에서 사용하면 아무런 실행을 하지 않는다.(안에 값이 없기 때문에) 여기서 ...을 사용하면 배열 안의 아이템(요소)가 펼쳐지면서 각각에 변수가 선언되는 것과 마찬가지가 된다. 하지만 아직 값은 할당하지 않았기 때문에 undefined인 초기값을 갖게 되는 것이다. 거기에 그것들을 [] 안에 넣게 되면 [ undefined, undefined, undefined ] 라는 새로운 배열이 생성되는 것이다.
위의 설명은 결과와 개념을 보고 최대한 논리적으로 매칭시켜보려고 한 결과입니다.
둘 중에 무엇을 사용하는 것이 좋을까?
효율적인 측면에서는 fill()이 더 좋은 것 같다. 그 부분에 있어서 분석해 놓은 착한 외국인 친구(?)가 있었다.
Array.from()
Array.from()은 유사 배열 객체(array-like object)나 반복 가능한 객체(iterable object)를 얕게 복사해 새로운 Array 객체를 리턴한다.
여기서 유사 배열 객체라 함은 length 속성을 지니고 있는 객체를 말한다. 그렇기 때문에 아래처럼 적으면 위와 같은 배열을 생성 할 수 있다.
const d = Array.from({length: 3});
console.log(d); // [ undefined, undefined, undefined ]
console.log(d.length); // 3
사실 문제를 푸는 시간보다 남의 코드를 보고 왜 저렇게 쓰였는지를 더 분석하게 만든다. 그걸 찾아가면서 꼬리에 꼬리는 무는 질문들로 인해서 배열을 생성 할 수 있는 다양한 방법들과 그것들이 어떻게 다른지에 대해서 알 수 있었다. 더 자세하고 깊이 있는 메카니즘까지는 아직 알지 못하지만 현상적으로, 결과적으로 알 수 있는 부분들에 대해서 알아보았다. 한가지 방법보다 다양한 방법들에 대해서 알아놓으면 여러 상황에 맞게 대처할 수 있을 것이며, 생각의 폭 또한 넓어질 수 있을 것이다.
🚀 문제를 풀어나갈 때 생각의 흐름을 정리합니다. 또한 새로운 풀이에 대한 코드를 분석하고 모르는 부분에 대해서 정리합니다. 생각이 다른 부분에 대한 피드백은 언제나 환영합니다. 틀린 내용에 대한 피드백 또한 항상 감사합니다.
MDN String.prototype.repeat()
MDN Array.prototype.fill()
MND Array.from()
What's the difference between Array(1) and new Array(1) in JavaScript?
Is there a functional way to init an array in JavaScript ES6?
전개 구문
문법과 자료형