ALGOBIT

2010/10/08

ハノイとバベルの塔 第八階 – 端末アニメ in Ruby

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 02:53

第五回, 第六回 と同様の構造で書いてみます.

#!/usr/bin/ruby

def move(src, tmp, dst, n)
	if (n == 0)
		return
	end
	move(src, dst, tmp, n - 1)
	dst.push(src.pop())
	printAStep()
	move(tmp, src, dst, n - 1)
end

def printAStep()
	sleep($w/1000.0)
	print "\x1b[2J\x1b[2;1H"
	$N.times do |i|
		$pegs.each do |peg|
			if (peg.length < $N - i)
				print " " + $ws + "|" + $ws + " "
			else
				l = peg[$N - i - 1]
				print " " + $ws[l..-1] + $bar[0..(l - 1)] + "|" +
					$bar[0..(l - 1)] + $ws[l..-1] + " "
			end
		end
		print "\n"
	end
	print $grnd + "\n"
end

$N = (0 < ARGV.length) ? ARGV[0].to_i : 3
$w = (1 < ARGV.length) ? ARGV[1].to_i : 500
$pegs = [[],[],[]]
$N.downto(1) do |i|
	$pegs[0].push(i)
end

$ws = " "*$N
$bar = "-"*$N
$grnd = "="*(6*$N + 9)

printAStep()
move($pegs[0], $pegs[1], $pegs[2], $N)

$N.times do |i| ... end のループに, 数値もオブジェクトであることや, ブロック(無名関数)を受け取る既定義メソッドなどに言語の特色が出ています.

手続き中で任意個の '-' からなる文字列を生成してもさほど遅くはならないと思うのですが, 言語毎の特色を見るために「$N個の '-' からなる文字列を作っておいて, 部分文字列を使う」ように書いています.

n個の '-' からなる文字列を "-"*n と書けるのは
* という演算子の機能を規定する言語の組み込み機能ではありません.
* も単なるメソッドであり, "-"*n の実態は "-".*(n) すなわち, 文字列 "-" のインスタンスメソッド * を引数 n で呼び出しているだけであり, クラスに適切なメソッドを定義することで実現されています.

部分文字列を取り出すときに, 配列の添字に負数が利用できるのも良いですね.

第五回, 第六回 で利用した「ランダムアクセスできるスタック」みたいな能力を「配列」が持っていることで記述量はずいぶん減っています.


Yugui
オライリージャパン
発売日:2008-06-26

本書は対象読者の設定が明快です.
既にいくつかのプログラミング言語を実用していて, 他のプログラミング言語と Ruby が違うところだけ知りたい人向けです.
従ってページ数に対して情報量が多く感じました. お買い得.
多くの言語を操る著者さんの, 特定のプログラミング言語に偏った考え方や用語にならないようにという配慮が素敵です.

2010/10/05

ハノイとバベルの塔 第七階 – 端末アニメ in Haskell

方針

第二階と同様, 出力は後回しにして, 出力すべきデータを作り出す部分を考えます.
今回は各手順を行った際の各杭の状態を順に出力したいので, 変化していく状態を, 状態のリストとして返すことができればよいですね.

状態の表現

各状態を, {各杭に積まれた円盤の番号を小さい順に並べたリスト} を三つの杭 A, B, C についてこの順に並べたリスト, とします.
例えば円盤の数 3 のハノイの塔の初期状態は

[[1,2,3],[],[]]

杭 A から杭 C に1 の円盤を移した状態は

[[2,3],[],[1]]

とします.

手順をあらわす関数

準備として, 手順を {移動前の杭, 移動後の杭, 円盤の番号} などの値の組で表現するのではなく, 手順を表す関数を考えてみます.
A の杭の一番上の円盤を B の杭に動かす関数は

(\[ah:at,b,c] -> [at,ah:b,c])

と書けます.
ghci で確かめてみます.

Prelude> (\[ah:at,b,c] -> [at,ah:b,c]) [[1,2] [] [3]]
[[2], [1], [3]]

