Life Goes On

まあまあだけど楽しんでる方です

鳥たちと戯れる

To Mock a Mockingbird を読んでいます。言わずと知れたコンビネータ論理の教科書(?)です。
そこで算術や論理を関数で表すという、ラムダ計算でよくある話が登場するのですが、自然数の定義が一般的なチャーチ数と違っていて分かりにくかったので、検算用のコードを書きました。
ふつうのチャーチ数では、zero f x = x = KIfx、one f x = f x = Ifx、two f x = f (f x) = SBIfx と定義しますが、zero = I、one = V(KI)I、two = V(KI)(V(KI)I) という定義になってます。
こんな感じ(↓)で遊べます。

Main> toInt $ exp five (multi three two)     
15625
Main> toBool $ greater (multi two five) (multi three three)
True
Main> toInt $ concat (multi three four) three              
123

コンビネータの本なので、下にあげた plus とか greater みたいな再帰関数をコンビネータで(ポイントフリーで)書く方法なんかも載っていて、勉強するにはとてもいい本だと思います。お薦め。
ちょっと確認する程度ならこんなコードで十分だけど、unsafeCoerceも鬱陶しいし、やはりここはちゃんとした処理系の実装に挑戦しようかな。

import Prelude hiding (succ,pred,exp,even,not,and,or,length,concat)
import Unsafe.Coerce

u = unsafeCoerce

-- 事前準備 各種コンビネータ
i x = x
k x y = x
t x y = y x
l x y = x (y (u y))
b x y z = x (y z)
c x y z = x z y
v x y z = z x y
r x y z = y z x
s x y z = x z (y (u z))

-- 論理
true  = k
false = k i
not   = v false true
and   = r false
or    = t true
imply = r true
equiv = c s not

-- 算術
zero   = i
succ   = v false
one    = succ zero
two    = succ one
three  = succ two
four   = succ three
five   = succ four
ten    = multi two five
pred   = t false
isZero = t true

-- 各種関数
plus    m n = isZero m n     $ plus (pred $ u m) (succ $ u n)
multi   m n = isZero m zero  $ plus  n (multi (pred $ u m) n)
exp     m n = isZero n one   $ multi m (u exp m (pred $ u n))
even      n = isZero n true  $ not $ u even $ pred $ u n 
greater m n = isZero m false $ isZero n true $ greater (pred $ u m) (pred $ u n)
length    n = a1 zero n
  where a1 l n = a l n (u l) $ u a1 (succ l) n
        a  l n = greater (exp ten l) n
concat  m n = plus (multi m (exp ten (length $ u n))) n

-- 表示・デバッグ用関数
toBool b = b True False :: Bool
toInt  n = isZero n 0 $ (+) 1 $ toInt $ pred $ u n :: Int

To Mock a Mockingbird

To Mock a Mockingbird

BFS

「人材獲得作戦・4 試験問題ほか」を解こうとしている(続・未完) - IKB: 雑記帖 について、コメント欄に書こうと思ったのですが、長くなったのでこちらに。
この場合、遅延評価云々はあまり関係なくて、元エントリのコードでも解が一つ見つかった時点ですぐに返ってきます。
ただこの迷路は自由度が大きいので、やはり既訪の場所を除外しないと何度も同じところを通ってしまいます。
例えば A : 1→2→3→4、B : 1→2→5→3 というような順序で探索する(2 のノードで分岐する)とき B の方では 3 のノードが既訪だということが分からないので、再探索してしまいます。
これを除外するためには探索の分岐に関わらず既訪のノードを記憶しておく必要があって、以下のコードでは solve の引数を追加しています。
その他、細かいところをちょこちょこいじってますが、ご参考まで。(Maybe にしたら mplus の意味がなくなってしまった)

import Control.Monad

type Position = (Int, Int)
type Board    = [String]
type State    = (Position, Board)

main :: IO ()
main = do
  input <- fmap lines getContents
  let answer = solve [] $ next (startOf input, input)
  putStrLn $ maybe "no route found" unlines answer

