2015. 5. 2. 00:30

AlgoSpot Quiz 'ENDIANS' 문제 풀이 과정을 통한 소프트웨어 설계 연습.


▶ 소프트웨어 설계 연습 서론

소프트웨어 문제를 풀이를 할 때, "어떤 절차 혹은 방식"으로 프로그램을 작성해야 하는 것인지 감이 잘 잡히지 않는 분들이 더러 있습니다. 여러분의 머리가 나쁘기 때문에 그런 것이 아니라, 논리적으로 문제를 푸는 연습(혹은 훈련)이 부족하기 때문에 어렵게 느껴지는 것입니다.


달리기를 잘하고 싶다면, 달리기 연습부터 해야 합니다. 걷지도 못하는 아이가 뛰려고 하면 넘어질 뿐이죠. 자전거를 타고 싶다고 당장 안장에 올라타서 패달부터 밟으면 어찌 되겠습니까? 논리적으로 사고하는 능력도 훈련이 필요한 것입니다. 대한민국에서 고등학교 수준의 교육을 정상적으로 이수했다면 프로그래밍을 하는데 있어서 필요한 '학습 능력'이 부족한 것이 아닙니다. 다만, 여러분이 논리 문제를 풀어 본 적이 별로 없다는 점이 여러분의 프로그래밍을 힘들게 만드는 것입니다. 단순한 예제를 풀어가는 과정을 통해 논리로 문제를 푸는 연습을 해보고자 합니다.


 ENDIANS 문제 분석 및 논리 전개

알고스팟 왕초보 문제 "ENDIANS" 에 제시된 문제를 먼저 읽어보시기 바랍니다.


문제를 읽고 나서, 별거 아닌데? 생각하고 바로 코딩부터 하려는 분도 있을 수 있습니다. 잠시 그 손을 멈추시기 바랍니다. 운동할 때, 100 미터쯤 대충 달리는 거 문제가 아닙니다. 그런데, 왜 운동 선수들을 매일 똑같은 달리기를 반복할까요? 그저 내달리기만 하는 것은 아닐 겁니다. 멀리 오래도록 달리기 위해서는 달리는 자세와 호흡법, 페이스 조절 능력 등이 함께 필요한 법입니다.


보다 복잡하고, 어려운 문제를 풀기 위해서는 작은 문제를 풀 때도 좀 더 차분하게 접근할 필요가 있다는 얘기입니다. 저는 아래에 서술하는 내용을 일단, 종이에 적어가면서 천천히 음미하고 반추하는 시간을 가졌습니다. 그리고, 이제 키보드를 치며 다시 문서로 정리하고 그런 연후에 코드를 작성할 것입니다.  여러분도 좀 더 여유를 가지고 프로그래밍에 임하셨으면 좋겠습니다.


제 방식이 무조건 옳다라고 말씀 드리기는 어렵지만, 제가 프로그램을 작성하는데 있어 가급적 지키고 있는 절차는 다음과 같습니다.


  1. 문제 분석 (problem analysis)
    : 주어진 문제를 논리 흐름(logical flow)에 따라 다시 정리합니다. 가장 쉬운 방법은 입력, 처리, 출력 3단계로 문제를 재정의하는 것입니다.

  2. 문제 분할 (break down problem) 
    : 문제를 해결하기 쉽도록 보다 작은 문제로 쪼개 봅니다. (분할 및 정복 :devide & conquor 라고도 합니다.)

  3. 핵심 논리 구성 (composite core logic) 
    : 대다수의 문제는 쉬운 부분을 제외하고 나면, 한 두개의 어렵거나 까다로운 부분이 드러나게 됩니다.

  4. 플랫폼 문제 해결 (solving platform issue)
    : 플랫폼이라 함은 하드웨어, 운영체제, 프로그래밍 언어, 라이브러리 및 프레임워크 등 어플리케이션 프로그램을 작성하는데 있어서 의존해야 하는 모든 것을 말합니다. 풀어내고자 하는 문제를 해결하기 위해 어떤 API를 사용해야 하는지? 하드웨어 어떤 기능을 이용해야 하는지에 대한 문제를 해결합니다. 이미 알고 있는 지식이라면 쉽게 해결되지만 잘 모르거나, 처음 접하는 문제라면 적극적으로 검색 기술을 활용합니다.

  5. 코딩 (coding)
    : 이제, 코딩할 수 있습니다.!