関数の型は [[t]] -> [[t]] です.

Prelude> :t (\[ah:at,b,c] -> [at,ah:b,c])
(\[ah:at,b,c] -> [at,ah:b,c]) :: [[t]] -> [[t]]

どの杭からどの杭に移すかを指定すると上記のような関数を返す関数を考えてみましょう.
関数の型は Int -> Int -> ([[t]] -> [[t]]) です.
移動は 6 通りある訳ですが, これをそのまま並べて書けば,

move :: Int -> Int -> ([[t]] -> [[t]])
move 0 1 = (\[ah:at,b,c] -> [at,ah:b,c])
move 0 2 = (\[ah:at,b,c] -> [at,b,ah:c])
move 1 2 = (\[a,bh:bt,c] -> [a,bt,bh:c])
move 1 0 = (\[a,bh:bt,c] -> [bh:a,bt,c])
move 2 0 = (\[a,b,ch:ct] -> [ch:a,b,ct])
move 2 1 = (\[a,b,ch:ct] -> [a,ch:b,ct])

と書けます.
コードを toh07.hs に保存してあるものとして, ghci で確かめてみます.

Prelude> :l toh07.hs
*Main> (move 0 1) [[1,2],[],[3]]
[[2],[1],[3]]
*Main> (move 2 1) [[1,2],[],[3]]
[[1,2],[3],[]]
*Main> (move 2 0) [[1,2],[],[3]]
[[3,1,2],[],[]]

もう少し抽象化できそうな気はしますが…

手順をあらわす関数のリスト

次に, 手順を表す関数のリストを返す関数を考えてみます.
円盤の数 n と, どの杭から(インデクス s) どの杭に (インデクス d) に移すかを与え, 先ほどの手順のリストを返します.
t は残りの杭のインデクスです.

hanoi :: Int -> Int -> Int -> Int -> [([[t]] -> [[t]])]
hanoi _ _ _ 0 = []
hanoi s t d n = hanoi s d t (n - 1) ++
                [(move s d)] ++
                hanoi t s d (n - 1)

s, t および d が各杭を表す文字でなく, 状態を表すデータ中の杭のインデクスであること,
出力するリストの要素が関数であること, の他は
第二階 の hanoi 関数と同じです.

hanoi 0 1 2 n で 目的の関数列が得られます.
head (hanoi 0 1 2 n) は最初の手順を表す関数です.
n == 3 として, 最初の手順を初期状態 [[1,2,3],[],[]] に適用してみます.

Prelude> :l toh07.hs
*Main> (head (hanoi 0 1 2 3)) [[1,2,3],[],[]]
[[2,3],[],[1]]

手順のリストを状態に順に適用

関数のリストと初期値を引数に取り, 初期値, 最初の関数を適用した結果, 前の結果に次の関数を適用した結果, … といった値の列を得るような関数について考えます.
つまり [f0, f1, f2, ...] と x を与えると
[x, (f0 x), (f1 (f0 x)), (f2 (f1 (f0 x))), ... ]
を返すような関数です.
これは

iterf [] x = [x]
iterf (f:fs) x = x:iterf fs (f x)

のようにかけますので, 円盤の数 n のハノイの塔を解く過程の全ての状態を出力するには

iterf (hanoi 0 1 2 n) [[1..n],[],[]]

とすれば良いですね.

Prelude> :l toh07.hs
*Main> iterf (hanoi 0 1 2 3) [[1..3],[],[]]
[[[2,3],[],[1]],[[3],[2],[1]],[[3],[1,2],[]],[[],[1,2],[3]],[[1],[2],[3]],[[1],[],[2,3]],[[],[],[1,2,3]]]

これで問題を解く部分は完成です.

整形

円盤の数 n と
[[2,3],[1],[]]
という状態を与えると

    |        |        |
  --|--      |        |
 ---|---    -|-       |
===========================

のような n + 1 行の文字列を返す関数を作ります.

