2007년 1월 24일

나의 스크립트 언어 이야기

친구와 스크립트 언어에 대한 얘기들을 나눴는데, 느낌이 와 닿아서, 충동적으로 글을 쓰게 되었습니다. 스크립트 언어라는 것에 대해서 소개합니다. 그리고 제 자신의 스크립트 언어에 대한 비하인드 스토리를 공개합니다.

예전에는 언어는 두 가지로 나누어져 있었습니다. 컴파일 언어와 인터프리터 언어로 말이죠. 컴파일 언어는 대부분 시스템을 건드릴 수 있는 저수준까지 내려갈 수 있고, 인터프리터 언어는 그렇지 않고 속도도 훨씬 느리죠.

그러나 이런 구분이 이제는 조금 모호해졌습니다. 스크립트 언어로도 얼마든지 운영체제에서 제공하는 수준에 접근할 수가 있게 되었죠. bash와 펄이 대표적으로 그런 언어였습니다. 점점 다른 언어들과의 교환 메커니즘이 발달하게 되고 이제는 스크립트 언어들이 풀언어(서로 다른 언어로 개발된 프로그램들을 이어 붙여주는 프로그램)로써 자주 활용되기도 합니다.

제가 본격적으로 스크립트 언어를 다루게 된 발단은 학부 2학년 때로 거슬러 올라갑니다. 그 당시 저는 수동으로 html을 대충 쳐서 만든 홈페이지를 만들었고, 여기에 제가 해야 될 일들의 목록을 적어 놓았습니다. 물론 홈페이지 내에서 쉽게 이것을 추가하거나 삭제할 수 있는 것이 아닌 바보같은 홈페이지였죠. 제가 그 당시 한 일은 이 html에서 grep 명령을 이용하여 할일 목록의 내용만을 추출한 것이었습니다. 그래서 학교 리눅스 시스템을 터미널로 접속하면 시작하자마자 날짜별로 해야 될 일의 목록이 터미널에 출력되고 저에게 압박을 가하는 일이었습니다.

그 이후에 나라의 부름을 받고 업무를 보게 되었는데, 저는 마음대로 컴파일러를 설치할 수 없었습니다. 물론 업무 내용상 관리자 권한이 주어지는 행운아였지만, 감시가 심한 상사는 컴퓨터들을 모니터링합니다. cmd.exe만 실행하여 검은 화면만 떠도 해킹으로 의심을 하는 상황이니까요.(결국 커맨드 창 배경화면을 흰색으로 바꾸니까 메모장처럼 보여서 의심을 안 받더군요.) 결국 일과 시간이 끝나고 나면, 메모장에 적힌 소스를 보면서 두뇌로 컴파일 하는 수 밖에 없었는데 할 수 있는 좋은 일을 찾은 것입니다.

그 당시 하는 업무는 매우 비효율적이었습니다. 손님이 오시면 MS 워드 문서에 있는 계약서 양식에 손님의 수많은 개인정보를 적습니다. 개인 정보 종류가 꽤나 많습니다. 그리고 나서, 다른 소프트웨어를 이용하여 서비스 요청 문서를 만듭니다. 똑같은 정보를 한번 더 입력합니다. 그리고 나서 인쇄해서 문서를 파일에 넣고, 이것을 전산환경으로 찾아볼 수 있게 하기 위하여 MS Access 데이터베이스에 똑같은 내용의 정보를 다시 다 입력해 넣습니다. 그 뒤에, 종이로 만들어진 일지 표에 똑같은 내용의 정보를 손으로 다 써 넣습니다. 개인정보 항목이 적으면 문제가 없겠지만 상당히 많습니다. 경우에 따라서는 한 두 곳에는 정보가 들어가지 않아서 데이터의 일관성을 해치기도 했습니다.

