ALGOBIT

2010/12/10

ぷログラミング

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

昨日 pgcafe の忘年会に参加して参りました.

pgcafe がコラボレーションのきっかけになっていますという常連皆さんのまとめ発表に混じり, ひとり自分勝手に最近興味のある関数プログラミングについて語ってきました. すいません.

私が最近になってはまっているというだけで, 関数プログラミング自体は新しい話題という訳ではないので, 既に知っている方か, 特に興味のない方が大半だと思うのですが, もしかしたら私の発表で興味を持ってくださった方もいないとも限りませんので一応資料載せておきます.

あわてて作った資料のため「ぷログラミング」と誤植していたのですが, 意外にかわいいのでそのまま修正せずにおきます.

関数ぷログラミング紹介

準備・持ち寄りしてくださった皆さんありがとうございました.
夜から参加で手伝えずいただくばかりでごちそうさまでした.

そして今年一年ありがとうございました.
来年は昼間は行けそうにありませんが, オンラインで, 二次会やイベントで, 参加できればと思っています.

追記: Haskell では構文をプログラムから書き換えるようなことはできるかというご質問ありました. Haskell ではできません. LISP ではできます. → LISPならば容易にできます. Haskell でも LISP ほど容易ではないけれどもできます.([2010.12.11] eagletmt さん情報により修正)
プログラムソースの構文が, プログラムが扱うデータと同じ構文になっていることを「同図像性」というのですが, LISP (といっても特定しきれませんが) はこの特徴を持っており, 構文を再定義することができます.
同図像性は関数プログラミングの特色というわけではないと思います.
逆に, 関数プログラミングの特色でないもので Haskell が持っている機能をリストしてしまってあるかもしれません. あしからず.

2010/12/02

第二双対定理(foldr と foldl の関係)

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

第二双対定理

演算子 ⊕, ⊗ および a は全ての x, y, z について, つぎの関係を満たすものとする.

x ⊕ (yz) = (xy) ⊗ z
x a = ax

つまり, ⊕ と ⊗ はたがいに結合的であり, ⊕ の右側に a があることと ⊗ の左側に a があることが等しいということである.
これらの条件のもとで, 任意の有限リスト xs に対して,

foldr (⊕) a xs = foldl (⊗) a xs

が成り立つ.

出典: 「関数プログラミング」p.72

だそうだ.

xs の要素を x1, x2, x3, … xn
とすると

foldr (⊕) a xsx1 ⊕ (x2 ⊕ (x3 ⊕ … (xna) … ))
foldl (⊗) a xs ⇒…(((ax1) ⊗ x2) ⊗ x3) … ⊗ xn

であり, 上の条件化ではこの二つは等しいからだ.

GHCi で, Haskell における reverse の再実装をしてみよう.

Prelude> :{
Prelude| let revr = foldr postfix []
Prelude| where postfix a xs = xs ++ [a]
Prelude| :}
Prelude> revr [1, 2, 3, 4]
[4,3,2,1]
Prelude> :{
Prelude| let revl = foldl prefix []
Prelude| where prefix xs a = [a] ++ xs
Prelude| :}
Prelude> revl [1, 2, 3, 4]
[4,3,2,1]

a `postfix` xs はリスト xs の後に, a だけからなるリストを連結したリスト.
xs `prefix` a は a だけからなるリストの前に, リスト xs を連結したリスト.

x `postfix` (y `prefix` z) と (x `postfix` y) `prefix` z は等しい.
x `postfix` [] と [] `prefix` x は等しい.

ので, revr と revl は(数学的には)等しい.

ふむ… わかったようなわからんような.


R. バード,P. ワドラー
近代科学社
発売日:1991-04