"ENDIANS" 문제에 대한 분석 및 논리 구성 과정 사례는 아래와 같습니다.

  1. 문제 분석
    소프트웨어는 거의 "입력, 처리, 출력" 등 3단계의 흐름을 설계할 수 있습니다.

    - input         : n개의 32 bit unsigned int 값이 한 줄(line)에 하나씩 있는 텍스트 파일을 읽어들인다.
    - processing   : 엔디언 변환(endian conversion)을 수행한다.
    - output       : 변환한 숫자를 출력한다.

  2. 문제 분할
    문제를 해결하기 쉽도록 좀 더 작은 문제들(혹은 자세한 절차)로 쪼개 봅니다.

    1) 텍스트 파일을 연다.
    2) 한 줄의 텍스트 문자열을 읽어들인다.
    3) 읽어들인 문자열을 32 bit unsigned int 형 값으로 변환한다.
    4) 엔디언 변환(endian conversion)을 수행한다.
    5) 변환 결과를 출력한다.
    6) 더 이상 읽어들일 라인이 없을 경우, 파일을 닫고 프로그램을 종료한다.
    7) 아니면, 다음 행을 읽어들이기 위해 2) 단계로 이동한다.

  3. 핵심 논리 구성
    위와 같은 흐름에서 가장 중요한 (혹은 어려운) 문제는 32 bit 부호없는 정수 값의 엔디언을 변환하는 것입니다.

    1) 입력된 32 bit 부호없는 정수 값을 4 byte 배열 형태로 분리(혹은 변환)한다.
    2) 바이트 배열에 담긴 데이터를 역순(reverse order)으로 재배치 한다.
    3) 재배열된 바이트 배열을 다시 부호 없는 정수 값으로 변환한다.
    4) 변환된 정수 값을 출력한다.

  4. 플랫폼 문제 해결
    문제를 코드로 작성할 때 다양한 기술적 문제를 고려할 필요가 있습니다.

    프로그래밍 언어, 운영체제, 하드웨어, 라이브러리 등 어플리케이션 프로그램 자체에서 직접 구현하는 것이 아니라, 
    어플리케이션 하위에 위치한 (혹은 어플리케이션이 의존하고 있는) 시스템에서 제공 받는 기능들을 활용하고자 한다면, 원하는 기능이 있는지 코딩하기 전에 확인할 필요가 있고, 최악(?)의 경우에는 직접 구현해야 하는 경우도 발생합니다.

    ENDIANS 문제를 자바로 구현하고자 할 경우에 다음과 같은 문제들을 고려할 필요가 있습니다.

    텍스트 파일을 열고, 한줄 씩 읽어들이는 방법은 무엇인가?
    : java.io.File 클래스와 InputStream, BufferedReader 등 JDK에서 제공하는 API를 활용하면 됩니다.

    자바 언어에서 '부호없는 32 bit 정수' 타입을 지원하는가?
    자바는 부호 없는 정수형 타입을 지원하지 않습니다. 그러나, 64 bit 정수형인 long 타입을 사용하면 32 bit unsigned int 값을 저장할 수 있습니다.

    바이트 배열과 32 bit 정수 값을 상호 변환하는 방법은 무엇인가?
    우리에게는 구글과 StackOverflow가 있습니다. StackOverflow 참조 : 정수와 바이트 배열 간의 상호 변환 

 핵심 정리