format :: Int -> [[Int]] -> String
format n s = format' $ pad0 s
  where
    ws m = take m $ repeat ' '
    bar m = take m $ repeat '-'
    pad0 = map (\x -> reverse (take n ((reverse x) ++ repeat 0)))
    rod x = (ws (n - x + 1)) ++ (bar x) ++ "|" ++
             (bar x) ++ (ws (n - x + 1))
    line a b c = (rod a) ++ (rod b) ++ (rod c) ++ "\n"
    format' [[],[],[]] = (take (6*n + 9) $ repeat '=') ++ "\n"
    format' [x:xs, y:ys, z:zs] = (line x y z) ++ format' [xs, ys, zs]

ws m は長さ m の空白文字, bar m は m 個のハイフンからなる文字列です.
pad0 は, 各状態を文字列で表現しやすいよう, 各杭を表すリストの先頭に長さが n になるまで に 0 を追加します.
n が 3 ならば, pad0 [[2,3],[1],[]] は [[0,2,3],[0,0,1],[0,0,0]] となります.

rod x は杭を表す縦棒の両側に, 円盤の大きさ x 個のハイフンと, 余白の空白からなる文字列を返します.
n が 3 の時, rod 1 は ” -|- “, rod 2 は ” —|— ” のようになります.

line a b c は, ある行に相当する各杭の円盤の大きさから, 一行分の文字列を作ります.

format’ は, 各杭の状態を表すデータが格段の円盤の大きさ (無い場合は 0) を表すよう pad0 で変形された状態を受け取り, line で作った一行分の文字列と {各杭の次の段以後の円盤のを引数として呼び出した自分自身} と連結します.
最後の行は take (6*n + 9) $ repeat ‘=’ で適当な数の ‘=’ にします.

確認

Prelude> :l toh02_02.hs
*Main> putStrLn (format 3 [[1,2,3],[],[]])
   -|-       |        |
  --|--      |        |
 ---|---     |        |
===========================
*Main> putStrLn (format 3 [[2,3],[1],[]])
    |        |        |
  --|--      |        |
 ---|---    -|-       |
===========================

main

main = do args <- getArgs
          let n | (not . null) args = read (head args)::Int
                | otherwise         = 3
          let w | length args > 1 = read (args!!1)::Int
                | otherwise       = 500
          printAllSteps n w $ iterf (hanoi 0 1 2 n) [[1..n],[],[]]
  where
    printAllSteps _ _ [] = do putStrLn ""
    printAllSteps n w (x:xs) = do
      usleep (w*1000)
      putStr "\x1b[2J\x1b[2;1H"
      putStr (format n x)
      printAllSteps n w xs

printAllSteps は, w ミリ秒停止して, 端末のクリアとカーソル位置の移動, 状態の表示を行い, 残りの状態に対して同じ手順を繰り返します.
リストの最後を認識するよう 40行目があります.
引数で, 円盤の数 n と, 各状態の表示の間の待ち時間 w (ミリ秒) を与えます.
デフォルトを円盤の数 3, 待ち時間 500 ミリ秒とします.

実行してみます.

ソースコード全体は以下のようになります.
toh07.hs

import System
import System.Posix.Unistd

move :: Int -> Int -> ([[t]] -> [[t]])
move 0 1 = (\[ah:at,b,c] -> [at,ah:b,c])
move 0 2 = (\[ah:at,b,c] -> [at,b,ah:c])
move 1 2 = (\[a,bh:bt,c] -> [a,bt,bh:c])
move 1 0 = (\[a,bh:bt,c] -> [bh:a,bt,c])
move 2 0 = (\[a,b,ch:ct] -> [ch:a,b,ct])
move 2 1 = (\[a,b,ch:ct] -> [a,ch:b,ct])

hanoi :: Int -> Int -> Int -> Int -> [([[t]] -> [[t]])]
hanoi _ _ _ 0 = []
hanoi s t d n = hanoi s d t (n - 1) ++
                [(move s d)] ++
                hanoi t s d (n - 1)