学部時代, 本著の翻訳者である武市先生の授業の教科書でした.
当時(1997年だったかな)C言語で計測値に対する解析計算をするプログラムを書くのに精一杯だった私にとっては, 実用性の無い数学論に見えましたが, Haskell や他の関数型言語を齧った現在読むと, とても系統的に関数型言語についてまとめられていることが分かります.
翻訳も, 翻訳であることを意識させない滑らかさです. 元の英語を括弧書きする頻度も抜群です.

動く様を見ないでも, 理論からその世界を想像できる方は, いきなりこれを読んでもよいでしょう.
実際に動かしてみないと分からない私のような人は, なんらかの関数型言語のチュートリアルを終えた後に読むと理解が系統立ってくるので良いと思います.

カリー化

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

「カリー化」は元々は, 複数引数を取る関数を, 「1つだけ引数を取り関数を返す関数」の連鎖で統一的に表現する記述方法の事を言う.
三つの引数を取りその和を返す関数 f を (ある型 a に対して)「(a, a, a) → a」という型を持つ関数「f (x, y, z) = x + y + z」と表現するか, 「a → (a → (a → a))」(あるいは ‘→’ は右結合であると約束して「a → a → a → a」) という型を持つ関数「f x y z = x + y + z」として表現するかの二つの表現方法について後者の事を「カリー化」された記述と呼ぶ.
実際の処理内容の違いを直接示す訳ではないし, なんらかの変換手続きの事でもない.

ところが, プログラミング言語を特定すると「書き方」が決められてくる. なんとなれば, プログラミング言語とは, ある処理をどう書くかを規定するものだからだ.

C言語で

int f(int x, int y, int z) { return x + y + z;}

という関数 f の定義があったとして「f(3) と書くと, 『二つ引数を取り, 3 とそれらの総和を取る関数』を意味する」というようなことはない. コンパイラが構文エラーを吐くか, コンパイラのエラーを無視して, スタックに 3 を積んで, 関数 f のアドレスへジャンプしても, スタックの不整合が起こるのみである.

Haskell や ML では, 内部構文がカリー化されている. Haskell で

f x y z = x + y + z

のように, 「三つの引数を取り, それらの総和を返す関数」f を定義をした場合, f 3 は「二つの引数を取り, 3 とそれらの総和を返す関数」であるし, f 3 4 は「一つの引数を取り, 3, 4 およびその引数の総和を返す関数」であり, f 3 4 5 は「3, 4, 5 の和を返す関数」である.
より正確に言うと, f は【引数を一つ取り x をその値に束縛し『引数を一つ取り y をその値に束縛し「引数を一つとり, x と y とその数の和を返す関数」を返す関数』を返す関数】であり,
Haskell での型宣言 f :: a -> a -> a-> a は, そのことを示している.
そもそも, 複数引数を取る関数が定義できているかのように記述できることの方が構文糖衣 (内部的には, もっと基本的な別の構文に変換されるような, コードを見やすくするために提供される書き方のバリエーション) なのである.

たとえば, 「二引数を取り値を返す関数」を要求する高階関数への引数として f 3 を渡すことができる. Haskell インタプリタ GHCi で確認してみよう.

Prelude> let f x y z = x + y + z   -- 3引数の和を返す関数 f を定義
Prelude> :t f                      -- f の型を確認
f :: (Num a) => a -> a -> a -> a   -- Num に属する任意の型 (仮に a とする) について
                                   --   a 型の引数を一つ取り,
                                   --     a 型の引数を一つ取り,
                                   --       a 型の引数を一つ取り a 型の値を返す関数
                                   --     を返す関数
                                   --   を返す関数
                                   -- 言い換えると,
                                   --   三つの引数を取り, 一つの値を返す関数.
                                   -- になっている.

Prelude> :t (f 3)                  -- f 3 の型を確認
(f 3) :: (Num t) => t -> t -> t    -- Num に属する任意の型 (仮に t とする) について
                                   --   t 型の引数を一つ取り,
                                   --     t 型の引数を一つ取り t 型の値を返す関数
                                   --   を返す関数
                                   -- 言い換えると,
                                   --   二つの引数を取り, 一つの値を返す関数.
                                   -- になっている.

