여러 언어로 보는 구구단 프로그램

당연한 얘기지만, 같은 문제를 푸는 방법은 한 가지가 아니다. 예를 들면 구구단을 출력하는 간단한 프로그램을 짜더라도 언어에 따라서 자연스러운 문제 풀이는 굉장히 달라진다. j로 구구단을 짜보면 이런 식으로 나온다.

#!plain_text!#
x =: 1 + i.9
x */ x

결과

#!plain_text!#
1  2  3  4  5  6  7  8  9
2  4  6  8 10 12 14 16 18
3  6  9 12 15 18 21 24 27
4  8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81

아마 이런 식으로 프로그래밍을 하는 건 다른 프로그래밍 언어에서는 상상하기 힘든 방식인데, 이건 j가 백터 기반의 언어이기 때문에 그렇다. 첫번째 문장에서 i.9는 0에서 8까지의 백터를 생성한다. 그리고 그 백터에 1을 더해서 1에서 9까지의 백터로 만든다. 그리고 외적 연산자인 */를 사용해서 구구단 테이블을 만든다.

같은 백터 기반의 언어인 R에서도 간단히 할 수 있을까 생각해봤는데, */ 같은 연산자가 떠오르질 않아서 J처럼 하는 방법은 도무지 안 떠오르고... 그냥 R스럽게 메트릭스 2개 만들어서 곱했다.

#!r!#
matrix(rep(1:9, 8), 8, 9, byrow=T) *
  matrix(rep(2:9, rep(9, 8)), 8, 9, byrow=T)

결과

#!plain_text!#
[,1] [,2] [,3] [,4] [,5] [,6] [,7] [,8] [,9]
[1,]    2    4    6    8   10   12   14   16   18
[2,]    3    6    9   12   15   18   21   24   27
[3,]    4    8   12   16   20   24   28   32   36
[4,]    5   10   15   20   25   30   35   40   45
[5,]    6   12   18   24   30   36   42   48   54
[6,]    7   14   21   28   35   42   49   56   63
[7,]    8   16   24   32   40   48   56   64   72
[8,]    9   18   27   36   45   54   63   72   81

찾아보니까 R에서도 j처럼 외적 연산자가 기본으로 있었다.

#!r!#
2:9 %o% 1:9

루비로도 만들어봤다. 루비스럽게 블럭으로. 블럭이 쓰이긴 했지만 이렇게 두 번 루프 돌리는 게 어떤 언어에서든 일반적으로 구구단 푸는 제일 무난한 방법일 것 같다.

#!ruby!#
2.upto(9) { |x| 1.upto(9){ |y| print "#{x * y} "}; puts}

결과

#!plain_text!#
2 4 6 8 10 12 14 16 18
3 6 9 12 15 18 21 24 27
4 8 12 16 20 24 28 32 36
5 10 15 20 25 30 35 40 45
6 12 18 24 30 36 42 48 54
7 14 21 28 35 42 49 56 63
8 16 24 32 40 48 56 64 72
9 18 27 36 45 54 63 72 81

별로 대단한 예제는 아니지만, j 예제보다가 살짝 충격을 받고, R로도 해보는데 매트릭스 만들어서 문제풀고 있는 스스로한데 충격을 받았다. 사실 R에서도 루프 두 번 돌려서 푸는 건 가능한데도 R을 잡고있다보니 저런 풀이가 제일 먼저 떠올랐다. 물론 루비든 다른 언어에서든 적당히 외적 함수 만들면 이런 식으로 풀 수 있겠지만, 역시 가장 자연스러운 풀이는 아니다.

찾아보니 doukaku라는 일본 사이트에도 구구단(일본어로는 九九) 만드는 문제가 나와있었는데 거기서 본 특이한 풀이법들. 아래처럼 출력해야한다. 괄호 안은 코드 만드신 분.

1 * 1 = 1

1 * 2 = 2

1 * 3 = 3

(생략)

7 * 8 = 56

7 * 9 = 63

8 * 1 = 8

8 * 2 = 16

8 * 3 = 24

8 * 4 = 32

(생략)

9 * 8 = 72

9 * 9 = 81

루비. sprintf나 루프를 사용하지 않고. (ihag)

#!ruby!#
i, x, y = 0, 0, 0
puts (('o' * 9 + "\n") * 9).
  gsub(/^/) { (y += 1).to_s + ':' }.
  gsub(/o/) { i = x; x = (x + 1) % 9; i + 1 }.
  gsub(/^(\d+):(.+)/) {
    y, line = $~[1..-1]
    line.gsub(/\d/) {
      "#{y} * #{$&} = " +
        (' ' + (y.to_i * $&.to_i).to_s)[-2, 2] +
        "\n"
    }
  }.gsub("\n\n", "\n")

factor. 음 factor는 전혀 모르겠다. 출력함수 정의하고 each 봐선 반복자로 돌리는 것 같긴한데... 실행도 안 되고, 디버깅할 능력도 없고. (queck)

#!plain_text!#
USING: kernel sequences math math.parser io ;

: out ( x y -- )
  over number>string write
  " * " write
  dup number>string write
  " = " write
  * number>string 2 CHAR: \s pad-left write nl ;

9 [ 1+ 9 [ dupd 1+ out ] each drop ] each

R. 위에서 얘기했던 외적 연산자와 출력함수를 사용하는 방법인데, 아 R에서 출력하는 거 진짜 불편하다. 그래프는 잘 만들어 주지만. (pooq)

