알고리즘이란?

알고리즘은 특정한 업무를 하기 위해 수행해야 하는 과정이나 단계들의 집합을 의미한다.
정확히는 문제를 해결하기 위해 필요한 수학적 단계들의 집합이다.
이런 알고리즘은 프로그래밍에서 사용하는 거의 대부분의 것들과 관련있다.
때문에 난이도가 높을수록 계획을 잘 세우고 차근차근 접근해야 한다.

문제 해결 접근 방식

1단계: 문제의 이해

문제를 해결하기 전에 우선 문제를 제대로 이해해야 한다.
문제를 정확히 이해하기 위해서는 문제를 자신의 표현으로 다시 적을 수 있어여야 한다.

1. 문제 그대로가 아닌 내가 이해할 수 있는 방식으로 정리해보면?
2. 문제에 맞게 어떤 입력 값이 들어가야 할까?
3. 해결한 뒤 어떤 출력 값이 나와야 할까?
4. 입력에서 출력을 결정할 수 있는가? 즉, 문제를 해결할 수 있는 충분한 정보를 가지고 있는가?
5. 문제의 일부인 중요한 데이터들을 어떻게 이름 지을 것인가?

두 숫자를 더해서 합을 출력하는 함수를 예시로 들어보자.

  1. 문제 그대로가 아닌 내가 이해할 수 있는 방식으로 정리해보면?
    - 두 숫자를 더해서 덧셈을 시행한다.
  2. 문제에 맞게 어떤 입력 값이 들어가야 할까?
    - 두 개의 숫자
    • 그러나 여기서 이 숫자는 정수인가 실수인가?
    • 숫자가 얼마나 클 것인가? (일부 언어들에서는 숫자의 상한선이 있기 때문에)
      자바스크립트에선 일정 이상의 큰 숫자를 입력하면 Infinity라고 출력해버린다. 때문에 상한선 이상의 숫자일 때는 차라리 문자열을 사용해서 더하는 것이 낫다.
    • 언제나 두 개의 숫자인가? 실수로 하나를 빼먹었다면?
  3. 해결한 뒤 어떤 출력 값이 나와야 할까?
    - 숫자
    • 입력과 같이 출력 값은 정수인가 실수인가?
    • 엄청나게 큰 숫자일 때 출력 값을 문자열로 해도 되는가?
  4. 출력 값이 입력 값에 의해 결정될 수 있는가? 즉, 문제를 해결할 수 있는 충분한 정보를 가지고 있는가?
    - 하나의 숫자나 잘못된 값이 들어왔을 때 어떻게 출력할 것인가? 0을 더해야하나? undefined? null?
  5. 문제의 일부인 중요한 데이터들을 어떻게 이름 지을 것인가?
    - 함수는 add, 입력 값은 number1, number2, 출력 값은 sum이 될 수 있을 것이다.

2단계: 구체적인 예제 탐색

  1. 간단한 예제로 시작해본다.
    - 2~3가지 간단한 예시들을 그 입출력값과 함께 적어본다.
  2. 더 복잡한 예제로 진행해본다.
  3. 빈 입력이 있는 예제를 생각해본다. - 비어 있는 인풋을 가지는 예시들을 탐색하는 것이 문제 구성 이해에 도움이 된다.
    - 특히 유효하지 않은 입력값을 가진 면접 상황 예시들에서 더 효과적이다.
  4. 잘못된 입력이 있는 예제를 생각해본다. - 사용자가 유효하지 않은 입력값을 입력한다면? (이런 예외처리는 항상 생각해주어야 한다.)

3단계: 세부 분석

  1. 문제를 해결하기 진행해야할 단계들을 생각해본다.

4단계: 해결 또는 단순화

  1. 문제에서 핵심이 되는 어려운 부분이 뭔지 찾아본다.
  2. 그 어려운 부분은 일단 넘어간다.
  3. 단순화된 해결 방법으로 적어본다.
    - 이 작업을 통해 그 부분이 어떻게 작동해야 하는지에 대한 통찰을 얻는 경우가 있을 수 있다!
  4. 그 다음 다시 어려운 부분으로 돌아가 해결해본다.

5단계: 되돌아보기 및 리팩토링

개발자로서 정말 중요한 단계이다.
구현에 목적을 두지 말고, 내 코드를 분석하여 개선할 점을 찾아야 한다.
코드의 효울성과 가독성에 균형을 두어 리팩토링 해볼 것!

  • 결과를 확인할 수 있는가?
  • 결과를 다르게 도출할 수 있는가?
  • 한눈에 이해할 수 있는가?
  • 결과나 방법을 다른 문제에 사용할 수 있는가?
  • 성능을 향상시킬 수 있는가?
  • 리팩토링하기 위한 다른 방법을 생각해 볼 수 있는가?
  • 다른 사람들은 이 문제를 어떻게 해결했는가?

refactoring 예시

입력 값으로 들어온 문자열을 그 문자열 안에 각각 문자의 개수를 반환하는 함수를 작성하라.

function charCount(str) {
  const obj = {}; // return 해줄 객체 생성
  
  for (var i = 0; i < str.length; i++) { // 문자열을 하나씩 loop
    // 대소문자가 둘 다 들어있는 입력 값을 고려
    const char = str[i].toLowerCase();
    if (/[a-z0-9]/.test(char)) { // 졍규식과 조건문을 통해 영문자와 숫자인 경우만 처리
      if (obj[char] > 0) { // 이미 값이 있으면 ++
        obj[char]++;
      } else { // 값이 없으면 1
        obj[char] = 1;
      }
    }
  }

  return obj; // 분석한 정보를 담은 객체를 반환
}
console.log(charCount('Hello hi'));

1단계 리팩토링 - 최신 문법과 else문 줄이기

function charCount(str) {
  const obj = {};

  for (let char of str) { // let과 for of를 사용
    char = char.toLowerCase();
    if (/[a-z0-9]/.test(char)) {
      obj[char] = ++obj[char] || 1; // 이미 값이 있으면(truthy) 왼쪽, 없으면(falsey) 오른쪽의 값이 대입
    }
  }

  return obj;
}
console.log(charCount('Hello hi'));

2단계 리팩토링 - 정규식 제거, charCode 이용

function charCount(str) {
  const obj = {};

  for (let char of str) {
    if (isAlphaNumeric(char)) { // charCode 구분 함수 실행
      char = char.toLowerCase();
      obj[char] = ++obj[char] || 1;
    }
  }

  return obj;
}

function isAlphaNumeric(char) {
  const code = char.charCodeAt(0);

  if (!(code > 47 && code < 58) && // 숫자(0-9)
      !(code > 96 && code < 123)) { // 소문자(a-z)
    return false;
  }

  return true;
}

console.log(charCount('Hello hi'));

정규 표현식은 어떤 브라우저를 쓰는지에 따라 다를 수 있고, 정규 표현식이 charCodeAt보다 약 55% 느리다.(크롬 기준)
때문에 charCodeAt과 같은 방법으로 대체할 수 있다.