간혹 어디까지가 알고리즘인가? 라는 의문이 들 때가 있습니다. 예를 들자면, 정수형 데이터와 바이트 배열 간에 상호 변환하는 과정도 알고리즘의 일부가 아니냐는 질문을 받습니다. 이런 과정을 직접 생각해야지 검색해서 남의 해법을 참고하는 것은 '편법' 혹은 틀린 자세는 아닌가 라는 의견을 접하고는 합니다. 소프트웨어 구현 과정의 모든 것을 스스로 해야 한다면, 파일을 읽고 쓰는 자바 API와 운영체제 까지도 직접 작성해야 하지 않을까 라는 반문을 해봅니다. 제가 정의하는 알고리즘은 '문제를 이해하고 논리적인 절차를 정의하는 것'이며, 구현 방안(방식)은 알고리즘의 본질이 아니라고 생각합니다.



Posted by 곽중선
2015. 5. 1. 15:11

알고리즘(수학적 논리), 영어 그리고 인문과 역사에 대한 이야기를 좀 해볼까 합니다.


"알고스팟'이라는 컴퓨터 알고리즘 수련 사이트에 올라온 문제를 풀이하는 스터디를 진행 중입니다. 우선 알고스팟의 튜터리얼 (왕초보) 문제를 풀고 있는데, 두번째 문제의 시험 문제가 영문이네요. 스터디에 참여하시는 분들이 문제 해석부터 난관에 부딧친 상황입니다.


어느 스터디 멤버 분의 고민 :


"해석은 얼추 했는데 정확히 문제가 원하는게 무엇인지 잘 모르겟네요."


문제를 해석하는데 있어서 가장 큰 문제는 영어와 인문학적 경험입니다. 아래 문장을 보시죠.


"People from Lilliput are called little-endians, since they open eggs at the small end. People from Blefuscu are called big-endians, since they open eggs at the big end."


'small end'와 'big-end'라는 표현이 대체 무슨 뜻일까요? 걸리버 여행기를 한글 책으로 읽어 본 경험이 있는 사람이라면, 금새 이해할 수 있습니다. 아래는 제가 번역한 문장입니다.


"릴리퍼트 사람들은 리틀 엔디언(little-endians)이라 불리며, 달걀의 뾰족한 끝(little-end)을 깨고 먹는다. 블레퍼스큐 사람들은 빅 엔디언(big-endians)이라 불리며, 뭉툭한 끝(big-end)을 깨고 먹는다."


그런데, 이제 또 다른 벽이 나타납니다. 알고리즘 문제 내용의 일부를 보겠습니다.


"이런 식의 논쟁은 그들의 아침식사 문제 뿐만 아니라, 컴퓨터에서도 발생하고 있다. 릴리퍼트와 블레퍼스큐의 컴퓨터는 정수(integers) 값을 저장하는 방식이 다른데 바이트(bytes) 데이터들을 서로 다른 순서로 저장하는 것이다. 릴리퍼트(리틀 엔디언)의 컴퓨터는 LSB(least significant byte)부터 MSB(most significant byte) 순서로 정렬하고, 블레퍼스큐의 컴퓨터는 정확히 반대로 동작한다.


따라서, 블레퍼스큐와 릴리퍼트의 컴퓨터 간에 정보를 주고 받을 필요가 있다면, 어떤 식이건 변환 절차가 필수적이다. 아무도 데이터를 보내면서 변환하는 것을 원하지 않기 때문에, 수신자들이 항상 변환 작업을 수행해야만 한다."


스터디 내에서 이런 식의 문제가 왜 필요한가? 어디에 쓰이는 건가? 라는 질문이 나옵니다. 컴퓨터의 발전 역사에 대한 지식이 없으면, 출제 의도를 전혀 이해할 수 없습니다. 다음 링크 "위키피디아, 엔디언이란?"을 참고하시면, 아오지탄광(알고스팟의 별칭)의 출제자들이 왜 이런 문제를 냈는가를 이해할 수 있습니다. 우리가 사용하는 모든 컴퓨터는 늘상 네트워크를 통해 데이터를 주고 받고 과정에서 엔디언 문제와 씨름하고 있습니다. 단지, 눈에 띄지 않을 뿐이지요.


