TIL | WIL

2/21 화 (이론 정리 없이 코딩만 한 하루) TIL

깊은바다거북 2023. 2. 22. 00:09

(막간 보충 공부중)

오늘 한 일

  • 프로그래머스 30분 - 옹알이(1)
  • 프로그래머스 30분 - 특정 문자 제거하기
  • 푼 문제 코드 리뷰 및 리팩토링
  • ‘여태 푼 문제 파일 중 랜덤으로 불러오기’ 프로그램 구상 및 개발 중 - fs 모듈 공부중


특정 문자 제거하기

https://school.programmers.co.kr/learn/courses/30/lessons/120826

문자열 replaceAll 이용

function solution1(my_string, letter) {
	return my_string.replaceAll(letter, '');
}

배열 이용

// replaceAll 없이
function solution2(my_string, letter) {
	return my_string.split(letter).join('');
}

배열 내장 함수 없이 (내장 함수를 쓰지 않은 이 방식이 코딩 테스트에서는 가장 좋다...)

function solution3(my_string, letter) {
	let newStr = ''
	for (let char of my_string) { 
		if (char !== letter) {
			newStr += char;
		}
	}
	return newStr;
}

정규식 이용:

// RegExp으로
function solution4(my_string, letter) {
	// 1. 'haha'에서 모든 a를 매치하고 싶다:
	// const regex = /a/g; // haha => hh

	// 2. 문자열 a 말고 모든 변수 letter를 매치하고 싶다:
	// const regex = new RegExp(`${letter}/g`)

	// 3. new RegExp로 만들 때엔 슬래시(/)를 빼야 함. => 그러면 g나 i같은 플래그는 어떻게? => 제 2의 인수로 넣어주면 됨: 
	// const regex = new RegExp(letter, 'g');

	const re = new RegExp(letter, 'g');
	return my_string.replace(re, '');
}

배운 점:

  • 정규식에서, /ha/gnew RegExp(’ha’, ‘g’) 와 같다. (플래그를 2번째 인수로 보내줌)
  • 정규식에서, 변수를 패턴으로 받으려면 /ha/g 방식으로는 안되고 객제 선언 방식으로 해줘야 한다 :
    const letter = 'ha';
    const regex = new RegExp(letter, 'g');

옹알이 (1)

(혼자) 처음에 푼 방식:

function solution1(babblingArray) {
    const possibles = ["aya", "ye", "woo", "ma"];
    let count = 0;
    for (let i = 0; i < babblingArray.length; i++) {
        let word = babblingArray[i];
        word = word.replace("aya", " ")
                .replace("ye", " ")
                .replace("woo", " ")
                .replace("ma", " ")
                .trim();
                if (word === "") {
                    count++;
                }
            }
    return count;
}

⇒ possibles를 사용하지 않고 있다. 나중에 할 수 있는 옹알이가 더 많아지게 될 때를 대비해 .replace()를 계속 추가해야 하는 지금의 상황보다 possibles 객체를 이용해 거기에만 추가해도 되게끔 만드는 것이 좋겠다.

⇒ 또 replace를 한 번 할 때마다 O(N)을 한 번 도는 것과 같다. 사실 nO(N)은 O(N)과 같다고 취급되므로 별 문제는 없지만, 그래도 replace를 네 번, 다섯번 돌게 되는 것을 방지할 수 있다면 더 좋을 것이다.

(Mob programming으로) 리팩토링 이후:

const POSSIBLES = ["aya", "ye", "woo", "ma"];
function solution(arr) {
    let count = 0;
    
    arr.forEach((word) => {
        let j = 0;
        let k = 0;

        while (k !== word.length) {
            const subString = word.slice(j, k + 1);

            if (POSSIBLES.includes(subString)) {
                // console.log(subString, " is a valid babble");
                j = k + 1;
                k = k + 1;
            } else {
                k++;
            }
        }

        if (j === k) {
            count++;
        }
    });
    
    return count;
}

배운 점:

  • Sliding Window 기법 : 루프 한 번에 인덱스 두 개를 놀리는 방식. 코테용 풀이법으로 좋다. 중상 정도의 풀이 점수를 받을 수 있을 것임.

    ⇒ 사실 sliding window 테크닉은 ‘같은 대상을 순회할 때’, 2중 for문을 만들 것을 단일 for문으로 만들 수 있는 기법이라 사용되는 것이다. 이 문제같은 경우엔 순회해야 하는 대상이 가능한 발음 POSSIBLES와 주어진 옹알이 arr로 서로 달라, 결국 둘 다 검사하려면 nested 반복문이 될 수밖에 없는 구조이다. sliding window 테크닉을 쓴다고 하더라도 효용이 없어지게 되는 것이다. 시간복잡도로 따지면 forEach 문 안에 다시 while문이 있으므로 이중 for문, O(N^2)이게 되는 것은 sliding window 방식을 사용하지 않은 풀이와 같게 된다. 다만 sliding window라는 기법을 연습해 볼 수 있었다는 데에 의의를 두자.