startOf :: Board -> Position
startOf input = head
  [(x,y) | (y,cs) <- zip [0..] input, (x,c) <- zip [0..] cs, c=='S']

solve :: [Position] -> [State] -> Maybe Board
solve _ [] = mzero
solve vs ((p@(x,y), b) : queue)
  | symbol=='G'                 = return b `mplus` solve vs queue
  | symbol==' ' && notElem p vs = solve (p:vs) $ queue ++ next (p, b)
  | otherwise                   = solve vs queue
  where symbol = b!!y!!x

next :: State -> [State]
next ((x,y), b) = zip [(x,y-1), (x,y+1), (x-1,y), (x+1,y)] $ repeat b'
  where b' = putAt y (putAt x '$' $ b!!y) b
        putAt n v vs = take n vs ++ v : drop (n+1) vs

リストも関数も同じもの

注:この文章は Haskell 布教のため、 Haskell を知らない人向けに書いています。なので、ちょっとでも分かりにくいところはどんどん指摘してください。

しばらく前に、hasekll-jp のメーリングリストHaskell の型クラスの体系が話題になっていたのですが、そこで衝撃を受けたメールがありました。
http://www.sampou.org/cgi-bin/w3ml.cgi/haskell-jp/msg/463
何に驚いたのかを以下に説明したいと思います。

リストと要素の変更

Haskell にはリストというデータ構造があります。他の言語と同じように特定の種類の要素を複数格納していて、インデックスを指定すると要素を取り出すことができます。

list = [1,5,7,6,2,8,3,0,9,4]  -- 0から1までの整数を格納したリスト
val = list !! 3  -- インデックスを指定して中身を取り出す(この場合は 6 が返る)

また map という関数があって、リストの要素をまとめて変更をすることができます。Ruby なんかにもありますね。

f x = x + 5  -- 引数に 5 を足す関数 f を定義
list' = map f list  -- list の要素それぞれに 5 を足す
val' = list' !! 3 -- 今度は 6 + 5 = 11 が返る

関数と関数の結合

今度は違った例を考えて、引数を 2 倍する関数を定義してみます。

func x = x * 2  -- 引数を 2 倍する関数 func を定義
res = func 3  -- func の引数に 3 を渡す(3 * 2 = 6 が返る)

また Haskell には、複数の関数をつなげて順番に適用する(結合する)関数 (.) があります。

func' = (.) f func  -- func してから f する(2 倍してから 5 を足す)
res = func' 3  -- 3 * 2 + 5 = 11 が返る

Functor と fmap

これまでリストの要素の変更と関数の結合という異なる操作を挙げましたが、実は fmap という関数を使うと、これらは同じように書くことができます。

import Control.Monad.Instances

list' = fmap f list  -- fmap を使って、list の要素を f で変更する
val' = list' !! 3 -- これも 11 が返る
func' = fmap f func  -- fmap を使って、f と func を結合する
res' = func' 3  -- これも 11 が返る

つまり、リストも関数も同じように扱うことができるのです。
なぜこんなことができるかを考えてみます。以下の図のように捉えると、リストも関数も値を格納した箱(コンテナ)であり、インデックスを指定したり引数を与えたりすることで、値を取り出すことができるのだとみなすことができます。

このように何らかの値を格納したコンテナのことを、Haskell では Functor クラスと定義しています。Haskell のクラスは JavaC# でのインタフェースに相当し、必要なメソッド(この場合は fmap)を定義することで、そのクラスのインスタンスとして扱うことができます。
さらに fmap はコンテナの構造を変えずに値を変更する操作だと言えます。僕は関数が Functor クラスとして定義できることを知識としては知っていましたが、関数結合の (.) が fmap で置き換えられることには気づいておらず、とても驚いたのです。
Functor のような型クラスは Applicative、Arrow、モナド など色々あります。実際、モナドは Functor の制約を厳しくしたサブクラスであり、リストに対応するリストモナド、関数に対応する Reader モナドといったインスタンスがあります。
このような抽象化ができると、使う側もこれまでと違った新しい見方ができるようになります。リストと関数を同じように扱える言語なんて、そうそうありません。そこが Haskell の大きな魅力の一つだと思っています。
参考文献:

