본문 바로가기

Technical/Development

[프로그래밍] Fizzbuzz 문제에 대하여(1)


Fizzbuzz(피즈버즈) 문제. 프로그래머라면 한 번 쯤 풀어 보거나 들어본 경험이 있을지도 모르겠다. 만약 프로그래머로서의 직업을 가지려고 하거나, 단순한 취미로라도 "나 프로그램 좀 짠다" 라는 말을 할 수 있으려면 꼭 접해 보았어야할 문제다.


만약 Fizzbuzz 문제를 처음 듣거나, 예전에 들었는데 가물가물한다...하는 분이라면 이참에, 다시 한 번 스스로를 돌아보는 계기를 마련해 보자. 이건 글을 쓰는 본인에게도 해당하는 말이 될게다. Solid programming이나 Grok coding는 수 많은 고민과 노력에 의해 충분히 만들어 질 수 있다고 나는 믿는다. 중요한 건 엔지니어로서의 동기, 자부심 또는 열정 아니겠는가?



Fizzbuzz 문제가 뭐임?


"Fizzbuzz questions"는 영국의 아이들이 학교에서 하게되는 일종의 놀이다(를 통한 동기 또는 흥미 유발을 위한). 해외의 학교에서는 놀이식으로 수업을 진행하는 경우가 많은 편인데, 우리 것과 궂이 비교하자면 일종의 369 게임과 비슷할까?


어쨌든 Fizzbuzz 문제를 프로그래머 채용 인터뷰시에 내 보았더니 대다수의 우수한 프로그래머의 경우 2분 이내로 답을 내어야 함에도 대부분의 컴퓨터과학 전공학과 졸업생들이 문제 자체를 제대로 풀지 못했고, 심지어 좀 한다는 시니어 프로그래머들도 솔루션을 내는데 10~15분이 걸렸다고 한다.


[조금 오래 된(2007~) 관련 페이지들]

프로그래머들이 왜 프로그램을 못짜?(CodingHorror)

Fizzbuzz 에 대해 너무 깊게 생각하지 말라(RaganWald)

Fizzbuzz로 코딩 잘하는 개발자 찾기(Imran)

Fizzbuzz still works(GlobalNerdy)



Fizzbuzz 문제를 활용하는 이유


"소프트웨어를 개발하다", "프로그램을 짜다" 는 말을 보통 "코딩한다" 라고 표현한다. 코딩을 잘한다는 건 무엇일까? "최소한의 시간에 주어진 문제를 정확히 해결하는 프로그램을 잘 만들 수 있는가" 라는 질문에서 어느 정도 답을 찾을 수 있겠지만 문제가 그리 간단하지만은 않다. 


결과가 빨리 나왔다고 해서 Sold 한 프로그램이라고 단정지을 수는 없으며, 코딩 이전의 설계와 코딩 이후의 추후 확장이 필요한 소프트웨어의 특성상, 전체적인 구조에 대한 설계에 충분한 시간과 공을 들여야 할 수도 있다. 또 결과에 오류가 없을 뿐 아니라, 요건 변경에 대비하여 확장성이 충분해야 하는 솔루션이 필요할 수도 있기 때문이다. 그렇다면 Fizzbuzz 문제 같은 간단한 코딩테스트를 전화 또는 대면인터뷰시에 사용하는 이유는 무엇일까?


위의 관련페이지들의 내용을 찬찬히 훑어 보면 답은 명확하다. 바로, 코딩을 잘하는 사람을 뽑기 보다는 코딩을 못하는 사람 즉, 최근 몇 개월간 코딩을 직접 경험해 보지 못한 사람, 코딩을 잘 하려는 노력이나 고민을 게을리 하는 사람을 가려내어, 채용 회사와 면접자의 불필요한 시간 손실을 일단 줄여보겠다는 단순한 의도라고 짐작할 수 있다.



Fizzbuzz 문제와 솔루션들


[문제(영어 원문)]

Write a program that prints the numbers from 1 to 100. But for multiples of three print "Fizz" instead of the number and for the multiples of five print "Buzz". For numbers which are multiples of both three and five print "FizzBuzz".