iterf [] x = [x]
iterf (f:fs) x = x:iterf fs (f x)

format :: Int -> [[Int]] -> String
format n s = format' $ pad0 s
  where
    ws m = take m $ repeat ' '
    bar m = take m $ repeat '-'
    pad0 = map (\x -> reverse (take n ((reverse x) ++ repeat 0)))
    rod x = (ws (n - x + 1)) ++ (bar x) ++ "|" ++
             (bar x) ++ (ws (n - x + 1))
    line a b c = (rod a) ++ (rod b) ++ (rod c) ++ "\n"
    format' [[],[],[]] = (take (6*n + 9) $ repeat '=') ++ "\n"
    format' [x:xs, y:ys, z:zs] = (line x y z) ++ format' [xs, ys, zs]

main = do args <- getArgs
          let n | (not . null) args = read (head args)::Int
                | otherwise         = 3
          let w | length args > 1 = read (args!!1)::Int
                | otherwise       = 500
          printAllSteps n w $ iterf (hanoi 0 1 2 n) [[1..n],[],[]]
  where
    printAllSteps _ _ [] = do putStrLn ""
    printAllSteps n w (x:xs) = do
      usleep (w*1000)
      putStr "\x1b[2J\x1b[2;1H"
      putStr (format n x)
      printAllSteps n w xs

追記: 2011.10.04
内容を大幅に修正しました.

追記: 2011.10.05
全然抽象的じゃないけど

move :: Int -> Int -> [[t]] -> [[t]]
move s d x = take 3 $ drop (3 - s) $ cycle $ rev
    $ (\[ah:at,b,c] -> [at,ah:b,c]) $ rev $ take 3 $ drop s $ cycle x
 where
   rev [a,b,c] | 0 < (mod (2 + d - s) 3) = [a,c,b] | otherwise = [a,b,c]

でも一応動く.


Graham Hutton
オーム社
発売日:2009-11-11

Bryan O'Sullivan,John Goerzen,Don Stewart
オライリージャパン
発売日:2009-10-26


ハノイとバベルの塔 第六階 – 端末アニメ in Java

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 03:05

第五回 の C の実装の構造をそのまま踏襲します.

class Toh06 {
	static Stack[] pegs = new Stack[3];
	static int N, w;
	static String ws = "", bar = "", grnd = "";

	static void print()
		throws InterruptedException
	{
		int i, j, k;

		Thread.sleep(w);
		System.out.print("\u001b[2J\u001b[2;1H");
		for (i = 0; i < N; i++) {
			for (j = 0; j < 3; j++) {
				if (pegs[j].depth < N - i)
					System.out.print(" " + ws + "|" + ws + " ");
				else
					System.out.print(
					  " " +
					  ws.substring(pegs[j].data(N - i - 1)) +
					  bar.substring(N - pegs[j].data(N - i - 1)) +
					  "|" +
					  bar.substring(N - pegs[j].data(N - i - 1)) +
					  ws.substring(pegs[j].data(N - i - 1)) +
					  " ");
			}
			System.out.println("");
		}
		System.out.println(grnd);
	}

	static void move(Stack src, Stack tmp, Stack dst, int n)
		throws InterruptedException
	{
		if (n == 0)
			return;
		move(src, dst, tmp, n - 1);
		dst.push(src.pop());
		print();
		move(tmp, src, dst, n - 1);
	}

	public static void main(String[] args)
		throws InterruptedException
	{
		int i;

		/*
		 * init
		 */
		N = (args.length < 1)?3:Integer.parseInt(args[0]);
		w = (args.length < 2)?500:Integer.parseInt(args[1]);
		pegs[0] = new Stack(N);
		pegs[1] = new Stack(N);
		pegs[2] = new Stack(N);
		for (i = 0; i < N; i++)
			pegs[0].push(N - i);

		/*
		 * for display
		 */
		for (i = 0; i < N; i++)
			ws += " ";
		for (i = 0; i < N; i++)
			bar += "-";
		for (i = 0; i < 6*N + 9; i++)
			grnd += "=";

		/*
		 * do
		 */
		print();
		move(pegs[0], pegs[1], pegs[2], N);
	}
}

