2024를 마무리하며 무릇 많은 성장을 이뤘고 그 속에서 배운 점과 부족하다 느낀 점, 앞으로의 방향성 등을 잡을 수 있었습니다. (이건 회고록에서 자세히..)
그 방향성 중 하나가 JS로 코딩테스트를 준비해야겠다 ! 였습니다.
사실 전 C++로 코딩테스트를 준비해왔고.. 나름 근본 언어라는 자부심과 함께 열심히 공부를 하였으나... 현실은 JS만 취급해주는 기업이 점점 늘고있으며, 왜인지 모르겠지만 C++만 제외되는 코테 (눈물의 카카오 모빌리티🥲)도 생기는 듯 합니다. (예상하기론 개발 공부는 안하고 코테만 공부했던 친구들을 걸러내기 위함이 아닐까 함, 그런데 이제 나도 함께 걸러져버린..)
그래서 2025 1, 2월동안 C++과 JS 두 마리 토끼를 모두 잡으려 합니다. (C++, Java, Python만 가능한 기업도 있으니 안전하게..!)
서론이 길었네요, 알고리즘을 풀며 새롭게 알게된 배열 메소드, JS 문법 등을 적으려 합니다.
~ 문제 풀면서 계속 추가 예정 ~
1. 입력
fs 모듈
입력 처리 방식
파일 전체를 한 번에 읽습니다.
장점
빠르다
적합한 경우
입력 데이터가 고정적이거나, 한꺼번에 처리할 수 있을 때
백준처럼 입력이 표준 입력(stdin)으로 한꺼번에 제공되는 경우
const fs = require("fs");
// vscode에선 "input.txt", 백준에선 "/dev/stdin"
const input = fs.readFileSync("input.txt").toString().split(" ");
readline 모듈
입력 처리 방식
데이터를 한 줄씩 읽으며 처리
속도
fs보다 느림, 한 줄 입력마다 line 이벤트를 발생시키며 처리하기 때문에 추가적인 오버헤드가 발생
적합한 경우
입력 데이터가 매우 많거나, 실시간으로 데이터를 처리해야 할 때
데이터를 한 줄씩 읽으며 바로바로 처리해야 하는 문제
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
});
let input = [];
rl.on('line',(line)=>{
input = line.split(' ');
}).on('close',()=>{
console.log(input[0], input[1]);
});
결론: fs 모듈을 사용하자
코딩테스트 중에선 실시간으로 입력을 받는 일이 없기 때문에 굳이 느린 readline 모듈을 사용할 이유가 없습니다.
따라서 항상 fs.readFileSync(0, 'utf8')을 사용하면 되겠습니다.
2. 출력
console.log를 활용하면 되는데, 백트레킹처럼 중간중간 출력을 해줘야 하는 경우에는 string 변수에 저장해뒀다가 한꺼번에 출력하는게 효율적이다.
3. 메소드
사실 이거때문에 글을 작성하기 시작한 것이었습니다. 처음 알게된, 혹은 자주 까먹는 메소드들을 적어보겠습니다
String.prototype.repeat()
주어진 문자열을 지정된 횟수만큼 반복하여 새 문자열을 반환
const readline = require('readline');
const rl = readline.createInterface({
input: process.stdin,
output: process.stdout
})
let input=[];
rl.on('line', (line)=>{
const input = line.split(' ');
const str = input[0];
const num = Number(input[1]);
console.log(str.repeat(num));
rl.close()
})
String.prototype.toUpperCase(), String.prototype.toLowerCase()
입력받은 문자열을 대문자로, 소문자로 변경
for(let i = 0; i < str.length; i++){
if('A'<= str[i] && str[i] <= 'Z'){
answer += str[i].toLowerCase();
}else{
answer += str[i].toUpperCase();
}
}
4. 그 외
정규표현식
ex) str이 소문자인지 확인 후 소문자면 대문자로, 대문자면 소문자로 변경
정규표현식을 사용하면 아래처럼 긴 if문 조건을 사용하지 않아도 된다.
const str = 'a';
/** 정규표현식 사용 */
const regex = /[a-z]/
console.log(regex.test(str) ? str.toUpperCase() : str.toLowerCase())
/** 정규표현식 미사용 */
if('A' <= str && str <= 'Z'){
console.log(str.toLowerCase());
} else if ('a' <= str && str <= 'z'){
console.log(str.toUpperCase());
}
string to Array
만일 내가 'example'을 입력받았고, 이를 ['e', 'x', 'a', 'm', 'p', 'l', 'e']로 바꾸고 싶다면 아래처럼 spread 연산자를 사용하면 된다.
const str = 'example';
const arr = [...str]; // ['e', 'x', 'a', 'm', 'p', 'l', 'e']
그게 아니라 ['example']로 바꾸고 싶다면 냅다 넣으면 된다.
const str = 'example';
const arr = [str]; // ['example']
배열 구조분해 할당
배열의 속성을 해체하여 그 값을 개별 변수에 담을 수 있게 하는 JS 표현식입니다.
let a, b, rest;
[a, b] = [1, 2];
console.log(a, b); // 1, 2
[a, b, ...rest] = [1, 2, 3, 4, 5];
console.log(a, b) // 1, 2
console.log(rest) // [3, 4, 5]
기본 변수 할당
var foo = ["one", "two", "three"];
var [red, yellow, green] = foo;
console.log(red); // "one"
console.log(yellow); // "two"
console.log(green); // "three"
선언에서 분리한 할당
변수의 선언이 분리되어도 구조 분해를 통해 값을 할당할 수 있습니다.
var a, b;
[a, b] = [1, 2];
console.log(a); // 1
console.log(b); // 2
기본값
변수에 기본값을 할당하면, 분해한 값이 undefined일 때 그 값을 대신 사용합니다.
var a, b;
[a = 2, b = 3] = [1];
console.log(a); // 1
console.log(b); // 3
변수 값 교환하기
하나의 구조 분해 표현식만으로 두 변수의 값을 교환할 수 있습니다.
구조 분해 할당 없이 두 값을 교환하려며 임시 변수가 필요합니다.
var a = 1;
var b = 2;
[a, b] = [b, a];
console.log(a); // 2
console.log(b); // 1
함수가 반환한 배열 분석
함수는 이전부터 배열을 반환할 수 있었습니다. 구조 분해를 사용하면 반환된 배열에 대한 작업을 더 간결하게 수행할 수 있습니다.
function f(){
return [1, 2];
}
var a, b;
[a, b] = f();
console.log(1);
console.log(2);
일부 반환 값 무시하기
필요하지 않은 반환 값을 무시할 수 있습니다.
function f() {
return [1, 2, 3];
}
var [a, , c] = f();
console.log(a); // 1
console.log(c); // 3
변수에 배열의 나머지를 할당하기
var [a, ...b] = [1, 2, 3];
console.log(a); // 1;
console.log(b); // [2, 3];
var [a, ...b ,] = [1, 2, 3]; // SyntaxError: rest element may not have a trailing comma
Array.prototype.splice
splice()는 배열의 기존 요소를 삭제 또는 교체하거나 새 요소를 추가하여 배열의 내용을 변경할 때 사용합니다.
구문
array.splice(start[, deleteCount[, item1[, item2[, ...]]])
start
배열의 변경을 시작할 인덱스
deleteCount
배열에서 제거할 요소의 수, deleteCount를 생략하거나, 값이 array.length-start 보다 크면 start부터 모든 요소를 제거
만일 deleteCount가 0이하라면 어떤 요소도 제거하지 않음
item1, item2 ...
배열에 추가할 요소, 아무 요소도 지정하지 않으면 splice()는 요소를 제거하기만 함
// 1. 하나도 제거하지 않고, 2번 인덱스에 "drum" 추가
var myFish = ["angel", "clown", "mandarin", "sturgeon"];
var removed = myFish.splice(2, 0, "drum");
// 2. 하나도 제거하지 않고 2번 인덱스부터 "drum", "guitar" 추가
var myFish = ["angel", "clown", "mandarin", "sturgeon"];
var removed = myFish.splice(2, 0, "drum", "guitar");
//3. 3번 인덱스에서 한 개의 요소 제거
var myFish = ["angel", "clown", "mandarin", "sturgeon"];
var removed = myFish.splice(3, 1);
//3. 0번 인덱스에서 두 개 요소 제거하고 "parrot", "anemone", "blue" 추가
var myFish = ["angel", "clown", "mandarin", "sturgeon"];
var removed = myFish.splice(0, 2, "parrot", "anemone", "blue");
Array.prototype.slice()
어떤 배열의 begin부터 end까지 (end는 미포함)에 대한 얕은 복사본을 새로운 배열 객체로 반환, 원본은 변경되지 않음
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2)); // ['camel', 'duck', 'elephant']
console.log(animals.slice(2, 4)); // ['camel', 'duck']
console.log(animals.slice(1, 5)); // ["bison", "camel", "duck", "elephant"]
Array.prototype.join()
배열의 모든 요소를 쉼표나 지정된 구분 문자열로 구분하여 연결한 새 문자열을 반환
배열에 항목이 하나만 있는 경우에는 구분 기호를 사용하지 않고 반환
const elements = ['Fire', 'Air', 'Water'];
console.log(elements.join()); // "Fire,Air,Water"
console.log(elements.join('')); // "FireAirWater"
console.log(elements.join('-')); // "Fire-Air-Water"
Array.prototype.reduce()
배열의 각 요소에 대해 주어진 reducer 함수를 실행하고, 하나의 결과값을 반환
const arr = [1, 2, 3, 4];
const initialValue = 0;
const sumWithInitial = arr.reduce((accumulator, currentValue)=>{
accumulator + currentValue,
initialValue,
})
console.log(sumWithInitial); // 10
리듀서 함수는 네 개의 인자를 가짐
1. 누적된 값 (acc)
2. 현재 값 (cur)
3. 현재 인덱스 (idx)
4. 원본 배열 (src)
구문
arr.reduce(callback[, initialValue])
callback 함수
배열의 각 요소에 대해 실행할 함수, 다음 네 가지 인수를 받음
accumulator
누적된 값은 콜백의 반환값을 누적함. 콜백의 이전 반환값 또는 콜백의 첫번째 호출이면서 initialValue를 제공한 경우에는 initialValue임
currentValue
처리할 현재 요소
currentIndex (optional)
처리할 현재 요소의 인덱스, initialValue를 제공한 경우 0, 아니면 1부터 시작
array (optional)
reduce를 호출한 배열
String.prototype.split()
String 객체를 지정한 구분자를 이용해 여러 개의 문자열로 나누고, 주여진 문자열을 seperator마다 끊은 부분 문자열을 담은 Array를 반환
// input.txt
5 1 4
const fs = require('fs');
const input = fs.readfileSync('input.txt', 'utf-8').trim(); // ' 5 1 4 ' -> '5 1 4'
const arr = input.split(" "); // ["5", "1", "4"]
String.prototype.trim()
문자열 양 끝의 공백을 제거하면서 원본 문자열을 수정하지 않고 새로운 문자열을 반환
한쪽 끝의 공백만 제거한 새로운 문자열을 반환하고 싶으면 trimStart() / trimEnd()를 사용하면 됨
const world = " Hello World ! ";
console.log(world); // " Hello World ! "
console.log(world.trim()); // "Hello World !"
console.log(world.trimStart()); // "Hello World ! "
console.log(world.trimEnd()); // " Hello World !"
Array.from()
Array.from() 정적 메서드는 순회가능 혹은 유사 배열 객체에서 얕게 복사된 새로운 Array 인스턴스를 생성합니다.
console.log(Array.from("foo")); // ["f", "o", "o"]
console.log(Array.from([1, 2, 3], (x) => x+x)); // [2, 4, 6]
구문
Array.from(arrayLike)
Array.from(arrayLike, mapFn)
Array.from(arrayLike, mapFn, thisArg
arrayLike
배열로 변환할 순회 가능 또는 유사 배열 객체
순회 가능 객체란 ?
Map, Set과 같은 경우
유사 배열 객체
length 속성과 인덱싱된 요소가 있는 객체
mapFn
배열의 모든 요소에 대해 호출할 함수, 이 함수를 제공하면 배열에 추가할 모든 값이 이 함수를 통해 먼저 전달되고 mapFn의 반환 값 대신 배열에 추가 됨
thisArg
mapFn 실행 시에 this로 사용할 값
Array.prototype.fill()
배열의 인덱스 범위 내에 있는 모든 요소를 정적 값으로 변경후 수정된 배열을 반환
const arr = [1, 2, 3, 4];
console.log(arr.fill(0, 2, 4)); // [1, 2, 0, 0]
console.log(arr.fill(5, 1)); // [1, 5, 5, 5]
console.log(arr.fill(6)); // [6, 6, 6, 6]
구문
arr.fill(value)
arr.fill(value, start)
arr.fill(value, start, end)
value
배열을 채울 값들. 배열의 모든 요소는 정확히 이 값이 될 것임. value가 객체인 경우, 배열의 각 슬롯은 해당 객체를 참조
start
채우기를 시작할 0 기반 인덱스로, 정수로 변환
음수 인덱스는 배열의 끝부터 거꾸로 셈
end
채우기를 끝낼 0 기반 인덱스, 정수로 변환. end - 1까지 채움
// 1로 채워진 5*5 행렬 만들기
const arr = new Array(3);
for(let i = 0; i < arr.length; i++){
arr[i] = new Array(3).fill(1); // 크기가 5이고, 1로 채워진 배열 생성
}
arr.forEach((arrs)=>{
const row = "";
arrs.forEach((num) => {
row += num + " ";
})
console.log(row);
})
/** 출력
1 1 1
1 1 1
1 1 1
*/
spread 연산자와 Array.prototype.push()의 차이
아래 두 코드는 동일한 값을 냅니다. 그러나 각 코드는 다른 특징을 가지고 있습니다.
/** spread 연산자 사용 */
arr.reduce((list, num) => [...list, ...new Array(num).fill(num)], []);
/** push 사용 */
arr.reduce((list, num) => {
list.push(...new Array(num).fill(num));
return list;
}, []);
1. spread 연산자
spread 연산자는 기존 list 배열과 새로운 배열을 합쳐 새로운 배열을 반환합니다.
매번 새로운 배열을 만들어 반환하기 때문에 이전의 배열의 상태를 유지하지 않고 새로운 배열을 생성하게 됩니다.
장점
- 불변성
- 배열을 매번 새로 생성하는 방식이기 때문에 기존 배열을 변경하지 않고 새로운 배열을 반환합니다. 따라서 불변성을 중요시하는 코드 스타일에서 선호될 수 있습니다.
- 함수형 프로그래밍 스타일
- 새로운 배열을 반환하는 방식은 함수형 프로그래밍 스타일에 맞춰 사이드 이펙트가 없고 순수함수처럼 동작합니다
단점
- 성능 저하
- 배열을 매번 새로 생성하고 복사하기 떄문에 성능이 떨어질 수 있습니다. 특히 배열의 크기가 커질수록 더 많은 메모리와 시간을 소비할 수 있습니다.
- 메모리 사용 증가
- 매번 새로운 배열을 생성하기 때문에 메모리 사용량이 증가할 수 있습니다.
2. push 사용
기존 배열에 값을 직접 추가함으로써 배열을 직접 수정합니다.
push 는 배열을 밴경하는 메서드로, 원본 배열 list에 직접 값을 추가합니다. 따라서 같은 배열 객체를 계속해서 사용합니다.
장점
- 성능 효율성
- 배열을 수정하는 방식이기 때문에 불필요한 배열 복사 없이 배열을 직접 수정할 수 있습니다. 따라서 메모리와 시간 효율성에서 더 낫습니다.
단점
- 불변성 위반
- 배열을 직접 수정하기 때문에 불변성을 유지하지 않습니다. 특히나 함수형 프로그램이 스타일이나 상태고나리에서 불변성을 중시하는 경우 단점이 될 수 있습니다.
- 사이드 이펙트
- 배열을 수정하는 방식은 함수 외부에서 list를 참조하고있으면 예상치 못한 사이드 이펙트가 발생할 수 있습니다.
String.prototype.charCodeAt()
주어진 인덱스의 UTF-16 코드 단위를 표현하는 0과 65535 사이의 정수를 반환
유니코드를 받고 싶을 때 사용
const num = 'A'.charCodeAt(0);
console.log(num); // 65
제너레이터
일반 함수는 하나의 값 (혹은 0개의 값)만을 반환합니다.
하지만 제너레이터를 사용하면 여러 개의 값을 필요에 따라 하나씩 반환(yield)할 수 있습니다.
제너레이터 함수
제너레이터를 만들려면 '제너레이터 함수'라 불리는 특별한 문법 구조, `function*` 이 필요합니다.
제너레이터 함수는 일반 함수와 동작 방식이 다릅니다. 제너레이터 함수를 호출하면 코드가 실행되지 않고, 대신 실행을 처리하는 특별한 객체인 `제너레이터 객체`가 반환됩니다.
function* generatorFn(){
yield 1;
yield 2;
yield 3;
return 4;
}
let gen = generatorFn();
alert(gen); // [object Generator], 아직 함수 본문 코드는 실행되지 않음
next()
`next()`는 제너레이터의 주요 메서드입니다.
`next()`를 호출하면 가장 가까운 yield <value> 문을 만날 때까지 실행이 지속됩니다. 만일 value 값이 없다면 undefined가 반환됩니다.
`next()`는 항상 두 프로퍼티를 가진 객체를 반환합니다.
- value: 산출 값
- done: 함수 코드 실행이 끝났으면 `true`, 아니라면 `false`
function *generator(){
yield 1;
yield 2;
return 3;
}
let gen = generator();
let one = gen.next();
console.log(JSON.stringify(one)); // {value: 1, done: false}
let two = gen.next();
console.log(JSON.stringify(two)); // {value: 2, done: false}
let three = gen.next();
console.log(JSON.stringify(three)); // {value: 3, done: true}
let next = gen.next();
console.log(JSON.stringify(next)); // {done: true}
제너레이터와 이터러블
`next()`를 보면 알 수 있듯 제너레이터는 이터러블 입니다.
따라서 `for...of` 반복문을 사용할 수 있습니다.
function *generator(){
yield 1;
yield 2;
return 3;
}
let gen = generator();
for(let value of gen){
console.log(value); // 1, 2
}
그러나 위 예시에서도 알 수 있듯 3은 출력되지 않습니다. 그 이유는 `for...of` 이터레이션이 `done:true`일 때 마지막 value를 무시하기 때문입니다.
따라서 `for...of`를 사용했을 때 모든 값이 출력되길 원한다면 yield로 값을 반환해야 합니다.
이진수 만들기
자바스크립트에서 N진수를 만드는 방법은 간단합니다.
1. 10진수를 진수 변환할 때는 Number 객체의 내장함수인 `toString()`을 사용하면 됩니다.
2. 10진수 외의 다른 진수를 10진수로 변환할 때는 전역 함수인 `parseInt()`를 활용한다.
/** 10진수 -> N진수 */
let decimalNum = 125;
console.log(Number(decimalNum).toString(2)); // 1111101, 2진수
console.log(Number(decimalNum).toString(16)); // 7d, 16진수
/** 2진수 -> 8진수 */
let binary = 1111101;
console.log(parseInt(binary, 2).toString(8)); // 175
/** 8진수 -> 10진수 */
let octalNum = 175;
console.log(parseInt(octalNum, 8)); // 125
Array.prototye.some()
some() 메서드는 배열 안의 어떤 요소라도 주어진 판별 함수를 적어도 하나라도 통과하는지 테스트합니다.
만일 배열에서 주어진 함수가 true를 반환하면 true를, 그렇지 않으면 false를 반환합니다.
이 메서드는 배열을 변경하지 않습니다.
const array = [1, 2, 3, 4, 5];
const even = (element) => element % 2 === 0;
console.log(array.some(even)); // Expected output: true
배열에서 최댓값 찾기
const arr = [1, 2, 3, 4];
const maxNum = Math.max(...arr);
const arr = [{num: 1, idx: 2}, {num: 7, idx: 2}, {num: 5, idx: 2}, {num: 3, idx: 2}]
const maxNum = Math.max(...arr.map((a) => a.value));
'💛 Javascript' 카테고리의 다른 글
[JS] Cannot read properties of null (reading 'addEventListener') feat. 브라우저 렌더링 과정 (2) | 2024.07.26 |
---|---|
[JS] Array와 메서드 등에 대해 알아보자 (6) | 2024.07.15 |