네, 그렇습니다. MS 오피스 제품군은 VB 스크립트라는 언어로 제어할 수 있습니다. MS Access는 덤으로 SQL을 이용할 수 있습니다. 저는 작업에 들어갔습니다. MS 오피스가 떠 있을 뿐이었으므로, 이것은 하얀 배경 화면이기 때문에 아무 문제가 없었습니다. MS Access에 자료를 한번만 입력하고 Report 기능 등을 이용하면 필요한 다양한 양식의 문서를 동일한 정보로 인쇄할 수 있었습니다. 게다가 날짜 계산 같은 것도 자동으로 줍고, 일을 넘겨받는 부서에서 필요한 정보를 요약해서 볼 수 있는 문서도 인쇄가 됩니다. 덕분에 많은 시간과 인력을 절감할 수 있었습니다. 한 손님이 서비스를 받고 나가는데 걸리는 시간은 기존의 1/4 수준으로 떨어졌고, 만족도도 올라갔습니다. 저는 이것 이외에도 업무가 있었기 때문에 다른 업무에 좀 더 집중할 수 있는 시간도 생겼습니다. 직속 상관은 이것이 무엇인지 전혀 이해하지 못했고 슈퍼바이저도 마찬가지였지만, 어느 순간 슈퍼바이저 얘기가 고객에게서 서비스에 대한 피드백이 훨씬 좋아지고 왠지는 모르지만 만족도가 높아졌다는 이유로 여러번 칭찬을 받았습니다. 조금 이상한 이유로 포상도 받았습니다.

아쉽게도 이제 제 머리속의 VBScript는 잊혀졌습니다. 이제 제가 주로 스크립트 언어를 제 시스템에서 사용하는 용도는 시스템 관리입니다. bash와 파이썬, 루비, 펄 등으로 하나하나 만들어두면 나중에는 꽤 쓸만해집니다. 작은 코드조각이지만 유용하게 됩니다. 이럴 때에는 C나 C++ 등으로 작성된 프로그램들을 바깥에서 감싸는 랩언어(이런 용어가 있는지는 모르겠습니다)가 되겠군요.

음악 듣는 프로그램인 아마록에서도 파이썬 스크립트를 많이 사용합니다. 이것으로 듣고 있는 음악의 가사를 긁어오거나, 앨범 커버 그림을 다운받아 올 수 있습니다. 가수에 대한 정보를 보여주기도 합니다. 예를 들어서 아마록에서 앨범 커버를 가져오는 곳은 아마존입니다. 그런데 국내 음악이라던지 이런 것들은 앨범 커버가 나오지 않죠. 웹에서 다운 받은 것들이라서 그런게 아니라, CD에서 추출한 경우에도 가사가 들어있지도 않습니다. 제가 국내 가요들을 재생할 때 옆에 가사가 표시되게 하려면 어떻게 해야 할까요? 아마록 소스 코드를 수정해서 긴긴 시간 다시 컴파일을 해야 할까요?

절대 그럴 필요가 없답니다. 파이썬 등으로 스크립트만 하나 작성해서 아마록에 끼워 주면 됩니다. 노래 제목이나 가수 이름으로 검색해서 얻은 텍스트 데이터에서 정규식 등을 이용해서 필요한 그림의 URL이나 가사의 위치를 알 수 있게 됩니다.

예전에 이맥스 시스템에서 elisp의 역할에 놀랐었는데, 이제 굳이 elisp이 아니더라도 이런 일을 충분히 할 수 있게 되었습니다. emacs와 같이 커스터마이징 가능한 소프트웨어를 만들기가 무척 쉬워졌다는 것이지요. 그것도 lisp보다 좀 더 쉽게 와 닿을 수 있는 형태의 언어로 말입니다.

물론 스크립트 언어로 모든 것을 할 수는 없습니다. 그러나 스크립트 언어로 할 수 있는 일이 점점 더 많아지고 있습니다. 예전에는 스크립트 언어로 한다고 하면 절대 안 될 것이라고 말했던 것들이 이제는 되고 있거든요.

삶의 묘미를 찾고 계신 분들은 스크립트 언어 하나쯤 배워 놓는 것은 어떠신가요? 배워서 지금 당장 웹 영어 사전에서 내용을 긁어오는 스크립트를 작성하셔서 편리하게 사용하셔도 좋습니다. 정말 컴퓨터에 내가 귀찮은 일들을 명령을 내리면 충실히 수행해주는 느낌. 어릴 때 누구나 꿈꾸어 왔던 로봇과 같은 모습이 아닐까요.

2007년 1월 17일

스케일링

