곧 있을 네이버 부스트 캠프에서는 2차 코테에서 웹 전형은 JS로 언어가 제한된다고 알고 있다. 그래서 약 한 달 간 JS로 벡준 문제를 풀고 있다. JS 기본 문법은 알고 있지만 실제로 써본 경험이 많이 없다. 요즘 copilot 도 잘 되어 있고 복붙을 많이 하기 때문에 백지 상태에서 처음부터 끝까지 구현하는 경험이 필요하다.
간단한 배열을 입력받아서 정렬하는데에도 문제가 생겼다.
문제는 N개의 문자열을 여러 줄로 입력받아서 사전순으로 정렬이 되어있는지 판별하는 문제다.
N의 범위도 적기 때문에 그냥 내장되어있는 sort를 사용하면 되었다.
const input = require("fs")
.readFileSync(process.platform === "linux" ? "/dev/stdin" : "./ex.txt")
.toString()
.split("\n")
.map(line => line.trim());
const copy = JSON.parse(JSON.stringify(input));
let t = copy.slice(1);
t[2] = '1';
console.log(copy);
코드는 파일 입출력을 통해 여러줄로 입력을 받았고, 문자열로 저장이 되므로 각 line마다 trim을 사용해 개행문자를 없애주었다. 여기서 배열의 복사가 2가지 필요한데, 각각 원본 배열을 내림차순, 오름차순으로 정렬한 배열이다. N 값을 빼주기 위해서 slice 를 사용하는 와중에 궁금증이 생겼다.
JSON.parse(JSON.stringify(input)) 으로 input에 대한 깊은 복사를 만드는 건 알았는데, slice 로 만든 t 배열은 얕은 복사가 되었을 것이다. 하지만 t 배열의 원소를 아무리 바꿔도 copy배열은 아무것도 바뀌지 않았다.
Copilot 과 GPT에게도 물어보고 MDN을 참고해도 t 는 copy 에 대한 얕은 복사한 배열임이 틀림없었다. 하지만 이는 원소의 타입과 관련이 있었다. JS에서는 배열의 원소를 두 가지 타입으로 나누었는데, 하나는 기본형 데이터 ( 숫자, 문자열 등) 나머지 하나는 참조형 데이터 ( 객체, 배열 등 ) 이다. 여기서 얕은 복사를 해도 기본형 데이터는 새롭게 만들어져 할당되기 때문에, 형식적으로 얕은 복사일지라도 아예 새로운 배열이 만들어지는 것이다.
예를 들어보면,
// 배열의 첫 번째 원소는 참조형 객체, 나머지는 기본형 데이터.
const input = [{name: "Alice"}, "line2", "line3"];
const copy = JSON.parse(JSON.stringify(input));
// `copy` 배열의 모든 원소를 얕은 복사하여 `t` 배열을 생성.
let t = copy.slice(0); // t = [{name: "Alice"}, "line2", "line3"]
// t 배열의 첫 번째 객체의 속성을 변경.
t[0].name = 'Dave';
// t 배열의 두 번째 기본형 원소를 변경.
t[1] = '1';
// 결과
console.log(t); // [{name: "Dave"}, "1", "line3"]
console.log(copy); // [{name: "Dave"}, "line2", "line3"]
만약 input 배열에는 첫 번째 원소로 참조형 데이터 (객체) 가 있고, 나머지는 기본형 데이터가 있다. 이를 깊은 복사한 copy를 다시 t에 얕은 복사했다. 이때 t에 할당된 배열의 첫 번째 원소는 얕은 복사가 된 상태이고, 나머지 기본형 데이터는 새롭게 만들어진 데이터다. 따라서 아무리 값을 변경해도 원본 배열에 영향을 주지 않는다.
여기서 slice 함수도 살짝 알아보겠다. slice는 타입이 4가지가 있다.
slice(start, end) 에서 slice는 start index 를 포함하고 end index를 포함하지 않는 배열을 얕은 복사한다.
여기서 중요한 것은 end index는 생략 가능하다. 생략될 경우 배열 길이의 끝까지 복사한다. 여기서 end index 가 원본 배열의 길이를 넘어도, 원본 배열 끝까지 복사한다.
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2));
// Expected output: Array ["camel", "duck", "elephant"]
console.log(animals.slice(2, 4));
// Expected output: Array ["camel", "duck"]
console.log(animals.slice(1, 5));
// Expected output: Array ["bison", "camel", "duck", "elephant"]
console.log(animals.slice(-2));
// Expected output: Array ["duck", "elephant"]
console.log(animals.slice(2, -1));
// Expected output: Array ["camel", "duck"]
console.log(animals.slice());
// Expected output: Array ["ant", "bison", "camel", "duck", "elephant"]
또한 음수가 들어갈 수 있는데, 4번째 예제에서 silce(-2) 는 start index -2, end index 생략이다. 이는 배열의 끝에서 부터 2번째 index를 뜻한다. 따라서 duck 부터 배열의 끝까지 복사가 된 것을 볼 수 있다.
마지막으로 5번째 예제는 slice(2,-1) 이다. 이는 start index 2 , end index -1이다. 이는 camel을 포함하고 elephant를 포함하지 않고 복사하므로 camel, duck이 담긴 모습이다.
이렇게 간단한 배열을 복사해서 정렬하다가 블로그 글을 쓰는 여기까지 왔다. JS 기본기를 다지도록 하자.