알고리즘 몸풀기

이효범·2022년 4월 11일
0

알고리즘

목록 보기
1/12
post-thumbnail

문자열을 취하고 문자열의 각 문자 수를 반환하는 함수를 만들어 보자.
다음 예시처럼 말이다.

charCount("aaaa");
/* {
	a: 4
} */

charCount("hello");
/* {
	h: 1,
    e: 1,
    l: 2,
    o: 1
} */

charCount("Your PIN number is 1234!");
/* {
	1: 1,
    2: 1,
    3: 1,
    4: 1,
    b: 1,
    e: 1,
    i: 2,
    m: 1,
    n: 2,
    o: 1,
    p: 1,
    r: 2,
    s: 1,
    u: 2,
    y: 1
} */

실제로 코딩 테스트를 볼 때에도 위처럼 몇 가지 예시들을 생각하고 작성해보면서
구현해야하는 솔루션 함수의 구조를 잡아 나가는 것이 좋다.

자 이제 몸을 풀어보자.

function charCount (str) {  // string을 인자로 받는다.
  // 출력해야하는 값은 문자열 내의 소문자, 숫자인 키를 지닌 객체이다.
  // 대문자는 소문자로 치환해서 객체에 담는다.
   
  // 인자로 받은 str을 for문으로 루프를 돌며 세부적인 작업을 진행하자.
  
  // 마지막에 반환할 객체
  return result;
}

위처럼 어떠한 로직으로, 함수가 어떤 작업을 수행해야 할지 주석으로 적어 내려가면 코딩 테스트를 보다가 자신이 어떠한 로직을 구현하고 있는 지 헷갈리거나 하는 경우가 현저히 줄어들 수 있다. 즉 알고리즘 문제를 풀 때 강력히 추천하는 방법이다.

이제 실제로 위의 공백을 채우면서 charCount 함수를 구현해보도록 하자.

function charCount (str) {  // string을 인자로 받는다.
  // 출력해야하는 값은 문자열 내의 소문자, 숫자인 키를 지닌 객체이다.
  let result = {};
  
  // 인자로 받은 str을 for문으로 루프를 돌며 세부적인 작업을 진행한다.
  for (let i = 0; i < str.length; i++) {
    // 대문자는 소문자로 치환해서 객체에 담는다.
    let char = str[i].toLowerCase();
    // 만약 문자가 숫자 혹은 문자열이 아니라면 아무것도 하지 않는다.
    if (/[a-z0-9]/.test(char)) 
  	  if (result[char] > 0) {
        // 만약 char이 문자 혹은 숫자이고, 객체에 키로써 있다면 1을 더해 카운트해주고, 없다면 char을 추가하고 값을 1로 설정해준다.
   	   	result[char]++;
  	  } else {
  	   	result[char] = 1; 
  	  }
  	}
  }
  
  // 마지막에 객체를 반환한다.
  return result;
}

위에서 스트링을 소문자로 바꿔주는 방법은 두 가지 정도가 있을 수 있다. 함수의 시작부분에서 스트링 자체를 바로 소문자 형태로 바꿔주는 방법이 있거나, 루프를 돌면서 각 문자를 소문자로 바꿔주는 것. 위에서는 후자의 방법을 사용했다.

그리고 나서는 반환해야할 객체 안에 현재 바라보는 문자열이 존재하는지 안하는지를 체크해야 한다.
체크해서 존재한다면, 카운트를 증가시켜주면 되고 없다면 키값을 설정해주면서 값으로 1을 추가해주면 된다.

또한 공백이나 느낌표는 추가하지 않는다는 것을 명확히 해주어야 한다.
따라서 위의 조건에 추가적으로 다음 내용을 추가해주어야 한다.
"문자가 숫자 혹은 문자열이며 객체의 키로 존재한다면 1을 카운트에 추가해주고, 없다면 추가해주고 값으로 1을 설정해준다. 만약 문자가 숫자 혹은 문자열이 아니라면 아무것도 하지 않는다."

이를 해결하기 위한 방법은 또한 여러가지일테지만, 주인장은 문자가 영숫자인지 여부를 검사하는 간단한 정규표현식을 추가했다.

자 그렇다면 위의 코드를 더욱 간결하게는 표현할 수 없는지 고민을 해보자.
for 루프를 위처럼 꼭 사용했어야 할까? 단순한 로직인데 코드가 좀 길지 않나?
다음과 같이 리팩토링을 해보는 것도 좋을 것 같다.

function charCount (str) { 
  let result = {};

  for (let char of str) {
    char = char.toLowerCase();
    if (/[a-z0-9]/.test(char)) 
     // result 객체에 result[char]이 있다면 + 1, 없다면 값을 1로 설정.
  	 result[char] = ++result[char] || 1;
  	}
  }
  return result;
}

이런 식으로 코드를 정리하고 양을 줄일 수 있다. 그런데 한 가지 쟁점이 더 있을 수 있다. 정규표현식을 사용하는 방식은 완전한 걸까? 자바스크립트에서 정규 표현식에서 수행 중인 작업이 사용 중인 브라우저에 따라 성능이 달라질 수도 있다는 말을 들은 것 같다. 그렇다면 이를 대체할 수 있는 더 나은 방법은 무엇이 있을까? charCodeAt을 사용할 수 있을 것 같다.

charCodeAt을 구글링 해보면 이를 사용하는 방법이 자세히 나와있다. charCodeAt의 결과값이 47 - 58 사이에 해당하면 숫자 문자 코드이며 64 - 91 사이라면 대문자 알파벳임을 알 수 있고 96 - 123 사이라면 소문자 알파벳이라는 것도 알 수 있다. 게다가 charCodeAt을 사용하는 것이 정규표현식을 사용하는 것보다 55%가 더 빠르다는 결과가 나와있는 것 또한 볼 수 있다.

그렇다면 이를 우리의 코드에 적용해보기만 하면 될 듯 하다. 주인장을 따로 isAlphaNumeric이라는 함수를 따로 빼네는 방식으로 사용하였다.

function charCount (str) { 
  let result = {};

  for (let char of str) {
    char = char.toLowerCase();
    if (isAlphaNumeric(char)) {
     // result 객체에 result[char]이 있다면 + 1, 없다면 값을 1로 설정.
  	 result[char] = ++result[char] || 1;
  	}
  }
  return result;
}

function isAlphaNumeric(char) {
 let code = char.charCodeAt(0);
 if (!(code > 47 && code < 58) && // numeric (0-9)
     !(code > 64 && code < 91) && // upper alpha (A-Z)
     !(code > 96 && code < 123)) { // lower alpha (a-z)
  return false; 
 }
  return true;
}

이와 같이 분리된 함수를 사용하면 가독성을 높일 수 있어서 좋다. 이제 이 함수가 실제로 제대로 잘 작동하는지 확인해 보자.

잘 작동하는 것을 확인할 수 있다.

이렇게 의사 코드를 적어내려가면서 알고리즘 문제를 푸는 것은 위와 같은 예시에서는 그리 중요하게 다가오지 않을 수 있지만, 보다 복잡한 문제의 경우에는 이러한 행위가 구세주가 될지도 모른다. 특히 단계를 작성한다는 것은 방법을 확신하지 못하더라도 수행해야 할 작업을 알고 있다는 의미이다. 이는 면접관에게도 좋은 인상을 남길 수 있다.

profile
I'm on Wave, I'm on the Vibe.

0개의 댓글