[스택오버플로우] 2줄 짜리 단순한 질문에 대한 명료한 분석과 이유를 달아준 명답 of 명답. 지난 5월에 올라온 이슈답변에 이 시간 현재 평점 38만 점, 황금배지 54개!

보러 가기


짧고 격하게 공감하고 오래 기억하라


단순한 따라하기 보다는. 왜 그래야 하는지 알려고 노력하는 것이 얼마나 중요한지 새삼 깨닫게 해 준다.

비단 C++코딩에서 뿐이랴. 특히 과학을 하는 이에게 원리와 이유의 탐구가 얼마나 중요한가 말이다. 그러니 늘, 기본에 충실하라. 이건 나 자신에게 하는 말.


* RAII 는 Resource Acquisition IS Initialization 이라는 표현의 약어로 C++을 창시한 Bjarne Stroustrup 이 주장하는 일종의 기술적 원칙이다.


[스택오버플로우] 2줄 짜리 단순한 질문에 대한 명료한 분석과 이유를 달아준 명답.지난 5월에 올라온 이슈답변에 이 시간 현재 평점 38만 점, 황금배지 54개!단순한 따라하기 보다는. 왜 그래야 하는지 알려고...

Posted by Bryan Lee on Wednesday, December 2, 2015


- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들



지난 포스팅의 마지막에 Fizzbuzz 를 풀어 내는 희한한 예제를 게시한 바 있습니다. 좀 오래 되긴 했지만, 궁금해 하는 후배가 있어 한 번 같이 분석해 보았고 독특하고 엉뚱한 생각에 재미를 조금 느끼기도 했습니다.


실제 인터뷰시의 사례인지는 알 수 없지만, 이 해법을 소개한 페이지(Fizzbuzz 에 지겨워진 개발자들)을 잠깐 보면, "일단 똑똑하다", "뽑고 싶다" 거나 "생각이 한쪽으로 쏠린 사람", "팀웍을 해칠 것 같다" 는 등의 다양한 반응를 예로 들고 있네요. 글 쓴이(Samuel Tardieu) 자신은 일을 하면서 이런 식의 재미를 추구하는 방식을 좋아한다고 적고 있기도 합니다. 여러분은 어떠신가요?


우선, 소개된 원본 소스를 그대로 두고 한 번 훑어 보기로 합니다. 참, 한 번 실행시켜 보셨나요? 다른 잘 짜여진 소스와 마찬가지로 정확하게 결과를 출력해주고 있습니다. 희한하네? ^^;;

#include <stdio.h>

static const char *t[] = {"%d\n", "Fizz\n", "Buzz\n", "FizzBuzz\n"};

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


C 프로그램이고, t 라는 문자열들을 초기화해서 가지고 있는 문자열 포인터 배열이 보입니다. t[0] 는 "%d\n", t[1] 은 "Fizz\n" ...이 되겠지요.


그 외에는 for loop 에서 printf 함수를 쓴게 답니다. 그럼 어디에서 저런 괴물같은 결과가 나온 걸까요?


가만히 보면 printf 에서 사용하는 파라미터의 t[3 & 19142723 >> 2 * i % 30] 부분에 뭔가 비밀이 숨어 있다는 얘긴데요. [] 내부의 값은 t[] 배열의 index 로 0~3 사이의 정수값을 가지게 되겠네요.


(1) 어떤 트릭일지 들여다봅시다. 3 & 19142723 >> 2 * i % 30 ... 부분을 보면 우리 눈을 현혹시키는 것이 한 가지 숨어 있습니다. 다름 아닌 연산자 우선순위! 이게 첫번째 포인트.


저 계산식을 우선 연산자 우선순위에 맞게 괄호를 써서 어떤 값을 어떻게 가지게 되는지 확인해 보아야겠습니다.


3 & (19142723 >> ( (2 * i) % 30) ) 이렇게 됩니다. &는 bit AND 연산으로 가장 우선순위가 낮고, 그다음 낮은 것이 >> 즉 RShift(오른 쪽 비트와이즈 쉬프트 연산) 이지요.


자 그러면 (2*i) 가 가장 먼저 계산됩니다. i는 1~100 사이의 카운터니까, (카운터 * 2) 를 계산한 값을 30으로 나눈 나머지를 취하는군요. 즉 2%30, 4%30, 6%30, ..., 28%30, 30%30 처럼 {2, 4, 6, ..., 28, 30, 0, 2, 4, ....} 와 같이 나열해 보면 15를 주기로 0부터 리셋되어 2씩 값이 증가하는 수들이 나열됩니다.


(2) 그럼 위의 식은 19142723 이라는 묘한 숫자값을 처음에는 2회 Rshift, 그 다음 4회 Rshift, 이런 식으로 반복해서 2칸씩 Rshift 하고 3(2진수로 11)과 bit AND 연산을 하는 겁니다. 그렇다면...