素因数分解

inamori さんが Project Euler の解説記事素因数分解について触れているのですが、そこで「あまりHaskellっぽくない」と書いてあるのが、気になってしまいました。
Haskell っぽいコードってどんなだろう、やっぱり自前で再帰せずに高階関数を使うのかな、そうするとこの場合は unfoldr だよな。とか考えながら書いたのが下のコード。
n の平方根以上の数では割らないようにするのがちょっと面倒でした。もっとすっきりした書き方があるでしょうか。
ちなみに、2以上のすべての数を素因数の候補にしていますが、もちろん 2と奇数だけでも、きちんとした素数列にしてもいいと思います。(性能差を確認できませんでした)

import Data.List

main = print $ last $ factors 600851475143

factors :: Integer -> [Integer]
factors n = unfoldr phi (n, takeWhile (\p -> p * p < n) [2..])
phi (1, _ ) = Nothing
phi (n, ps) = case dropWhile (\p -> mod n p > 0) ps of
  []        -> Just (n, (1, []))
  ps'@(p:_) -> Just (p, (div n p, ps'))

詳細設計書

ネタ元:詳しすぎる詳細設計書 - SiroKuro Page
自分だったらこんな風に書きたかったりするので、やっぱりまだ詳細設計書が邪魔だと思いました。:p
あ、詳細設計書なんて要らんだろ、という話には激しく同意です。

import Control.Arrow
import Data.List

data Sex = Man | Woman deriving (Eq, Show)
type Height = Double
data Person = P {sex :: Sex, height :: Height}

main = print $ calc [P Man 50, P Woman 40, P Woman 50, P Man 70, P Man 90]

calc :: [Person] -> Height
calc = uncurry (-) . (avg *** avg) . partition ((== Man) . sex)

avg :: [Person] -> Height
avg = uncurry (/) . (sum . map height &&& toEnum . length)

if を追放する

Kコンビネータと真偽値に関する話題が挙がっていたので、それを使って何か書いてみようと思いました。
条件分岐と言えば FizzBuzz。以下のようなプログラムを考えます。

main = mapM putStrLn [ max (show n) $ fizz n ++ buzz n | n <- [1..100] ]
fizz n = if mon n 3 == 0 then "Fizz" else ""
buzz n = if mon n 5 == 0 then "Buzz" else ""

まずは以下のように if' を定義しますが、これだけだとまだあまり変わっていません。
Haskell 標準の Bool 型を使っているし、パターンマッチで結局 if 相当のことをやっているからです。(ポイントフリーで書けない!!)

fizz n = if' (mod n 3 == 0) "Fizz" ""
buzz n = if' (mod n 5 == 0) "Buzz" ""

if' True  = const
if' False = const id

なので、数値型から直接条件分岐することを考えます。リストを使ってこんな感じ。

fizz n = if' (mod n 3) "Fizz" ""
buzz n = if' (mod n 5) "Buzz" ""

if' b = foldr id const $ take b [flip]

foldl を使えば、False = flip const ではなく False = const id で定義することもできますが、型推論が上手くいかないので流行りの Unsafe.Coerce が必要になります。

import Unsafe.Coerce
if' b = foldl (unsafeCoerce id) const $ take b [id]

一般的には 0 が False、0 以外が True かな。そうすると↓のような感じ。

fizz n = if' (mod n 3) "" "Fizz"
buzz n = if' (mod n 5) "" "Buzz"

if' b = foldr id (flip const) $ take b [flip]

うん、とりあえず if や case が無くてもプログラムは組めそう。

ちなみに if-then-else という糖衣構文をやめて、Prelude に if' 関数を追加しようということを主張しているページはこちら。
http://www.haskell.org/haskellwiki/If-then-else