Prelude> :t zipWith                -- zipWith の型を確認
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c]
                                   -- 任意三つの型 (仮に, a, b, c とする) について
                                   --   (a 型の引数を取り
                                   --      b 型の引数を取り c 型の値を返す関数
                                   --    を返す関数)
                                   --   を引数に取り
                                   --     a 型のリストを引数を取り
                                   --       b 型のリストを引数に取り c 型のリストを返す関数
                                   --     を返す関数
                                   --   を返す関数
                                   -- 言い換えると,
                                   --   (二つの引数を取り, 値を返す関数) と, 二つのリストを引数に取り,
                                   --   リストを返す関数.
                                   -- となっている.

Prelude> zipWith (f 3) [5, 7, 11, 13] [17, 19, 23, 29]
[25,29,37,45]                      -- 引数を二つ取る関数 (f 3) に
                                   -- 二つのリストの要素を順に与え,
                                   -- 返り値を順に要素とするリストを得た.

このように最初からいくつ目かまでの引数を適用し, 残りの引数を取るような関数をオブジェクトとして扱うことを「関数の部分適用」という.
カリー化表現を全面的に採用している言語では, 部分適用した関数を作るために特別な処理は必要ないが, そうでなくて且つ部分適用をサポートする言語では, 関数と最初の引数を与えると, 残りの引数を取る関数に変換することができる.
この関数の変換を行う関数が curry という名前であることが多いが, curry が行うのは関数部分適用であって, 「カリー化」ではない.

紛らわしいことには, Haskell にも curry という関数があり, これは, 一つのペアを引数に取る関数を二引数関数(実際には一引数を取る関数を返す一引数の関数)に変換する.

元の意味はともかく, 引数を部分適用した関数を作成する関数が curry という名前であることが多く, この curry 関数がしてくれる変換を「カリー化 (currying)」と呼ぶ人も多いので, 別にそれでもいいんではないかとは思う.

いずれにせよ, プログラマにとっては引数を部分適用した関数が作成できてオブジェクトとして扱えるかどうか, その処理はどう書くか, が関係するのみだ.
高階関数(関数を引数に取ったり, 関数を返したりする関数)があるような言語, とりわけ関数が第一級のオブジェクト(値も関数も同じように変数に束縛できる)であるような言語では, curry という名前の関数がこの変換を行ってくれる.

追記:
scala だと, _ を使った表記で, 引数を部分的に適用した関数を表現したり,

scala> def func1(x:Int, y:Int, z:Int):Int = x + y + z
func1: (Int,Int,Int)Int
scala> func1(3, _:Int, _:Int)
res1: (Int, Int) => Int = <function>

そもそもカリー化された状態で関数を記述し, 最初から任意個までの引数を適用した関数を表現したり (最後にアンダーバー ‘_’ が要るけど) できるようだ.

scala> def func2(x:Int)(y:Int)(z:Int):Int = x + y + z
func2: (Int)(Int)(Int)Int
scala> func2(3)_
res2: (Int) => (Int) => Int = <function>

また, Function.curried(func1 _) で func2 と同じ型の関数を, Function.uncurried(func2 _) で func1 と同じ型の関数を作れるらしい. この命名は適切に思える.

Haskell の例と同じことをやろうとしたが, zipWith が無いので, zip して map したが, それだと (Int, Int) のペア一つを取る関数を要求してしまう. func1 も func2 も直接渡せない. なんか良い例題ないかな…

scala> List(5, 7, 11, 13) zip List(17, 19, 23, 29) map func2(3)_
<console>:6: error: type mismatch;
 found   : (Int) => (Int) => Int
 required: ((Int, Int)) => ?
       List(5, 7, 11, 13) zip List(17, 19, 23, 29) map func2(3)_
                                                       ^

↓これでは部分適用の例題にならない…