우리 말로 풀어서 쓰면, 1부터 100사이의 숫자를 프린트하는 프로그램을 작성하는데 3의 배수이면 "Fizz"를, 5의 배수이면 "Buzz"를, 둘 모두의 배수 즉 15의 배수이면 "FizzBuzz" 를 프린트하도록 하라.


문제는 한마디로 간단하고 명확하다. 인터뷰시에 해당되는 중요한 얘기인데, 만약 문제에 구체적인 제약사항이 필요하다면 적극적으로 면접관에게 질문하고 상의하라. 문제가 요구하는 답은 다음과 같은 모양일 것이다.


1

2

Fizz

4

Buzz

.

.

또는

1 2 Fizz 4 Buzz Fizz 7 ...


본인이 스스로 능력있는 코더라고 자부한다면 2분 내외로 실제로 동작하는 프로그램 코드가 완성되어야 한다. 실제로 면접시 제시되는 방법은 다양하다. 전화인터뷰시에 말로 설명해야 될 수도 있고, 화이트보드에 손으로 코딩하거나(손코딩 & 눈디버깅 & 뇌컴파일) 또는 실제로 코딩할 수 있는 노트북이나 작업 PC가 주어질 수도 있다. 그때 그때 상황에 따라 잘 대처해야 하겠다.


만약 한 번도 이런 퀴즈나 문제를 접해본 적이 없는 응시자하면 적잖이 당황하게 될 것이다. 학교나 학원의 교수나 선생님들은 이런 문제의 해결법을 따로 가르쳐 주지 않기 때문이고, 그 해법이 너무나 다양할 수 있기 때문일 것이다. 또 결과는 언뜻 단순해 보이지만 해결 방법이 그리 썩 단순하지는 않으며, 뭔가 트릭을 쓰면 '쌈빡'한 방법으로 해결될 듯도 하기 때문에 더 머리 속이 복잡해 질지도 모른다.


[참고]

오메가: 수학귀신들의 잡학사전 - FizzBuzz 테스트

c2.com - Fizz Buzz Test


위의 c2.com 과 같은 사이트의 페이지들에는 다양한 개발 언어로 된 샘플 소스들이 많이 올라와 있다. 또 구글링을 통해 검색해 보면 다양한 Fizzbuzz 솔루션들을 접해 볼 수 있다. 참고로 위의 관련페이지들에 링크로 소개된 Imran의 사이트의 페이지에는 최근까지도 새로운 댓글들이 올라오고 있는 실정이다.


printf("1\n");

printf("2\n");

printf("Fizz\n"); ...


이런 답들도 있기는 하다 ^^;; 심지어...