(3) 모든 비밀은 19142723 이라는 숫자에 숨어 있는 거군요. 이 숫자를 2진수로 출력해 보면(아래 예제에 2진수 출력 함수가 있으니 확인해보세요) 00 00 01 00 10 01 00 00 01 10 00 01 00 00 11 이렇게 됩니다. 이해하기 쉽게 2자리씩 끊어서 보자구요.


위의 (2)에서처럼 생각하면, 카운터 i 가 1일 때 00 00 01 00 10 01 00 00 01 10 00 01 00 00 11 을 오른쪽으로 2칸 밀면 끝 자리에 00이 나오고 2진수 11과 AND 연산을 하면 00 즉 0이 나옵니다. 카운터 i가 2일 때는 오른쪽으로 4칸 밀면 끝자리에 00, 2진수 11과 AND 연산을 하면 00, 카운터 i 가 3일때는 6칸 밀고 끝자리에 01 과 2진수 11 AND 연산하면 01 이 나오지요.


즉 주어진 숫자를 2번씩 Rshift 하면서 2비트씩 뽑아 먹는 일을 계속 반복하는 겁니다.


자, 여기서 다른 Correct한 예제 솔루션들에서 출력되는 결과를 다시 한 번 보면,


 1

 i 값 = t[0]

 2

 i 값 = t[0]

 Fizz

 t[1]

 4

 i 값 = t[0]

 buzz

 t[2]

 Fizz

 t[1] 

 7

 i 값 = t[0]

 8

 i 값 = t[0]

 ...

 

 15

 t[3]

 16

 i 값 = t[0]


이 패턴이 15개씩을 주기로 반복되고 있습니다(당연하겠지요 ^^;; 아마도 짖궂은 코더는 여기서 아이디어를 착안했겠지요).


이제, 위 표 오른쪽 칼럼의 t[]의 인덱스를 보니 {0, 0, 1, 0, 2, 1, 0, ...} 이렇게 되는데, 2자리의 2진수로 바꿔서 다시 나열해 보면 {00, 00, 01, 00, 10, 01, 00, ...} 이렇게 됩니다.


위의 (3)에서 보이는 2진수를, 오른쪽에서부터 11를 제외하고 숫자를 2개씩 묶어서 나열해 보면 정확히 맞아 떨어지지요.


이제 감이 조금씩 잡히시나요? 매직넘버로 보였던 19142723 이라는 숫자가, 사실은 미리 관찰된 패턴에 의해 나오는 t[]의 인덱스 값을 오른쪽(가장 낮은 값에는 11을 채우고) 에서부터 왼쪽으로 채워나가서 만들어지는 2진수를 10진수로 표현한 값이라는 얘기입니다. 한 마디로 줄이면, 솔루션이 아니라 '사기' 입니다.


이해를 돕기 위해 아래 C++ 프로그램을 작동시켜 보시기 바랍니다. 말로 설명하는 것보다 프로그램을 보고 실행해 보는 것이 더 직관적으로 이해가 잘 될 수 있을 것 같습니다.

#include <iostream>

using std::cout;
using std::endl;

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);
}

// 10진수를 2진수 값으로 출력
void binary_cout(int _n)
{
    if( _n <= 0)
        return;

    binary_cout(_n >> 1);

    cout << _n % 2;
}

int main()
{
    // Magic number 19142723는 2진수로 00 00 01 00 10 01 00 00 01 10 00 01 00 00 11 (2자리씩 끊어서 표기)
    int x, y;

    // fizzbuzz7 과 같은 로직에 해설을 추가하여 다시 써 봅니다
    for(int i = 1; i <= 100; i++) {
        cout << i << ": Magicnumber 를 " << (x = (2 * i) % 30) << "회 Rshift -> ";
        binary_cout(y = (19142723 >> x));
        cout << " 이 되고 이것을 0x11 과 bit and를 하면: " << (3 & y) << " 이 되며, 이것은 배열의 index." << endl;
    }

    return 0;
}



누가 작성했는지 모르지만 이 프로그램의 코더는 분명 일반인들의 허를 찌르는 센스를 가진 사람일 것 같습니다. 틀에 얽매이지 않는 자유로운 영혼, 저도 이런 분들을 좋아하고 또 본받으려고 노력합니다. 물론 프로그래머로서의 목표의식과 인성이 갖추어져 있다는 전제하에서.