scala> List(5, 7, 11, 13) zip List(17, 19, 23, 29) map {p => func2(3)(p._1)(p._2) }
res3: List[Int] = List(25, 29, 37, 45)

2010/11/21

簡易KVS mutable版

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

Haskell です.

前回の簡易KVSに, ファイルからのロード, セーブを追加. 操作を破壊的にしたもの.

kvs.hs : ソース

import System.IO
import Data.List
import Data.IORef

-- print an IORef object
p r = readIORef r

-- display contents of a file
cat fn = do
	h <- openFile fn ReadMode
	hGetContents h >>= putStr
	hClose h

-- to bind variable, use like 'r <- load filename'
-- r will be the type of IORef [(Int,String)]
load myRead fn = do
	h <- openFile fn ReadMode
	c <- hGetContents h
	newIORef $ myRead c

save fn r = do
	h <- openFile fn WriteMode
	c <- readIORef r
	hPrint h c
	hClose h

new = newIORef []

ins e r = modifyIORef r (e:)
lup k r = do
	t <- readIORef r
	return (lookup k t)
upd (k,v) r = modifyIORef r
	((\(l, r) -> l ++ (k, v):tail r) . break ((k ==) . fst))
del k r = modifyIORef r ((\(l, r) -> l ++ (tail r)) . break ((k ==) . fst))

「IORef a 型の値」(a は任意の型) を第一引数, 「a 型の値を引数にとって a 型の値を返す関数」を第二引数に modifyIORef を呼び出すと, IORef で参照される値が変更されます.
ので, 前回との違いは,

del k t = (\(l, r) -> l ++ (tail r)) $ break ((k ==) . fst)

のように引数 t を変更した新しいテーブルを返すように書いていた関数を以下のように書き換えます.
すなわち, 左辺で, t を newIORef を使って, IORef 型にした変数 r を使います.
右辺では第一引数を r として, 第二引数を元の関数の右辺として, modifyIORef を呼び出します.

del k r = modifyIORef r ((\(l, r) -> l ++ (tail r)) . break ((k ==) . fst))

test.kvs : テスト用ファイル

[(1, "foo"),
 (2, "bar"),
 (3, "baz")]

使ってみます.

% ghci
GHCi, version 6.10.4: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.
Prelude> :l kvs.hs              -- 上記ソースをロードします.
[1 of 1] Compiling Main             ( kvs.hs, interpreted )
Ok, modules loaded: Main.
*Main> cat "test.kvs"           -- ファイルの中身を確認
[(1, "foo"),
 (2, "bar"),
 (3, "baz")]
*Main> r <- load (read::String -> [(Int,String)]) "test.kvs"
                                -- test.kvs の中身を [(Int,String)] として解釈し, r に束縛
*Main> p r
[(1,"foo"),(2,"bar"),(3,"baz")]
*Main> ins (4,"qux") r          -- (4, "qux") を挿入
*Main> p r
[(4,"qux"),(1,"foo"),(2,"bar"),(3,"baz")]
*Main> lup 2 r                  -- キーが 2 であるエントリを検索
Just "bar"
*Main> lup 5                    -- キーが 5 であるエントリを検索
                                -- Nothing が返されたときの対処をしていないので