for(...) {

    if(if i == 1) printf("1\n");

    else if ...


이런 답도 있다 ㅠㅠ.


본 글에서는 가장 직관적이고 보기 쉬운(평범한 필자의 수준에서 직접 작성한 것이기에) 대표적인 샘플 몇 개와 장난기 섞인 것들 몇 개를 소개한다. 주로 C/C++과 javascript, Python 으로 작성된 것들을 소개해 둔다.


글 맨 마지막에는, 다음글에 연속으로 게재될 엽기적인 답안 하나를 제시해 놓으려 하니 잘 읽어두시고 한 번 실행해 보기를 권한다.


Example 1, 2, 3 은 C++ 이지만 cout 만 제외하면 C 에서도 그대로 적용 가능하며, Example 4, 5, 6은 C의 printf 함수와 삼항조건연산자등을 사용하는 트릭들이 들어 있다.



C++ Example 1 : 2분만에 풀기에 딱 좋은 단순한 구성

#include <iostream>

using std::cout;

void fizzbuzz1(int _n)
{
    for(int i = 1; i <= _n; i++) {
        if(i % 15 == 0)
            cout << "fizzbuzz";
        else if(i % 3 == 0)
            cout << "fizz";
        else if(i % 5 == 0)
            cout << "buzz";
        else
            cout << i;
        cout << endl;
    }
}

int main()
{
    fizzbuzz1(100);
    return 0;
}


(아래 부터는 main()을 제외한 처리 함수 부분만 표기한다)


C++ Example 2 : 첫 번째 솔루션은 뭔가 비교조건이 중복되게 느껴진다. 그렇다면...

void fizzbuzz2(int _n)
{
    int check_more = false;

    for(int i = 1; i <= _n; i++) {
        if(i % 3 == 0) {
            cout << "fizz";
            check_more = true;
        }
        if(i % 5 == 0) {
            cout << "buzz";
            check_more = true;
        }
        if(!check_more)
            cout << i;
        else
            check_more = false;
        cout << endl;
    }
}


C++ Example 3 : 역시 첫번째 솔루션에 대한 약간의 변형

void fizzbuzz3(int _n)
{
    for(int i = 1; i <= _n; i++) {
        if(i % 3 == 0)
            cout << "fizz";
        if(i % 5 == 0)
            cout << "buzz";
        else if(i % 3 != 0)
            cout << i;
        cout << endl;
    }
}


C++ Example 4, 5, 6 : 평범하게 풀기 싫다면 약간의 C 언어만의 트릭을 써 보자

void fizzbuzz4(int _n)
{
    for(int i = 1 ; i <= _n; i++) {
        if(i%3 && i%5) printf("%d", i);
        printf("%s%s\n", i%3 ? "" : "fizz", i%5 ? "" : "buzz");
    }
}
void fizzbuzz5(int _n)
{
    // printf 함수는 출력한 문자의 갯수를 return 한다
    // 연산자 우선순위에 주의한다
    int i = 0;

    while(i++<_n)
        (i%3 || !printf("Fizz")) * (i%5 || !printf("Buzz")) && printf("%d",i), printf("\n");
}
void fizzbuzz6(int _n)
{
    char i = 0, n[3];
    while(i++ < 100)
        printf("%s%s%s\n", i%3 ? "":"Fizz", i%5 ? "":"Buzz", (i%3&&i%5&&sprintf(n, "%d", i)) ? n : "");
}


Javascript Example 1 : 참 단순 명료한 javascript !

for (var i=1; i<=100; i++) {   
    console.log( ((i%3 ? '':'Fizz') + (i%5 ? '':'Buzz') || i) )
}


Python Example 1 : 위의 일반적인 경우보다는 좀 더 Pythonic 하게 작성해 보자

FizzBuzz 와 같은 2가지 경우가 아니라 n개의 확장된 case에 대해서도 쉽게 처리가 가능한 방식을 구현할 수 있다.

# -*- coding:utf-8 -*-

# 각각의 나누는 수는 고유의 이름을 가진다. 이를 divisor_pairs(divisor, name) 이라는 튜플로 설정한다.
# 1~n 까지의 각각의 수(i)에 따라,
#   ... 나누는 수(divisor) 의 name이 있으면 프린트하고
#   ... 없으면 숫자 자체를 프린트한다

divisor_pairs = [
    (3, "3으로나누어짐"),
    (5, "5로나누어짐"),
    (7, "7로나누어짐")
]

for i in range(1, 501):
    # i에 대해 나누어 떨어질 경우, name 으로 스트링을 만든다
    name_string = "".join(name for (divisor, name) in divisor_pairs if i % divisor == 0)
    # name_string 을 프린트한다. 만약 빈문자열이면 i 를 프린트한다
    print(name_string or i)



다음 2회에서 분석해 볼 엽기적 솔루션

[솔루션이 소개된 원문 페이지 보기FizzBuzz and bored programmers ...아우~ 이걸 채용해야 하나?

void fizzbuzz7(int _n)
{
    static const char *t[] = {"%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n"};

    for(int i = 1 ; i <= _n; i++) {
        printf(t[3 & 19142723 >> 2 * i % 30], i);
    }
}


다음 글: 2015/12/07 - [Technical/Development] - [프로그래밍] Fizzbuzz 문제에 대하여(2)



- Barracuda -