Sliding Window 테크닉을 쓸 수 있는 문제는?

문제에서 객체나 배열, 문자열같이 iterable한 데이터 타입이 주어지고 다음과 같은 리턴값을 요구하는 경우:

  1. Minimum value
  1. Maximum value
  1. Longest value
  1. Shortest value
  1. K-sized value

연속된’이라는 키워드가 들어가는 경우.

ex) 주어진 숫자 배열에서, 연속된 두 개의 숫자 쌍 중 합이 가장 큰 쌍을 배열로 반환하기. ⇒ 2-sized value와 maximum value가 모두 요구되는 케이스.

ex) 주어진 문자열에서, 중복된 문자가 없는 가장 긴 부분 문자열(=연속됨)의 길이를 찾기. ⇒ longest value를 요구하는 케이스.

(참조: https://levelup.gitconnected.com/an-introduction-to-sliding-window-algorithms-5533c4fe1cc7)

매일 품새를 연습하기 위해 ‘여태 푼 문제 파일 중 하나를 랜덤으로 불러와주는 프로그램’을 만들고 싶다 (미완)(구상중):

FS

동기와 비동기. 모든 비동기 메소드는 뒤에 ‘Sync’를 달고 있는 형제 동기 메소드가 있다.

예: fs.writeFile() 과 fs.writeFileSync().

파일을 처리하는 데 항상 시간이 걸릴 것이라 감안하여 언제나 비동기 메소드(Sync가 안 붙은)를 써주고 콜백으로 완료 처리를 부탁하도록 하자.

기본 CRUD 메소드:

fs.writeFile(), fs.readFile(), fs.unlink()

구현해야 하는 workflow:

  • 부분 파일명으로 특정 파일 찾기 ⇒ readdir() / read() / readFile() / exists() /
  • 찾은 파일에서 ‘주석’부분까지만 읽어들이기 ✔️
  • 부분 파일명으로 찾은 특정 파일의 이름에서 넘버링 따오기 ⇒ fsPromises.readdir()의 .name속성 /
  • +1 해서 새 파일명으로 삼아 새 파일 만들고
  • 찾은 ‘주석’ 부분을 써넣기.
  • 저장하기(어디에?)
  • 방금 만든 새 파일 편집기로 열어두기.

  • 아니다. …각 kata 파일들은 테스트 코드와 소스 코드가 한 쌍씩만 있는 게 더 깔끔하고 좋으려나…

    한 파일 안에 날짜 구분해서 몽땅 집어넣기의 장단점:

    장: 폴더 관리가 깔끔해진다.

    단: 파일 하나의 구조가 지저분해진다. 어떻게 새롭게 푼 부분을 구분해 넣을 것인가?

    ⇒ 음. 크게 달라진 날과 날의 코드마다 마일스톤처럼 기록으로 남겨두고, 그게 그거같이 비슷비슷하게 풀린 코드는 풀고서 이전 코드를 삭제하는 식으로?

    ⇒ 날짜와 날짜의 구분은 그냥 /* 2023. 02. 20 (월) */ 같이 주석으로 간단히 표시하자.

    여러 파일을 만들 때의 장단점:

    장: 디렉토리 목록을 보고 얼마나 여러 번 반복해서 풀었는지 한 눈에 볼 수 있다. (푼 횟수만큼 파일이 생성되었을 테니) ⇒ 파일명 끝에 반복 횟수를 매번 업데이트 하는 방법으로 대체할 수 있을 것 같다.

    단: 너무 많은 파일이 생성된다. 나중에는 오히려 디렉토리 구조로 한 눈에 파악하기가 더 힘들어지게 된다.

    생각하다보니 여러 파일을 만드는 것은 단점이 너무 크다는 결론이 났다.

    하지만 파일 모듈을 다루는 연습을 위해 한 번 원래의 계획대로 만들어보는 것도 나쁘지 않을 듯? 일단 ‘새 파일명으로 편집기를 여는 것’까지는 하고, 저장하지 않고 파괴하면 되잖아.


Uploaded by N2T