<interactive>:1:0:              -- 実行時例外
    No instance for (Show (IORef [(t, b)] -> IO (Maybe b)))
      arising from a use of `print' at <interactive>:1:0-4
    Possible fix:
      add an instance declaration for
      (Show (IORef [(t, b)] -> IO (Maybe b)))
    In a stmt of a 'do' expression: print it
*Main> upd (2,"quux") r         -- キーが 2 であるエントリの値を "quux" に変更
*Main> p r
[(4,"qux"),(1,"foo"),(2,"quux"),(3,"baz")]
*Main> del 1 r                  -- キーが 1 であるエントリを削除
*Main> p r
[(4,"qux"),(2,"quux"),(3,"baz")]
*Main> save "test2.kvs" r       -- "test2.kvs" に保存
*Main> cat "test2.kvs"          -- "test2.kvs" の中身を確認
[(4,"qux"),(2,"quux"),(3,"baz")]

追記: 2010.11.26
これ KVS って言わないよな… タイトルから来た方すいません.

2010/11/20

GHCiで簡易KVS

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

Haskell です.

Eq k => [(k, v)] の形をしたリスト t について考えます.
k は Eq クラスを実装した型(*1)
v は任意の型です.
k についてソートされていることを要求しないとします.
k について重複は無いものとします.
t へのデータの挿入・検索・更新・削除は次のように書けます.

lut.hs

import Data.List

ins (k,v) t = (k, v):t
lup key [] = Nothing
lup key ((k, v):ts)
	| key == k = Just v
	| otherwise = lup key ts
upd (k, v) t = (\(l, r) -> l ++ (k, v):tail r) $ break ((k == ) . fst) t
del k t = (\(l, r) -> l ++ (tail r)) $ break ((k ==) . fst) t

使ってみましょう.
ghci のコマンドライン引数に lup.hs を渡すか, 起動後に :l でロードします.

% ghci
GHCi, version 6.10.4: http://www.haskell.org/ghc/  :? for help
Loading package ghc-prim ... linking ... done.
Loading package integer ... linking ... done.
Loading package base ... linking ... done.
Prelude> :l lut.hs
[1 of 1] Compiling Main             ( lut.hs, interpreted )
Ok, modules loaded: Main.
*Main>

とりあえず最初のバージョンのテーブル t を作り, ins で新エントリを挿入してみます.

*Main> let t = [(1, "foo"), (3, "baz"), (2, "bar")]
*Main> t
[(1,"foo"),(3,"baz"),(2,"bar")]
*Main> ins (4, "qux") t
[(4,"qux"),(1,"foo"),(3,"baz"),(2,"bar")]

いいですね.
ここで t は変更されないので, 変数の非破壊性についてはまたの機会に述べるとして, とりあえず別の変数に代入します.

*Main> let t2 = ins (4, "qux") t
*Main> t2
[(4,"qux"),(1,"foo"),(3,"baz"),(2,"bar")]

Haskell には, Maybe モナドという, 値があるなら Just , 値が無いなら Nothing という, エラー処理は上位のコードに任せましょうな統一的な多相型があります.
lup k t は, テーブル t に, キー k であるエントリが見つからなければ Nothing を, 見つかればその値を v として Just v を返します.

*Main> lup 3 t2
Just "baz"
*Main> lup 5 t2
Nothing

Maybe なんちゃら型を受け取った上位のコードはどうするかというと, 返り値が Nothing ならばエラー処理をして, そうでないなら fromJust で値を取り出すとか, さらに上位にエラー処理を任せるよう, 別の Maybe 型を返すようにしたりするわけですが, ここではコード片の試験なので, Nothing かどうかチェックせず fromJust を適用して, 実行時例外に任せます.

*Main> :m +Maybe

「:m +」はソースコード中の import 文のようなものです. fromJust が使えるように Maybe モジュールをスコープに追加します.

*Main Maybe> fromJust $ lup 3 t2
"baz"
*Main Maybe> fromJust $ lup 5 t2
"*** Exception: Maybe.fromJust: Nothing

更新と削除もやってみます.

*Main Maybe> upd (3, "quux") t2
[(4,"qux"),(1,"foo"),(3,"quux"),(2,"bar")]
*Main Maybe> del 3 t2
[(4,"qux"),(1,"foo"),(2,"bar")]

いいようです.

挿入
(k, v) を t に挿入したリストを返す関数は, 元のリストの先頭に要素を追加するだけです.

ins (k,v) t = (k, v):t

今回は, わざわざ関数に名前を付けましたが, t が特定の構造を保つことを要請するようなデータ構造でなければ, 右辺をそのまま書いた方が速いですね.

検索
k が key であるペアを検索してあれば Just v をなければ Nothing を返す関数は

lup key [] = Nothing
lup key ((k, v):ts)
	| key == k = Just v
	| otherwise = lup key ts

と書けます. 今回はわざわざ書きましたが, 標準ライブラリ Prelude に含まれる lookup でまったく同じことができますので, 特に理由がなければ, 標準の lookup を使えばよいでしょう.

更新
k をキーとする要素の値を v で置き換えます.

upd (k, v) t = (\(l, r) -> l ++ (k, v):tail r) $ break ((k == ) . fst) t

break f t は, 「ある型の値を引数にとって Bool 値を返す関数 f」と「f が受け取れる型のリスト t」を引数にとり, t の要素に対して先頭から順に f を適用し, 初めて True を返した要素で t を分割します. f を満たさない間のリストを l, f を最初に満たした要素以降のリストを r とすると, (l, r) というペアで返します.
(\(l, r) -> l ++ (k, v):tail r) は無名関数で, (l, r) というペアとして引数を受け取り, r の最初の要素を取り除いたものの先頭に (k, v) を追加し, l の後ろに連結します.

t2 = [(4,"qux"),(1,"foo"),(3,"baz"),(2,"bar")]
upd (3, "quux") t2

に対しては

((3 ==) . fst) (4, "qux") = False
((3 ==) . fst) (1, "foo") = False
((3 ==) . fst) (3, "baz") = True
break ((3 ==) . fst) t2 = ( [(4,"qux"),(1,"foo",)],  [(3,"baz"), (2,"bar")] )
(\(l, r) -> l ++ (k, v):tail r)  ( [(4,"qux"),(1,"foo",)],  [(3,"baz"), (2,"bar")] )
  = [(4,"qux"),(1,"foo",)] ++ (3, "quux"):tail [(3,"baz"), (2,"bar")])
  = [(4,"qux"),(1,"foo"),(3,"quux"),(2,"bar")]

といった感じに評価されます.

削除
k をキーとする要素を t から削除する関数 del は

del k t = (\(l, r) -> l ++ (tail r)) $ break ((k ==) . fst) t

と書けます. 更新のコードとだいたい同じです.
List モジュールの deleteBy を使って

del2 k t = deleteBy (\x y -> fst x == fst y) (k, []) t

と書いてもいいです. (k, []) と fst が一致するようなペアをリスト t から削除します.

*1: Haskell でいう「クラス」は, 一般的には「抽象型」もしくは「抽象クラス」と呼ばれるものの一種で, 実装すべき関数を規定するもの. Java でいう「インターフェイス」, ML でいう「シグネチャ」. C++ は多重継承できるから要らない? 型無し(弱い型付け)言語には関係ない?
Haskell でいう「型」が, オブジェクト指向でいうところの「クラス」に相当すると思ってよい.
紛らわしいですね.
Eq クラスを実装する型は (==) 関数で同値比較できなければならない.

2010/11/17

数値の桁並べ替え

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

なんとなく覚え書き的に.
とりあえず順列.

import Data.List (delete)
perms [] = [[]]
perms xs = [ h:t | h <- xs, t <- perms (delete h xs) ]

perms は, 引数に与えられたリストが空ならば空のリストをだだ一つ持つリストを返す.
引数に与えられたリストが空でないなら, そのリストを xs とする.
xs から順番に要素を取り出し, これを h とする.
xs から h を (あれば) 削除したリストに対して perms を呼び出した結果の各要素を順番に取り出し t とする.
t の先頭に h を付加したもののリストが結果である.

要素三つの場合で評価がどんな風に行われるか手で書いて確認してみます.

perms [2, 3, 5] = [ h:t | h <- [2,3,5], t <- perms (delete h xs) ]
 = [[2:t | t <- perms (delete 2 [2,3,5]) ],
    [3:t | t <- perms (delete 3 [2,3,5]) ],
    [5:t | t <- perms (delete 5 [2,3,5]) ]]
 = [[2:t | t <- perms [3,5]],
    [3:t | t <- perms [2,5]],
    [5:t | t <- perms [2,3]]]

ところで,

perms [3,5] = [ h:t | h <- [3,5], t <- perms (delete h xs) ]
 = [[3:t | t <- perms (delete 3 [3,5])], [5:t | t <- perms (delete 5 [3,5])]]
 = [[3:t | t <- perms 5], [5:t | t <- perms [3]]]

ところで

perms [3] = [3:t | t <- perms (delete 3 [3])]
 = [3:t | t <- perms []]
 = [3:[]]
 = [[3]]

perms [] は [[]] すなわち空のリストをただ一つ含むリストですから, その全ての要素を順に t に代入するということは, t が空のリストの場合だけです. 空リスト [] の先頭に 3 を追加した結果は 3 だけを要素とするリストです.
同様に perms [5] = [[5]] です.
perms [3,5] の評価の続き,

 = [[3:t | t <- perms 5], [5:t | t <- perms [3]]]
 = [3:[5], 5:[3]]
 = [[3,5], [5,3]]

同様に

perms [2,5] = [[2,5], [5,2]]
perms [2,3] = [[2,3], [3,2]]

です.
perms [2,3,5] の評価に戻ると

 = [[2:t | t <- perms [3,5]],
   [3:t | t <- perms [2,5]],
   [5:t | t <- perms [2,3]]]
= [2:[3,5], 2:[5,3], 3:[2,5], 3:[5,2], 5:[2,3], 5:[3,2]]
= [[2,3,5], [2,5,3], [3,2,5], [3,5,2], [5,2,3], [5,3,2]]

ghci で :l して動作確認.

*Main> perms "abcd"
["abcd","abdc","acbd","acdb","adbc","adcb","bacd","badc","bcad","bcda","bdac","bdc
a","cabd","cadb","cbad","cbda","cdab","cdba","dabc","dacb","dbac","dbca","dcab","d
cba"]
*Main> perms [2, 3, 5, 7]
[[2,3,5,7],[2,3,7,5],[2,5,3,7],[2,5,7,3],[2,7,3,5],[2,7,5,3],[3,2,5,7],[3,2,7,5],[
3,5,2,7],[3,5,7,2],[3,7,2,5],[3,7,5,2],[5,2,3,7],[5,2,7,3],[5,3,2,7],[5,3,7,2],[5,
7,2,3],[5,7,3,2],[7,2,3,5],[7,2,5,3],[7,3,2,5],[7,3,5,2],[7,5,2,3],[7,5,3,2]]

いいですね.

数値に対して数値のリストで返したいので

*Main> map (read::String->Int) $ perms $ show 23579
[23579,23597,23759,23795,23957,23975,25379,25397,25739,25793,25937,25973,27359,273
95,27539,27593,27935,27953,29357,29375,29537,29573,29735,29753,32579,32597,32759,3
2795,32957,32975,35279,35297,35729,35792,35927,35972,37259,37295,37529,37592,37925
,37952,39257,39275,39527,39572,39725,39752,52379,52397,52739,52793,52937,52973,532
79,53297,53729,53792,53927,53972,57239,57293,57329,57392,57923,57932,59237,59273,5
9327,59372,59723,59732,72359,72395,72539,72593,72935,72953,73259,73295,73529,73592
,73925,73952,75239,75293,75329,75392,75923,75932,79235,79253,79325,79352,79523,795
32,92357,92375,92537,92573,92735,92753,93257,93275,93527,93572,93725,93752,95237,9
5273,95327,95372,95723,95732,97235,97253,97325,97352,97523,97532]

show で文字列 (文字のリスト) に変換, perms で文字を並べ替えた文字列のリストにして, (read::String->Int) で文字列から Int に変換する操作を map で全ての要素に適用.

追記:
「Haskell 順列」で検索するといろいろ出てくる.

import Data.List
perms [] = [[]]
perms xs = concat [ map (x:) $ perms (delete x xs) | x <- xs]

の方が変数一個少ない.

追記2:
Data.List に permutations が既定義だった…

Prelude> :module Data.List
Prelude Data.List> permutations [2,3,5]
[[2,3,5],[3,2,5],[5,3,2],[3,5,2],[5,2,3],[2,5,3]]

辞書順にならないらしい.

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


2010/09/20

ハノイとバベルの塔 第二階 – 端末出力 C および Haskell

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

移動手順を文字列で出力するプログラムを考えます.

左の杭を A, 真ん中の杭を B, 右の杭を C とします. 小さい順に 1番から n番まで円盤には番号が付いているとします.
A の杭にある全ての円盤を C の杭に移します.

円盤の個数をコマンドライン引数として与えると

move disc #1 from A to C
move disc #2 from A to B
...

のように, 移動するべき円盤の番号と, 移動元および移動先の杭の名称を出力するようにします. 引数が無い場合は円盤の数を 3 とすることにします.

円盤の数を 3 としたときの実行結果は以下のようになります.

move disc #1 from A to C
move disc #2 from A to B
move disc #1 from C to B
move disc #3 from A to C
move disc #1 from B to A
move disc #2 from B to C
move disc #1 from A to C

以下のように考えて実装してみます.
n番以下の全ての円盤を杭srcから杭dstに動かすには, もうひとつの杭を tmp として,

  • n – 1 番以下の全ての円盤を杭src から, 杭tmp に動かす.
  • n番の円盤を杭src から杭dst に動かす.
  • n – 1 番以下の全ての円盤を杭tmp から, 杭dst に動かす.

n – 1番以下の全ての円盤を杭src’ から, 杭dst’ に動かすには, src’ を新しい src として, dst’ を新しい dst として, n – 1 を新しい n として, 上記と同じことをします.
ただし, 新しく n として設定した値が 0であれば, 目的達成です.

C 言語で記述したものです.

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

static void
hanoi(char src, char tmp, char dst, int n)
{
	if (n == 0)
		return;
	hanoi(src, dst, tmp, n - 1);
	printf("move disc #%d from %c to %c\n", n, src, dst);
	hanoi(tmp, src, dst, n - 1);
}

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

以下は Haskell で記述したものです.

import System

format :: (Int, Char, Char) -> String
format (n,s,d) = "move disc #" ++ (show n) ++ " from " ++ [s] ++ " to " ++ [d]

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

main = do args <- getArgs
          let n | (not . null) args = read (head args)::Int
                | otherwise = 3
          putStr $ unlines $ map format $ hanoi 'A' 'B' 'C' n

6〜10行目が, 主要な関数の定義です. C の場合と違い, この関数の中で出力を行うわけではありません.
15行目が, 動作の本体であり, $ を挟んで, 右側から左側に処理されたデータが引き渡されていきます.
hanoi ‘A’ ‘B’ ‘C’ n は [(1, 'A', 'C'), (2, 'A', 'B'), ...] のような, 円盤を動かす手を表すタプルのリストを返します.
map format で ["move disc #1 from A to C", "move disc #2 from A to B", ...] のように, 各タプルを整形,
unlines が “move disc #1 from A to C\nmove disc #2 from A to B\n…” (\n は改行です) のように行の集合に変換,
putStr が出力します.
UNIX のパイプに似ていると思う方もいらっしゃるかもしれません.

このような小さなプログラムでは, 大きな違いとはなりませんが, 出力の方法が, パズルを解く部分に組み込まれていないところが, 分離性が良いですね.


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

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


Copyright © 2010 Yoshinori Kohyama All Rights Reserved.