발칙하다는 것은 기발하고 독창적이라는 말과 비슷합니다. 하지만 또 어떻게 보면 사회성이 떨어지고 규범을 지키지 않을 것 같은 부정적인 느낌을 주기도 합니다. 위의 솔루션은 사실 예상되는 답의 패턴에 문제해결 과정을 끼워 맞춘 것으로, 엄밀하게 말하자면 문제해결에 적합한 코딩은 아닙니다. 즉 나누는 수를 3과 5에다가 새로운 수인 7을 하나 더한다든지 하게 되면, 예상되는 결과로부터 패턴을 추출하여 프로그램 내의 매직넘버를 다시 계산하고 t[]의 index 를 계산하는 식도 다시 써야 하니까요.


또 이 솔루션은 문제를 출제한 사람을 사실은 조롱하고 있다고 생각할 수 있습니다. 하지만 그 기발한 방법으로 보아 좋은 직관력을 가졌다고 생각되며, 동적계획법이든 뭐든 적당한 훈련 과정을 거친 사람이고 문제해결을 위한 코딩의 감각이 어느 정도 갖추어져 있는 사람임은 분명해 보입니다.


이전 글: 

2015/12/07 - [Technical/Development] - [프로그래밍] Fizzbuzz 문제에 대하여(1)



- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


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 -

저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들


자주 만나는 헷갈리는 우리말 표현들 모음 II

(지난 관련 글-2012/12/13-에 이어서 추가할 만한 것들을 별도 포스팅으로 정리)




[~ㅓ, ~ㅔ, ~ㅐ, ~ㅖ]


1. ~건대, ~컨대

동사 또는 형용사 '~하다'와 '~건대' 가 합해지는 과정에서 'ㅏ' 가 빠지고 'ㅎ'이 남는 경우에 'ㄱ'과 결합하여 거센소리로 '~컨대' 가 됩니다. 따라서 "단언컨데", "단연컨데" 아니고 "단언컨대" 가 맞습니다. 당연히 "요컨데" 아니고 "요컨대" 가 맞습니다.


2. 요새

"요즈음" 이라는 뜻으로 쓰일 때는 "요세" 아니고 "요사이" 즉 "요새"가 맞습니다.


3. 재작년

"2 년 전" 이라는 의미로 쓰일 때에는 "제작년" 아니고 "재작년" 이 맞습니다. 1 년 전은 "작년".


4. 도대체

"도데채", "도데체" 아니고 "도대체" 가 맞습니다.



받침


1. "오랜만이다"

"오랫만" 아니고 오랜만


2. "앳되다"

"애띤 모습" 아니고 "앳된 모습"


3. "무난하다"

"문안하다" 아니고 "무난하다", 어렵지 않다(한자어)는 뜻.


4. '빈털털이'

소리 나는대로 '빈털터리' 라고 씁니다.


5. "건드리다"

"건들이다" 아니고 "건드리다". "건들거리다" 와 헷갈리면 안됩니다.


6. "널찍하다"

"넓직하다", "넓찍하다" 아닙니다. "너르다", "너그럽고 크다" 와 연관지어 둡니다.


7. '뒤치다꺼리'

'뒤치닥거리' 아닙니다. 비슷하게 헷갈리는 말로 '푸닥거리', '일거리', '마수걸이' 가 있습니다. '푸닥거리' 는 '푸다' 의 어원이 명확하지 않기 때문에 '푸다꺼리' 로 소리나는 대로 써야 한다는 설도 있습니다만, 현재 표준어 사전에는 '푸닥거리' 만 인정하고 있습니다.


8. '굳이'

"그걸 구지 말해야 하나"에서 '굳이' 로 씁니다. 이건 소리 나는대로 아닙니다. 


9. '구시렁'

"궁시렁거리다" 아니고 "구시렁거리다"


10. '움큼'

"한 움큼 거머쥐다". '웅큼' 아닙니다.


11. "닦달하다"

"닥달하다" 아닙니다. "닦다" 와 연관지어서 기업합니다. "남을 단단히 윽박질러서 혼을 내다" 또는 "물건을 손질하고 매만지거나 다듬다"의 뜻입니다.


12. "더욱이", "일찍이"

부사에 '~이' 가 붙어서 역시 부사가 되는 경우에 어근이나 부사의 원형을 적는 맞춤법의 원칙이 있습니다. 따라서 "더우기" 아니고 "더욱이", "일찌기" 아니고 "일찍이". 마찬가지로 "곰곰이, 생긋이, 오뚝이, 히죽이" 를 연관 짓습니다.


13. '구레나룻'

'구렛나룻', '구렌나루'  아니고 '구레나룻'



- Barracuda -


저작자 표시 비영리 변경 금지
신고
블로그 이미지

Barracuda

Bryan의 MemoLog. 쉽게 익혀 보는 IT 실습과 개념원리, 코딩 세계의 얕은 맛보기들, 평범한 삶 주변의 현상 그리고 進上, 眞想, 진상들