치과에서 스케일링이라는 것을 받았습니다. 10살때쯤에 이 뽑으러 치과에 간 이후로 치료 받으러 간 것은 처음입니다. 원래 병원 가는 것을 무척이나 귀찮아합니다. 그래서 오랫동안 치료를 하지 않았는데, 좀 일찍 왔으면 좋았을 걸 그랬습니다. 검사할 때마다 항상 아무 이상 없다고 했으니 치료 받지 않았던 것이지요.

스케일링 하는 기분은 꽤 좋았습니다. 스케일링 하시는 분이 잘 하셔서 그런 것인지 몰라도 아프지도 않고 받고 나니 상쾌한 느낌입니다. 자꾸 받으면 중독될지도 모르겠습니다. 스케일링한 결과 아랫쪽 이 사이가 좀 벌어지게 되었습니다. 벌어졌다기 보다는 원래 치석이 있던 자리가 비어서 그렇게 되었지요.

치위생사 선생님께 치솔 사용법과 치실 사용법을 배웠습니다. 정말 상세하게 잘 가르쳐 주시더라구요. 한번 해 보려고 치실을 구입해서 해 보는데, 이게 너무너무 어렵습니다. 그러지 않아도 삼차원 공간 감각이 떨어지는 것 같은데 전혀 감이 안 잡히더라구요. 어디가 안쪽이고 어디가 바깥쪽인지 손가락 둘의 삼차원 좌표를 움직이는 것조차 너무나 어려운 작업이었습니다. 1주일 정도 하면 능숙해진다고 하셨으니 열심히 해 봐야겠습니다. 치실 쓰는 것이 이맥스 에디터 처음 쓸 때보다는 훨씬 어려운 느낌입니다.

스케일링 후에 얼음물을 마셔도 이가 시리다거나 하지는 않는군요. 사람마다 다른가 봅니다.

2007년 1월 7일

Gnash 플래시 플레이어

Gnash는 GNU 플래시 무비 플레이어입니다.

웹서핑을 하다가 우연히 Gnash를 발견하고 호기심에 설치해 보았습니다. 큰 문제없이 잘 동작하네요. 저는 젠투 리눅스를 64비트 아키텍쳐로 쓰고 있습니다. 주로 쓰는 브라우저는 오페라이고 오페라는 2007년 1월 현재 32비트만 지원하는 브라우저이기 때문에 플래시를 쓰는데에는 아무 문제가 없었습니다. 그러나 가끔씩 64비트를 목표로 컴파일 된 파이어폭스를 쓸 경우에는 플래시를 설치하지 않았기 때문에 플래시를 볼 수 없었습니다. 그 이유는 64비트 어도비 플래시 플레이어가 출시되는 것이 자꾸 지연되고 있기 때문인데 Gnash를 설치하니 잘 동작했습니다.

젠투 리눅스 기준으로

emerge gnash
를 해 주면 gnash가 설치됩니다. 이후에는 firefox, seamonkey, epiphany 등의 브라우저에서 큰 문제없이 플래시를 쓸 수 있네요.

2007년 1월 2일

팩토리얼을 구하는 스킴 스크립트

스킴(Scheme)이라는 프로그래밍 언어를 심심풀이로 배우고 있습니다. 상당히 깔끔한 언어라는 생각이 듭니다. 특히 알골 계열의 언어에만 길들었다면 처음 코딩할 때의 기분이 상당히 새롭습니다.

새로운 언어를 배우면 무언가 만들어 봐야 하기 때문에 차례곱을 구하는 프로그램을 작성하여 보았습니다. 실제로 차례곱을 구하는 부분은 5줄이지만 다른 부분이 대부분을 차지하고 있네요. MzScheme의 구현을 사용하여 스크립트 파일을 만들어 보았습니다. 맨처음으로 작성한 프로그램이라서 깔끔하지 못합니다. 여러 개의 숫자를 인자로 받거나 표준 입력으로부터 받아서 그 차례곱을 표준 출력으로 출력합니다. 이 프로그램을 /usr/local/bin에 넣어 두었습니다. 이제 차례곱을 쉽고 편리하게 구할 수 있겠습니다.

연습삼아 작성한 것이어서 재귀, letrec, do를 모두 한 번씩 사용해 보았습니다. 이번에는 가장 핵심적인 차례곱을 구하는 부분은 다음과 같이 재귀적으로 되어 있습니다.

(define fact
  (lambda (x)
    (if (= x 0)
        1
        (* x (fact (- x 1))))))