정리하자면, "알고리즘"을 공부하는데 있어서 수학적 재능과 노력만 필요한 게 아닙니다. 영어, 상식 그리고 (컴퓨터) 역사 등의 배경 지식과 이론이 없으면 앞으로 나아가기 어렵습니다. 컴퓨터 소프트웨어의 목표는 현실 세계의 문제를 가상의 세계 (cyberspace)로 이식하는 것입니다. 그런데, 현실 세계의 문제들을 이해조차 하기 어렵다면, 어떻게 문제를 풀 수 있겠습니까? 자, 이래도 어쨋건 코딩만 잘하면 (빨리 빨리 코드를 만들어 내기만 하면) 되는 걸까요?


문제 출처 : "AlgoSpot : ENDIAN Quiz" 



Posted by 곽중선
2015. 5. 1. 11:50

알고스팟 (통칭 아오지탄광) 문제풀이 스터디를 진행 중입니다.

지원자 12명 중에서 11명이 튜터리얼 중에서 가장 쉬운 첫번째 문제를 풀이한 상태이며,

초보자들이 지나치기 쉬운 잘못된 코딩 습관을 통계내 봤습니다.

참고 하시기 바랍니다.


분류 유형
코드
좋은 않은 코딩 스타일 코드 예시 설명 (진지하게 읽지 마세요.) 발생
건수
발생
빈도
Bad B1 무의미한 제어문 사용
(불필요한 논리)
while ( flag ) { break; } if 조건 없이 break 실행 2 18%
B2 의미 없거나 불필요한 변수 사용 flag = true; 사용 안하는 변수 선언 1 9%
Not
Good
N1 루트 패키지 사용 패키지 선언 없음 9 82%
N2 입력 값 범위 체크 안함 if( inputNum < 10 ) { … } 음의 정수를 입력할 수 있으나,
그러지 않기를 바래요.
8 73%
N3 불분명하거나
의미없는 클래스 명칭
Main { … } 어떤 기능을 수행하는지 알 수 없음.
(내 마음 알고 싶나요? 나도 몰라요.)
6 55%
N4 불명확한 변수 명칭 int num; 데이터 타입은 알 수 있으나,
용도는 파악 안됨
4 36%
N5 들여쓰기 일관성 없음 석봉아 이제 불을 끄자꾸나…
어머니는 떡을 써시고, 나는 코딩을…
3 27%
N6 연산자와 조건문의 결함  while( i-- > 0 ) { ... } 로직을 한눈에 파악하기 어려움. 2 18%
N7 패키지 명칭에 대문자 사용 package KingBeginner; 그런데, 번역이 맞습니까? 1 9%
N8 Camel Case와 Snake Case 혼용 int input_Num; 엎어 치던가.. 매치던가.. 하나만.. 1 9%
N9 API 에 대한 불충분한 이해 exit(0); 오류가 발생했으나, 정상 종료 처리함 1 9%
N10 불필요한 주석 표기 // input number 코드만 보고 충분히 이해할 수 있음에도
주석을 쓰려면 차라리 한글로 쓰시는게?
1 9%
N11 주석에 적힌 파일명과
실제 파일명칭 불일치
gistfile1.cpp != main.cpp 암 유발자…. ? 1 9%
N12 소스 주석에 한글 영어 혼용 Copyright (c) 2015년 암 유발자…. ? (2) 1 9%
N13 잘못된 주석 위치 unsigned int inputVal;
// unsigned int로 선언
난 네가 이미 읽고 이해한 것,
그것 조차 설명하겠다!
1 9%
N14 값 비교 시, 작은 값 부터
비교하는 것을 권장함
if(n <= 10 && n > 0) 큰 것이 좋아? (응?) 1 9%
총 참가자 수  11




Posted by 곽중선