class Stack {
	int depth;
	int[] _data;
	Stack(int n) { _data = new int[n]; depth = 0; }
	void push(int data) { _data[depth] = data; depth++; }
	int pop() { depth--; return _data[depth]; }
	int depth() { return depth; }
	int data(int position) { return _data[position]; }
}

スタックを (java.util.Stack は使わず) 内部クラスで定義.
この規模ではオブジェクト指向の良さは特に発揮できないかもしれませんが, もう少し整理された書き方ができるとは思います.

他の言語と違うところは. ファイル名とクラス名を一致させないといけないところです.
上記コードを Toh06.java として保存する必要があります.

2010/10/04

ハノイとバベルの塔 第五階 – 端末アニメ in C

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 13:59

スタック* を少々拡張し, 任意の位置のデータが見れるようにしたデータ構造で一つの杭に積んである円盤の番号を保存することにし, これを三つ用意します.

以下の操作を提供する stack 型のオブジェクトがあるとします. すなわち

  • init(int n) で n 個の int 値を格納できるよう初期化
  • push(int data) で data をスタックにプッシュ
  • int pop() でデータをスタックからポップ
  • int depth() で現在のスタックの深さを参照
  • int data(int position) で指定した位置の値を参照
  • finalize() で終了処理を行う

とします.

仮のコードは以下のようになります.

#include <stdio.h>
#include <stdlib.h>

stack pegs[3];
int N, w;

static void
print()
{
	int i, j, k;

	w ミリ秒停止.
	端末をクリアして 2行1桁目に移動.
	for (i = 0; i < N; i++) {
		for (j = 0; j < 3; j++) {
			if (pegs[j].depth < N - i)
				1 個の ' ', N個の ' ', 1個の '|', N個の ' ' および 1個の ' ' を出力.
			else
				1 個の ' ',
				(N - pegs[j].data[N - i - 1]) 個の ' ',
				pegs[j].data[N - i - 1] 個の '-',
				1 個の '|',
				pegs[j].data[N - i - 1] 個の '-',
				(N - pegs[j].data[N - i - 1]) 個の ' ' および
				1 個の ' ' を出力.
		}
		改行を出力.
	}
	(6*N + 9) 個の '=' と改行を出力.
}

static void
move(stack *src, stack *tmp, stack *dst, int n)
{
	if (n == 0)
		return;
	move(src, dst, tmp, n - 1);
	dst->push(src->pop());
	print();
	move(tmp, src, dst, n - 1);
}

int
main(int argc, char *argv[])
{
	/*
	 * init
	 */
	N = (argc < 2)?3:atoi(argv[1]);
	w = (argc < 3)?500:atoi(argv[2]);
	pegs[0].init();
	pegs[1].init();
	pegs[2].init();
	for (i = 0; i < N; i++)
		pegs[0].push(N - i);

	/*
	 * do
	 */
	print();
	move(&pegs[0], &pegs[1], &pegs[2], N);

	/*
	 * fainalize
	 */
	free(pegs[0].data);
	free(pegs[1].data);
	free(pegs[2].data);

	return 0;
}

stack の実装.

  • データ自体は構造体で定義します.
    typedef _struct {
    	int depth;
    	int *data;
    } struct;
    

    stack s; に対して

  • s.init(int n) は
    s.data = (int *)malloc(n*sizeof(int));
    s.depth = 0;
  • s.push(int data) は
    s.data[s.depth++] = data;
  • s.pop() は
     s.data[--s.depth];
  • s.depth() は
    s.depth
  • s.data(int position) は
    s.data[position]
  • s.finalize() は
    free(s.data);

のように書けます.
また, 利用される可能性のある最大の長さで, ‘ ‘, ‘-’, ‘=’ の連なった文字列を用意しておき, (用意した長さ – 出力したい長さ)番目の文字から出力することで任意の長さの ‘ ‘ や ‘-’ の連なった文字列を出力します.