#!r!#
a <- 1:9
b <- a %o% a
writeLines(paste(col(b),"*",a,"=",format(b,0,2))

prolog. 논리형 언어.(pooq)

#!prolog!#
d(1). d(2). d(3). d(4). d(5). d(6). d(7). d(8). d(9).
:-d(A),d(B),C is A*B,\+writef('%d * %d =%3r',[A,B,C]).

prolog. 다른 풀이법. (yohei)

#!prolog!#
kuku :- between(1, 9, X), between(1, 9, Y),
    Z is X * Y, format('~d * ~d =~t~d~10|~n', [X, Y, Z]).
:- findall(_,kuku,_).

c. 실행 속도는 최고속, 개발 속도는 최악. 스스로 계산하기.

#!c!#
#include <stdio.h>
void main() {
 printf("1 * 1 =  1\n");
 printf("1 * 2 =  2\n");
 printf("1 * 3 =  3\n");
 printf("--- 中略 ---\n");
 printf("9 * 8 = 72\n");
 printf("9 * 9 = 81\n");
}

c. 실행속도를 올리려면 printf 81번 쓰는 거보다 fputs 한 번 쓰는 게 낫다는 지적. (ihag)

#!c!#
#include <stdio.h>
void main()
{
  fputs("1 * 1 =  1\n"
        "1 * 2 =  2\n"
        "1 * 3 =  3\n"
          /* 中略 */
        "9 * 8 = 72\n"
        "9 * 9 = 81\n",
        stdout);
}

common lisp, 재귀판. (ibaza)

#!lisp!#
(defun kuku (&optional (x 1) (y 1))
  (cond ((and (= x 9) (> y 9)) nil)
        ((> y 9) (terpri) (kuku (1+ x) 1))
        (t (format t "~D * ~D = ~2D~%" x y (* x y))
         (kuku x (1+ y)))))

perl. 마법의 연산자 $_. 이런 거 보면, perl 공부하고 싶다. (ぴょん)

#!perl!#
map {printf "%d * %d = %2d\n",$_,++$count{$_},$count{$_}*$_} (my %count, map {($_) x 9} 1..9);

haskell. 헤에... 하스켈로는 이렇게 쓰는구나. 알 듯 모를 듯. (nobsun)

#!haskell!#
import Text.Printf

displayNN n
 = sequence_
 $ do { i <- s
      ; j <- s
      ; return $ putStrLn $ printf fmt i j (i*j)
      }
   where
     s   = [1..n]
     k   = length $ show n
     k'  = length $ show (n*n)
     fmt = printf "%%%dd * %%%dd = %%%dd" k k k'

scheme. 상식 없는 사람의 풀이라는데, 이건 뭘까. 전체적으로 함수형 언어들 풀이가 변태적인 것 같다. (koguro)

#!scheme!#
(use srfi-42)

(for-each (lambda (line i)
          (cond
           ((memq i '(0 1 2 61 62 63 64 65 66 79 80))
            (print line))
           ((memq i '(3 67))
            (print "(略)"))
           (else
              #f)))
        (list-ec (: a 1 10) (: b 1 10) (format "~a * ~a = ~2@a" a b (* a b)))
          (iota 100))

scheme. 아... 왠지 평범하게 보인다. (katsu)

#!scheme!#
(use srfi-42)
(do-ec (: i 1 10) (: j 1 10) (format #t "~d * ~d = ~2d\n" i j (* i j)))

haskell. 이것도 평범하게 보이긴 하는데... 평범하면 오히려 이상하게 보이는 함수형 언어들. (mad)

#!haskell!#
{-# OPTIONS_GHC -fglasgow-exts #-}
import Text.Printf (printf)
import Data.Traversable

main = for [1..9] (\(i::Int) ->
         for [1..9] (\(j::Int) ->
             printf "%d * %d = %2d\n" i j (i*j)
         )
       )

sql. 쿼리. 아.... (flmn)

#!sql!#
DROP TABLE digits;
CREATE TABLE digits(n INTEGER PRIMARY KEY);
INSERT INTO digits VALUES(1);
INSERT INTO digits VALUES(2);
INSERT INTO digits VALUES(3);
INSERT INTO digits VALUES(4);
INSERT INTO digits VALUES(5);
INSERT INTO digits VALUES(6);
INSERT INTO digits VALUES(7);
INSERT INTO digits VALUES(8);
INSERT INTO digits VALUES(9);

  SELECT a.n || ' * ' || b.n || ' = ' || TO_CHAR(a.n * b.n , '99') AS kuku
    FROM digits a, digits b
ORDER BY b.n, a.n ;

c. Intel 계에서만 돌아가는 방법이라는데 전혀 모르겠다. (tamanecoplus)

#!c!#
#include <stdio.h>

int main() {
  long a[3];
  int i, j;
  for (i = 0; i <= 80; i++) {
    j = (i/9 + 1) * (i%9 + 1);
    a[0] = 539631665L + i/9;
    a[1] = 540876849L + i%9;
  a[2] = 12320L + (j%10)*256 + (j>9 ? 16+j/10 : 0);
    puts((char *)a);
  }
  return 0;
}

matlab. matlab 없어서 중간에 어떻게 돌아가는 지는 잘 모르겠지만. 이것도 벡터를 이용한 방법이라고 한다. 외적은 아니고 매트릭스 만들어서 곱하는 걸로 보이긴 하는데... (atsu-kan)

#!plain_text!#
a = kron(1:9, repmat(1, 1, 9));
b = repmat(1:9, 1, 9);
c = a.*b;
fprintf('%d * %d = %2d\n', [a; b; c])

여담이지만 lisp 괄호는 볼 때마다 느끼는 거지만 진짜 너무 안 예쁘다. c도 전부 소괄호면 충분하지 않냐는 사람들의 눈은 진짜 이해가 안 된다...;


Who am I?