스킴을 처음 보시는 분도 위의 코드는 이해하실 수 있으리라고 생각합니다. 다음은 전체 프로그램의 소스 코드입니다.

#!/usr/bin/mzscheme -r
;;; Prints out factorial of given numbers.
;;; Date: 2nd of January, 2007

;; Returns factorial of given parameter.
(define fact
  (lambda (x)
    (if (= x 0)
        1
        (* x (fact (- x 1))))))

;; Prints out help message
(define help
  (lambda ()
    (display "Usage: fact [--help | [NUMBER]...]")
    (newline)
    (display "Calculate factorial of given numbers.")
    (newline)
    (display "If no numbers are given, it reads numbers from standard ")
    (display "input until EOF and prints each factorials of them.")
    (newline)
    (newline)
    (display "--help   display this message")
    (newline)
    (exit)))

;; Main procedure.
(define main
  (lambda ()
    (let ((number-of-args 
          (vector-length (current-command-line-arguments))))
      (if (and (= number-of-args 1)
               (equal? "--help" 
                 (vector-ref (current-command-line-arguments) 0)))
        (help))
      (if (= number-of-args 0)
        (begin
          (do ((number (read) (read)))
            ((eof-object? number))
            (display (fact number))
            (newline)))
        (letrec ((iter
                  (lambda (counter)
                    (if (< counter number-of-args)

            (begin
              (display (fact
                        (string->number
                          (vector-ref
                            (current-command-line-arguments)
                            counter))))
              (display #\space)
              (iter (+ counter 1)))))))
      (iter 0))))
    (newline)))

(main)

2006년 12월 21일

고른 표본 추출을 통한 빠른 정렬

고른 표본 추출을 통한 병렬 정렬(Parallel Sorting by Regular Sampling: PSRS)은 n개의 프로세스에서 골고루 표본을 추출하여 이것을 주축으로 하여 값들을 정렬하는 병렬 정렬 알고리즘의 일종입니다. 균형이 잘 잡히고, 프로세스의 수가 2의 n제곱 꼴이 되지 않아도 된다는 등의 장점이 있습니다.

MPI를 배우면서 이것을 한번 C+MPI로 구현해 보았습니다. p개의 노드에서 n/p개만큼의 [0..1) 범위의 float형 난수를 발생한 다음에 정렬을 하는 과정입니다. 잘 작성된 코드는 아니지만, 참고할 수 있는 코드일 수도 있어서 실어 봅니다.

  1 /**
  2  * PSRS implementation using MPI.
  3  *
  4  * Date: 5th of December, 2006
  5  * Author: Jay
  6  *
  7  * Compile options:
  8  *     PRINT_MSG: print some messages to stdout.
  9  *     OUTPUT_FILE: write 'glist_nn.txt', and 'slist_nn.txt'.
 10  *                  'glist_nn' contains sorted generated numbers
 11  *                  of each node. 'slist_nn' is final result
 12  *                  of each node.
 13  */
 14 #include <stdlib.h>
 15 #include <stdio.h>
 16 #include <mpi.h>
 17 #include <limits.h>
 18 #include <time.h>
 19 #include <stddef.h>
 20 #include <string.h>
 21 
 22 /* Upper bound of generated floating point */
 23 #define UPPER_BOUND 1.0
 24 
 25 /* float comparision function */
 26 int float_comp(const void* aa, const void* bb)
 27 {
 28     const float* a = aa;
 29     const float* b = bb;
 30     if (*a == *b) return 0;
 31     if (*a < *b) return -1;
 32     return 1;
 33 }
 34 
 35 /* Generates random sequence into array A with size */
 36 void generate_random_sequence(float* A, size_t size)
 37 {
 38     size_t i;
 39     for (i=0; i<size; i++)
 40         A[i] = (float)rand()/RAND_MAX;
 41 }
 42 
 43 /* Returns pointer to the lower_bound,
 44  * which means the lowest position
 45  * that the element can be inserted
 46  * with sorted order. */
 47 float* lower_bound(float* first, float* last, float val)
 48 {
 49     ptrdiff_t len = last - first;
 50     while (len > 0)
 51     {
 52         ptrdiff_t half = len / 2;
 53         float* middle = first + half;
 54         if (*middle < val)
 55         {
 56             first = middle + 1;
 57             len = len - half - 1;
 58         }
 59         else
 60             len = half;
 61     }
 62     return first;
 63 }
 64 
 65 /* Writes each elements in float array to the file */
 66 void output(const char* filename, float* A, size_t size)
 67 {
 68     FILE* f = fopen(filename, "w");
 69     int i;
 70     for (i=0; i<size; i++)
 71     {
 72         fprintf(f, "%1.5f\n", A[i]);
 73     }
 74     fclose(f);
 75 }
 76 
 77 /*
 78  * First command-line argument: n
 79  */
 80 int main(int argc, char* argv[])
 81 {
 82     int n_number = 1000000;
 83     if (argc > 1) n_number = atoi(argv[1]);
 84     int id, p;
 85 
 86     MPI_Init(&argc, &argv);
 87 
 88     MPI_Barrier(MPI_COMM_WORLD);
 89     double elapsed_time = -MPI_Wtime();
 90 
 91     MPI_Comm_rank(MPI_COMM_WORLD, &id);
 92     MPI_Comm_size(MPI_COMM_WORLD, &p);
 93 
 94     /* Some nodes should control 1 more
 95      * element if n % p != 0. */
 96     float A[(n_number+(p-1))/p];
 97     float* single_list = A;
 98     int n_control = n_number/p;
 99     int n_sorted = n_control;
100     if (n_number%p > id) n_control++;
101     int i;
102 
103 #ifdef PRINT_MSG    
104     fprintf(stdout, "Process %d generates %d of %d elements.\n",
105             id, n_control, n_number);
106     fflush(stdout);
107 #endif
108 
109     /* For unique random number, add (id*1000) */
110     srand( time(NULL) + id * 1000);
111     generate_random_sequence(A, n_control);
112 
113     /* Phase 1 */
114     /* Each process quicksort their own list and each one picks samples */
115     qsort(A, n_control, sizeof(float), float_comp);
116 
117     if (p > 1)
118     {
119 
120         float samples[p];
121         for (i=0; i<p; i++)
122             samples[i] = A[i*n_control/p];
123 
124         /* Phase 2 */
125         /* Node 0 gathers samples, sorts them, and picks pivots. */
126         float all_samples[p*p];
127         MPI_Gather(samples, p, MPI_FLOAT, all_samples, p,
128                 MPI_FLOAT, 0, MPI_COMM_WORLD);
129         float pivots[p-1];
130         if (!id)
131         {
132             qsort(all_samples, p*p, sizeof(float), float_comp);
133 
134             for (i=0; i<p-1; i++)
135                 pivots[i] = all_samples[(i+1)*p+p/2-1];
136         }
137         /* Node 0 broadcasts pivots and each process 
138          * partitions its own list. */
139         MPI_Bcast(pivots, p-1, MPI_FLOAT, 0, MPI_COMM_WORLD);
140         int send_cnts[p], send_disp[p];
141         send_disp[0] = 0;
142         for (i=1; i<p; i++)
143         {
144             send_disp[i] =
145                 (float*)(lower_bound(A, A+n_control, pivots[i-1]))-A;
146             send_cnts[i-1] = send_disp[i] - send_disp[i-1];
147         }
148         send_cnts[p-1] = n_control - send_disp[p-1];
149 
150         /* Phase 3 */
151         /* First, exchanges the number of elements that 
152          * each one is going to exchange. */
153         int recv_cnts[p], recv_disp[p+1];
154         MPI_Alltoall(send_cnts, 1, MPI_FLOAT, recv_cnts, 1,
155                 MPI_FLOAT, MPI_COMM_WORLD);
156         recv_disp[0] = 0;
157         for (i=1; i<p; i++)
158             recv_disp[i] = recv_disp[i-1] + recv_cnts[i-1];
159         recv_disp[p] = recv_disp[p-1]+recv_cnts[p-1];
160         float partitions[recv_disp[p]];
161         /* Exchanges elements to appropriate nodes. */
162         MPI_Alltoallv(A, send_cnts, send_disp, MPI_FLOAT, partitions,
163                 recv_cnts, recv_disp, MPI_FLOAT, MPI_COMM_WORLD);
164 
165         /* Phase 4 */
166         /* Each node merges its own partitions into a single list. */
167         int j;
168         int merge_disp[p];
169         n_sorted = recv_disp[p];
170         single_list = malloc(n_sorted*sizeof(float));
171         memcpy(merge_disp, recv_disp, p*sizeof(int));
172         for (i=0; i<n_sorted; i++)
173         {
174             float min = UPPER_BOUND;
175             int min_pos = 0;
176             for (j=0; j<p; j++)
177                 if (merge_disp[j] < recv_disp[j+1]
178                         && min > partitions[merge_disp[j]])
179                 {
180                     min = partitions[merge_disp[j]];
181                     min_pos = j;
182                 }
183             single_list[i] = min;
184             merge_disp[min_pos]++;
185         }
186 
187     }
188     /* Synchronizes for checking maximum elapsed time among nodes. */
189     MPI_Barrier(MPI_COMM_WORLD);
190     elapsed_time += MPI_Wtime();
191 
192 #ifdef PRINT_MSG    
193     fprintf(stdout, "Process %d now has sorted the list that contains \
194 %d of %d elements.\n", id, n_sorted, n_number);
195     fflush(stdout);
196 #endif
197 
198     if (!id)
199         printf("Elapsed Time with %d processes: %10.6f\n",
200                 p, elapsed_time);
201 
202     /* Output (elapsed_time doesn't count for file output!) */
203 #ifdef OUTPUT_FILE      
204     char filename[100];
205     strcpy(filename, "glistxx.txt");
206     filename[5] = '0' + id / 10;
207     filename[6] = '0' + id % 10;
208     output(filename, A, n_control);
209     filename[0] = 's';
210     output(filename, single_list, n_sorted);
211 #endif
212     if (single_list != A) free(single_list);
213     MPI_Finalize();
214 
215     return 0;
216 }
217 

홍정상인 호설암의 인간경영

홍정상인 호설암의 인간경영이라는 책을 읽어볼 기회가 생겼습니다. 그래서 이전부터 조금씩 관심을 가지고 있었던 호설암이라는 인물에 대해서 좀 더 알 수 있는 기회가 되었습니다. 이 책을 통하여 사업을 할 때 어떻게 협력자를 만들고 또 협력을 통하여 부를 창출하는지에 대해 알 수 있습니다.

호설암은 청대의 뛰어난 상인으로써, 맨손으로 일어나 최고의 부자 자리까지 오른 사람입니다. 그는 상대방이 진정으로 어려울 때 돕고, 눈앞의 작은 이익에 연연하지 않고 더 큰 이익을 위하며, 상대방의 체면을 세워주고, 상대방의 입장을 생각하고 다가섭니다. 그가 어떻게 많은 협력자를 얻었는지 알 수 있습니다. 또한 그는 다른 사람을 돕는데 그의 재물을 쓰는데 인색하지 않았습니다. 그렇기 때문에 진정으로 다른 사람을 감동시켜서 기꺼이 그를 돕게 할 수 있었던 것입니다.

만일 고고한 선비께서 이 책을 읽으신다면 1장~3장에서의 호설암의 행위에 대하여 마음이 편치 않으실 것입니다. 그가 단지 정경유착과 뇌물, 아첨 등으로 돈을 버는 소인배라고 생각할 것이기 때문입니다. 여기까지만 읽고 선입견을 가지면 곤란합니다. 계속 읽어나가다보면 이것이 그의 전부가 아니라는 것을 아실 수 있으실 것입니다.

관리자로서 호설암의 인간 경영의 면모를 보자면, 그는 인재의 장점만을 보고 그를 기용하려 했습니다. 이는 삼국지의 제갈량이 너무 완벽한 사람만을 보고자 하여 인재를 가렸기 때문에 결국 촉한의 인재가 부족해 진 것과 대조적입니다. 물론 이런 그의 성격 때문에 소인배를 제대로 가리지 못하는 문제점이 있었지만, 요즘 들어서 저는 호설암의 인간 경영 방법이 상당 부분 옳다고 생각합니다. 세상에 단점 없는 사람은 없습니다. 도덕적으로 큰 문제가 있는 일부를 제외하고는 각자의 장점을 잘 활용하는 것은 정말 중요한 능력입니다. 완벽한 사람을 가려서 부리는 것과 평범한 사람을 최대한 활용하는 능력 두 가지 모두 필요한데 완벽한 사람만을 찾는 것은 쉽지 않을 것 같다는 생각을 해 봅니다.

그가 살았던 시기는 청나라 때이므로 현대 사회에는 통하지 않는 점들이 있고 그에게도 단점이 있습니다. 소인배들에게 너무 관대했던 탓에 결국 사업은 망해버렸습니다. 그의 부인들도 그의 사업이 망하자 각자의 몫을 챙겨서 하나 둘 떠나고 맙니다. 이익으로 맺어진 많은 협력자 가운데 완전히 몰락한 후에도 끝까지 도와줄 수 있는 사람은 그다지 많지 않을 것입니다. 이것으로 보아 그의 인간 경영은 실패가 아닐까 생각할 수도 있겠습니다. 하지만, 보통 사람들보다 훨씬 많은 수의 평생 협력자들이 호설암에게는 있었다고 생각합니다. 과연 크게 파산한 사람을 끝까지 도와 줄 사람이 몇이나 있을지 생각해 봅니다. 다만 호설암은 그들에게 빚을 지고 싶지 않았기 때문에 도움을 받지 않았을 겁니다. 제가 생각하는 호설암의 단점은 소인배들에게 관대함과 배우자들을 선택하는데 너무 관대했던 것입니다.

원수를 자신의 편으로 만드는 데는 호설암을 따를 자가 없겠습니다. 그는 원수지는 일이 없게 하려고 많은 노력을 했습니다. 하지만 원수는 종종 생깁니다. 이런 사람들까지 자신의 편으로 만드는 호설암의 탁월한 모습에서 많은 것을 배울 수 있습니다.

읽어볼 만한 좋은 책이라고 생각합니다.

참고 도서:
홍정상인 호설암의 인간경영/ 호설암 원전/ 구양일비 해석/ 이선영 옮김/ 태웅출판사

2006년 12월 20일

기사의 여행

기사의 여행은 체스판 위의 임의의 위치에서 기사가 출발하여 각 위치를 오직 한 번만 방문하면서 모든 위치를 방문하는 순서를 구하는 문제입니다. 오일러 등의 많은 수학자들이 이 문제를 다루었으며, 다양한 해법과 변형된 문제들이 있습니다. 가장 일반적인 해법은 되추적을 이용한 방법입니다.

19세기의 H. C. Warnsdorff는 기사의 여행 문제를 푸는 실용적인 방법을 제시하였습니다. 기사가 움직이면서 어느 곳으로도 움직일 수 없는 막다른 곳에 다다르지 않게 하는 것이 목적입니다. 막다른 곳에 다다르지 않게 하기 위하여 Warnsdorff가 제시한 규칙은 현재 기사가 한 번에 갈 수 있는 곳 중에서 다음 번 수에 갈 수 있는 곳이 가장 적은 곳으로 간다는 규칙입니다. 이 방법은 휴리스틱한 방법입니다만, 8 x 8의 체스판 공간에서 기사의 여행 문제를 잘 풀어 줍니다. 체스판의 공간이 넓어지면 제대로 풀리지 않는 경우가 생길 수 있습니다.

Warnsdorff의 규칙을 이용하여 기사의 여행 문제를 푸는 간단한 프로그램을 작성해 보았습니다. C언어로 작성되어 있고 시작행과 시작열의 위치를 기본 입력에서 읽어서 기본 출력으로 해를 출력해 줍니다.

  1 /*
  2  * Knight's Tour.
  3  *
  4  * Author: Jay
  5  * Date: 20th of December, 2006
  6  */
  7 #include <stdio.h>
  8 
  9 /* definitions */
 10 #define ROW_SIZE 8
 11 #define COL_SIZE 8
 12 #define NUM_WAYS 8
 13 typedef int board_t[ROW_SIZE][COL_SIZE];
 14 int dr[NUM_WAYS] = {-2, -1, 1, 2, 2, 1, -1, -2};
 15 int dc[NUM_WAYS] = {1, 2, 2, 1, -1, -2, -2, -1};
 16 
 17 /**
 18  * Set every element to -1
 19  */
 20 void initialize_board(board_t board)
 21 {
 22     int i, j;
 23     for (i=0; i<ROW_SIZE; i++)
 24         for (j=0; j<COL_SIZE; j++)
 25             board[i][j] = -1;
 26 }
 27 
 28 /**
 29  * Print the board out.
 30  */
 31 void print_board(board_t board)
 32 {
 33     int i, j;
 34     for (i=0; i<ROW_SIZE; i++)
 35     {
 36         for (j=0; j<COL_SIZE; j++)
 37             printf("%d\t", board[i][j]);
 38         printf("\n");
 39     }
 40 }
 41 
 42 /**
 43  * Check if (r,c) is inside board.
 44  * @return true if (r,c) is inside board, false otherwise.
 45  */
 46 int is_inside_board(int r, int c)
 47 {
 48     return r >= 0 && r < ROW_SIZE && c >= 0 && c < COL_SIZE;
 49 }
 50 
 51 /**
 52  * Check if (r,c) is available in board.
 53  * @return true if (r,c) is available, that is has value -1,
 54  *         false otherwise.
 55  */
 56 int is_available(board_t board, int r, int c)
 57 {
 58     return is_inside_board(r, c) && board[r][c] == -1;
 59 }
 60 
 61 /**
 62  * @return number of next moves of (r,c) in board.
 63  */
 64 int num_next_moves(board_t board, int r, int c)
 65 {
 66     int i, result=0;
 67     for (i=0; i<NUM_WAYS; i++)
 68         if (is_available(board, r+dr[i], c+dc[i]))
 69             result++;
 70     return result;
 71 }
 72 
 73 /**
 74  * Get next way id from (r,c) in board.
 75  * Next way is the way whose destination has minimal number of next moves.
 76  * @return next way id, which is in [0, NUM_WAYS).
 77  */
 78 int next_way_of(board_t board, int r, int c)
 79 {
 80     int i, min = NUM_WAYS, result=0;
 81     for (i=0; i<NUM_WAYS; i++)
 82         if (is_available(board, r+dr[i], c+dc[i])
 83                 && num_next_moves(board, r+dr[i], c+dc[i]) < min)
 84         {
 85             min = num_next_moves(board, r+dr[i], c+dc[i]);
 86             result = i;
 87         }
 88     return result;
 89 }
 90 
 91 /**
 92  * Get r, c from user and solve knight tour problem.
 93  * Print result out.
 94  * @return 0 for successful moves, 1 otherwise.
 95  */
 96 int main()
 97 {
 98     int r, c, move, next_way;
 99     board_t board;
100 
101     initialize_board(board);
102     while (1)
103     {
104         printf("Input start position r c: ");
105         scanf("%d %d", &r, &c);
106         fflush(stdin);
107         if (is_inside_board(r, c)) break;
108         printf("Please put them again.\n");
109     }
110     board[r][c] = 0;
111 
112     for (move=1; move<ROW_SIZE*COL_SIZE; move++)
113     {
114         if (num_next_moves(board, r, c) == 0)
115         {
116             printf("Failed.\n");
117             print_board(board);
118             return 1;
119         }
120         next_way = next_way_of(board, r, c);
121         r = r + dr[next_way];
122         c = c + dc[next_way];
123         board[r][c] = move;
124     }
125     print_board(board);
126 
127     return 0;
128 }

참고할 수 있는 URL:
Warnsdorff's rule - 영문 페이지

블로그를 개설했습니다.

저는 대학교에서 컴퓨터공학을 공부하고 있는 학부생입니다. 이제 글을 좀 써 볼까하고 블로그를 개설하였습니다. 시작은 작지만 보잘 것 없는 글이라고 하나 하나 계속해서 모이게 되면 어느 사이에 저만치 나가 있지 않을까 생각해 봅니다.

현재 2006년 2학기 종강을 하고, 조금 여유로워졌기 때문에 글쓰기를 시작할 마음이 생겼습니다. 주제는 전산학을 비롯하여 개인적인 글 등 여러 가지를 해 볼 생각입니다. 시간이 날 때마다 작은 코드 하나 하나라도 작성해 보려고 합니다.

이런 작은 것 하나하나가 모이는 것이 생각했던 것보다 훨씬 중요한 것 같습니다. 더 늦기 전에 시작하려고 합니다. 글을 읽으시는 분들은 공유하고 싶은 생각이 있으시면 주저말고 코멘트나 메일 등을 통하여 생각을 나눠주셨으면 합니다.

2008년 초, 현재 추가: -_- 저만치 나가 있기는 커녕 발전이 없습니다. 이건 뭐...