それぞれのコード量が少ないので展開してしまいます. コードは以下のようになります.

#include <stdio.h>
#include <stdlib.h>

typedef struct _stack {
	int depth;
	int *data;
} stack;
stack pegs[3];
int N, w;
char *bar, *ws, *grnd;
char clsmv21[] = "\x1b[2J\x1b[2;1H";

static void
print()
{
	int i, j, k;

	usleep(w*1000);
	fputs(clsmv21, stdout);
	for (i = 0; i < N; i++) {
		for (j = 0; j < 3; j++) {
			if (pegs[j].depth < N - i)
				printf(" %s|%s ", ws, ws);
			else
				printf(" %s%s|%s%s ",
				  ws + (pegs[j].data[N - i - 1]),
				  bar + (N - pegs[j].data[N - i - 1]),
				  bar + (N - pegs[j].data[N - i - 1]),
				  ws + (pegs[j].data[N - i - 1]));
		}
		putchar('\n');
	}
	fputs(grnd, stdout);
}

static void
move(stack *src, stack *tmp, stack *dst, int n)
{
	if (n == 0)
		return;
	move(src, dst, tmp, n - 1);
	dst->data[dst->depth++] = src->data[--src->depth];
	print();
	move(tmp, src, dst, n - 1);
}

int
main(int argc, char *argv[])
{
	int i;

	/*
	 * init
	 */
	N = (argc < 2)?3:atoi(argv[1]);
	w = (argc < 3)?500:atoi(argv[2]);
	pegs[0].data = (int *)malloc(N*sizeof(int));
	pegs[0].depth = 0;
	pegs[1].data = (int *)malloc(N*sizeof(int));
	pegs[1].depth = 0;
	pegs[2].data = (int *)malloc(N*sizeof(int));
	pegs[2].depth = 0;
	for (i = 0; i < N; i++)
		pegs[0].data[pegs[0].depth++] = N - i;

	/*
	 * for display
	 */
	bar = (char *)malloc((N + 1)*sizeof(char));
	for (i = 0; i < N; i++)
		bar[i] = '-';
	bar[N] = '\0';
	ws = (char *)malloc((N + 1)*sizeof(char));
	for (i = 0; i < N; i++)
		ws[i] = ' ';
	ws[N] = '\0';
	grnd = (char *)malloc((6*N + 11)*sizeof(char));
	for (i = 0; i < 6*N + 9; i++)
		grnd[i] = '=';
	grnd[6*N + 9] = '\n';
	grnd[6*N + 10] = '\0';

	/*
	 * do
	 */
	print();
	move(&pegs[0], &pegs[1], &pegs[2], N);

	/*
	 * fainalize
	 */
	free(bar);
	free(ws);
	free(grnd);
	free(pegs[0].data);
	free(pegs[1].data);
	free(pegs[2].data);

	return 0;
}

ハノイとバベルの塔 第四階 – 端末アニメ イントロダクション

Filed under: 離散的な気まぐれ — タグ: , — Kohyama @ 13:49

実行結果を先にご覧ください.

目標とするのは
二つの引数 – 最初の引数をハノイの塔の円盤の数, 二つ目の引数を状態表示間の休み時間 (ミリ秒で指定) – を受け取り, ハノイの塔を解く過程の各状態を文字列を使った擬似的な絵で表示するプログラムです.

擬似的な絵は, 杭を文字 ‘|’ の縦の連なりで, 円盤を ‘|’ の両側の文字 ‘-’ の横の連なりで表します.
i番目に小さい円盤は ‘|’ の片側の ‘-’ の長さが i であるとします.
杭の長さは円盤の数, 杭あたりの横幅を 2 × 円盤の数 + 3 として実行時に動的に決定します.
三つの杭と円盤を横に並べて表示し, 最後の行に 6*円盤の数 + 10 個の ‘=’ を表示します.

円盤の数 3 のハノイの塔の初期状態であれば,

   -|-      |       |
  --|--     |       |
 ---|---    |       |
