돌멩이 하나/셀프 크리틱

[알고리즘] modulo 구현

미래에서 온 개발자 2022. 12. 8. 23:05

앞선 포스팅에서 얘기한 것처럼 부트캠프에서 매일 하루에 한 문제씩 알고리즘 문제를 풀게 하면서 하루 일과를 시작한다. (a.k.a. Daily Coding) 

이제까지 문제를 딱 보고 pseudo code를 하나도 적지 않고 바로 푼 문제도 있고, 1, 2, 3 번호를 매겨가며 pseudo code를 적은 다음 푼 문제도 있지만 어...? 하고 2초 간 얼었던 문제는 어제가 처음이었다.

 

나눗셈(/)과 나머지(%) 연산자를 사용하지 않고 두 수의 나머지를 구하는 함수를 작성하는 것

 

 

잠시 삼천포를 건널테니 이 포스팅을 읽는 여러분도 위의 함수를 어떻게 짤 지 한 번 생각해 보시면 좋겠다.

 

개인적으로 부트캠프에서 이제까지 했던 과제 중 가장 인상적인 과제를 꼽으라면 두 가지를 들 수 있겠다. (섹션이 끝날 때마다 웹 애플리케이션을 구현하는 개인 과제는 제외하겠다) 

 

  1. JavaScript Koans
  2. Underbar

 

첫 번째는 짧지만 이전에 남긴 포스팅 링크로 갈음하고, 두 번째 underbar 과제는 아마 지금은 아무도 쓰지 않고 역사의 뒤안길로 사라진 underscore.js를 대신해서 배열 내장 메소드가 없었던 시절의 자바스크립트를 떠올리며, 내장 메소드들을 하나하나 만들어 보는 과제였다. 

 

forEach, indexOf, map, filter, reduce 등을 해당 메소드들을 쓰지 않고 직접 함수를 만들어 보는 것이었다. 당연히도 이러한 내장 메소드들을 구현하기 위해서는 배열을 순회하는 과정이 필요하기에 forEach를 대신하는 _each 함수를 만들고, _each 함수를 활용해서 indexOf, map, filter, reduce(끝판왕...!) 등의 각종 메소드를 직접 만들어보는 과제였다.  

 

배열 내에 어떤 element가 있는지 여부를 판단해서 boolean 타입을 반환하는 includes 메소드를 indexOf를 활용해서 arr.indexOf(element) === -1 이라면 false를 반환하고, arr.indexOf(element) !== -1 이라면 true를 반환하게 할 수 있는 것처럼 push, pop, unshift, shift 를 제외하고는 어떠한 내장 메소드도 쓸 수 없다는 가정 하에 배열의 여러 내장 메소드를 직접 만드는 것이라고 생각하면 쉽다. (....쉬운가? 😇)

 

고차함수에 대한 이해를 높이기 위해 주어진 과제였는데, 내장 메소드가 얼마나 소중한 것인지를 절절히 느끼는 시간이었다. 동시에 내장 메소드라는 것이 약속된 객체의 함수를 정의해놓고 그것을 꺼내어 쓰는 것이구나 라는 아주 원초적인 정의를 몸소 깨달을 수 있었다. 

 

그러다 만난 모듈러 구현 알고리즘 문제...! (두둥) 

 

나눗셈과 모듈러 연산 없이 모듈러를 어떻게 구현하지...? 🤔 

잠시 2초 동안 얼음이 되었지만 underbar 과제를 떠올리며 예시로 나온 25와 4를 인자로 받았을 때 1이 나올 과정을 생각해 보았다 (= 종이에 적어 보았다).

 

무한 루프를 돌리면 되는구나...!

 

어떻게 접근해야겠다는 생각만 바로 서면 그 다음부터는 그 접근법을 컴퓨터가 이해할 수 있는 코드로 적어내려가면 된다. 

 

function modulo(num1, num2) {
  // 0으로 나누는 edge case 처리
  if (num2 === 0) return 'Error: cannot divide by zero'

  let temp = num1; 

  while (temp >= num2) {
    temp -= num2
  }

  return temp
}

 

🤔 위의 코드에 대한 셀프 크리틱: 
temp라는 변수를 굳이 정의하지 않고 num1로 대체해도 되는데 함수의 인자로 들어온 변수는 전역 변수가 아닌 지역 변수라는 점이 아직 완전히 와닿지 않아서인지 매번 저렇게 불필요한 변수를 선언하게 된다. 

 

메시지가 잡히면 그걸 그대로 반대 언어로 통역하면 되는 과정과 똑같아서 알고리즘 문제를 풀 때마다 통역 노트테이킹을 생각하게 된다. 나는 통역할 때부터 다른 사람들보다 chunking(문장의 단어들을 각각 따로 떼어내지 않고, 같은 의미를 한 덩어리로 인지하는 것)이 자동으로 잘 되는 편이었어서 연사가 말하고자 하는 바(메시지)를 잘 파악하면 노트테이킹을 따로 하지 않아도 통역을 잘 하는 축에 속했다. 알고리즘 문제를 풀 때도 똑같아서 어떻게 해 나가야겠다 라는 방향성만 잡히면 pseudo code를 쓰지 않고도 문제를 바로 푸는 편이다. pseudo code를 쓸 때는 오히려 방향성이 잡히지 않아서 내가 어떤 부분에서 뭘 잘못했는지 트래킹하기 위한 디버깅 용도로 작성한다. 

(프로그래밍과 통번역의 유사성이라는 주제는 아예 날 잡고 따로 썰을 풀어야 한다 ㅋㅋㅋ) 

 

모듈러 연산을 구현하면서 underbar 과제와 마찬가지로 고차원(high order)적인 동작도 결국에는 기본적인 연산을 통해서 미리 약속된 동작을 수행하는 것이라는 사실을 다시 한 번 깨달았다. 나눗셈과 모듈러 연산 없이 뺄셈으로 나머지 연산을 구현할 수 있는 것처럼 말이다. 

 

그러면서 한 가지 의문이 생겼다. 사칙연산 중의 기본인 덧셈과 뺄셈을 컴퓨터에게 어떻게 정의했을까? 덧셈을 정의하면 곱셈은 쉽다. 사실 덧셈, 뺄셈 둘 중의 하나만 약속할 수 있다면 input과 output을 반대로 정의하면 되니 둘 중 하나만 약속을 하면 될 일이다. 이건 언어를 떠나 프로그래밍의 기본 중의 기본일 거라 구글링을 아주 조금만 해봐도 나올 테지만 아직 구글링의 ㄱ도 하지 않고 성급하게 블로깅을 해보았다. 0과 1 밖에 모르는 컴퓨터에게 덧셈과 뺄셈을 가르치는 법은 주말에 찾아봐야지.