速い素数の求め方

ちょっと前に haskell-cafe で速い素数の求め方が話題になっていました。
どんなもんだか確認した結果を記録。
僕がよく使ってたコードはこんな感じ。改めて見るとツッコミどころ満載です。
106 番目の素数を求めるのに手元のマシンで 2 分以上かかります。

primes0 = 2 : filter isPrime0 [3..]
isPrime0 x = all ((/=0).mod x) $ takeWhile (<= (floor$sqrt$fromIntegral x)) primes0
  • 平方根または二乗を求めるのに、いちいち整数から浮動小数に変換するのはコスト高すぎ
  • 偶数も全てチェック対象にしているのは無駄
  • mod より rem の方が速い(これはなぜ?)

というあたりを修正したのが↓のコード。1 分 20 秒。

primes1 = 2 : 3 : filter isPrime1 [5,7..]
isPrime1 x = all ((/=0).rem x) $ takeWhile (\y -> y*y<=x) primes1

偶数だけでなく 3 の倍数もチェック対象から外したら、というのはよく話題になりますが、あまり速くなりません。たぶんそういう数列(内包表記の部分)を生成するのにコストがかかるのでしょう。1 分 20 秒。

primes2 = 2 : 3 : 5 : filter isPrime [ 6*i+j | i<-[1..], j<-[1,5] ] where
  isPrime x = all ((/=0).rem x) $ takeWhile (\y -> y*y<=x) $ drop 2 primes2

という訳で、アルゴリズムを考え直します。
32 未満の数が素数かどうかをチェックするには、2 で割り切れるかどうかを調べれば十分です。
同様に 52 未満の数が素数かどうかをチェックするには、2, 3 で割り切れるかどうかを調べれば十分です。(5 以上の素数で割ってみる必要はありません)
つまり n 番目の素数の二乗未満までは、最初の素数 n-1 個で割り切れるかを調べればよいわけです。
これを愚直に実装したのが以下のコード。特に何も効率化していませんが、50 秒くらいで終わります。
(以前のコードと同じようなフィルタリングですが、判定の回数が減っています)

primes3 = 2 : sieve 0 [3..] where
  sieve i xs = filter isPrime as ++ sieve (i+1) bs where
    (ps,p:_) = splitAt i primes3
    (as,bs) = span (<p*p) xs
    isPrime x = all ((/=0).rem x) ps

さらに偶数をチェック対象から除くと、40 秒くらいに縮みます。

primes4 = 2 : sieve 0 [3,5..] where
  sieve i xs = filter isPrime as ++ sieve (i+1) bs where
    (ps,p:_) = splitAt i primes4
    (as,bs) = span (<p*p) xs
    isPrime x = all ((/=0).rem x) ps

関数 sieve の引数にリストを渡すよりは、数を渡して内部でリストを生成するほうが、速くなります。
Haskell Wiki素数についてのページがありますが、その IV 番目に相当します。
30 秒。今のところ、これが最終形です。
ちなみに 32bit で収まるなら、primes5 :: [Int] と明示的に宣言した方が速くて 20 秒で終わります。(コンパイル時に Unbox 化されるらしい)

primes5  = 2 : primes' where
  primes' = 3 : sieve 0 5
  sieve i x = filter isPrime [x,x+2..p*p-2] ++ sieve (i+1) (p*p+2) where
    (ps,p:_) = splitAt i primes'
    isPrime x = all ((/=0).rem x) ps

おまけ。Haskell らしさを追及して高階関数で書くと以下のようになります。
人によってはこの方が分かりやすいかもしれません。

primes6 :: [Int]
primes6  = 2 : primes' where
  primes' = 3 : (concat $ snd $ mapAccumL sieve 5 [0..])
  sieve x i = (p*p+2, filter isPrime [x,x+2..p*p-2]) where
    (ps,p:_) = splitAt i primes'
    isPrime x = all ((/=0).rem x) ps

もっと速い求め方があれば、ぜひぜひコメントを。