=========================

のようになります.

ハノイとバベルの塔 第三階 – C におけるモジュールの分離

Filed under: 離散的な気まぐれ — タグ: , , , — Kohyama @ 13:32

C言語で, 第一階 の Haskell の記述と同様な,
データ生成部分と出力部分を分離する方法について考えます.
いろいろなやり方があると思いますが, 以下に二例ほど記述したいと思います.

手順を出力する関数を用意し, パズルを解く関数への引数として, 用意しておいた関数への参照を渡すことができます.

#include <stdio.h>
#include <stdlib.h>

static void
printop(int i, char src, char dst)
{
    printf("move disc #%d from %c to %c\n", i, src, dst);
}

static void
hanoi(char src, char tmp, char dst, int n, void (*pp)(int, char, char))
{
    if (n == 0)
        return;
    hanoi(src, dst, tmp, n - 1, pp);
    pp(n, src, dst);
    hanoi(tmp, src, dst, n - 1, pp);
}

int
main(int argc, char *argv[])
{
    int n = (argc < 2)?3:atoi(argv[1]);
    hanoi('A', 'B', 'C', n, &printop);
}

グラフィカルなプログラムではユーザからのアクション(イベント)を検知する部分がライブラリとして用意されており, ライブラリ関数にそのイベントの処理方法を渡すような場合に良く使われる方法で「コールバック関数」または「デリゲート」などと呼ばる方法です.

もうひとつの例は, パズルを解く部分とデータを表示する部分で異なるプロセスもしくはスレッドに分かれて片方から片方へデータを引き渡す方法です.
以下の実装では UNIX系の OS でプロセスをフォークして, データをパイプ渡しにします.

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

struct _op {
    int i;
    char dst;
    char src;
};

static void
printop()
{
    struct _op op;

    while (read(0, &op, sizeof(op)) > 0)
        printf("move disc #%d from %c to %c\n", op.i, op.src, op.dst);
}

static void
hanoi(char src, char tmp, char dst, int n)
{
    struct _op op = {n, src, dst};

    if (n == 0)
        return;
    hanoi(src, dst, tmp, n - 1);
    write(1, &op, sizeof(op));
    hanoi(tmp, src, dst, n - 1);
}

int
main(int argc, char *argv[])
{
    int n = (argc < 2)?3:atoi(argv[1]);
    int fildes[2];
    pid_t pid;

    if (pipe(fildes) < 0) {
        perror("pipe");
        exit(1);
    }
    if ((pid = fork()) < 0) {
        perror("fork");
        exit(1);
    }
    if (pid == 0) {
        close(fildes[0]);
        dup2(fildes[1], 1);
        close(fildes[1]);
        hanoi('A', 'B', 'C', n);
        close(1);
    } else {
        close(fildes[1]);
        dup2(fildes[0], 0);
        close(fildes[0]);
        printop();
        close(0);
        _exit(0);
    }
    wait(0);
    return 0;
}

引き渡すデータが struct _op 型であるという約束の元, hanoi 関数は, 手順を標準出力に次々と出力し, printop 関数は標準入力から手順が入力されてくる限りそれを読み取って出力します. hanoi 関数の実装者は, 処理したデータがどう扱われるかについては関知しません.
もちろん, 他のプラットフォームでも本質的に同様な実装が可能です.

このコード規模では分離のための記述が全体に占める割合が大きくなってしまいますが, もう少し大きなプログラムであれば, 実用されている方式と思います.


デビッド・A. クリ
アスキー
発売日:1991-07

実際に役に立つプログラムは直接なり間接なり OS と対話しなければならない.
とくに UNIX オペレーティングシステムを記述するために生まれた C 言語は, より上位のプログラミング環境の下位で OS と直接対話する基盤である.
システムコールプログラミングへようこそ.
Steve Oualline
オライリー・ジャパン
発売日:1998-06-15


Copyright © 2010 Yoshinori Kohyama All Rights Reserved.