はてな使ったら負けだと思っている deriving Haskell このページをアンテナに追加 RSSフィード

 | 

2009-04-05

Stern-Brocot 木 を Haskell + Graphviz で

| 20:34 |  Stern-Brocot 木 を Haskell + Graphviz で - はてな使ったら負けだと思っている deriving Haskell を含むブックマーク はてなブックマーク -  Stern-Brocot 木 を Haskell + Graphviz で - はてな使ったら負けだと思っている deriving Haskell

個人的に今週は 世界 Graphviz 週間 なので、Haskell で試してみた。

題材は Stern-Brocot 木。元ネタは結城さんの 既約分数クイズ。[0,1]の有理数を既約分数で全て列挙するアルゴリズム。

まずGraphviz化とか考えずに書いてみたコードが以下。


data Frac = Int :%: Int
instance Show Frac where
  show (a :%: b) = show a ++ "/" ++ show b

(.+.) :: Frac -> Frac -> Frac
(a :%: b) .+. (c :%: d) = (a+c) :%: (b+d)

sternize (x:y:xs) = x:(x .+. y):sternize(y:xs)
sternize xs = xs

stern = iterate sternize [ 0 :%: 1, 1 :%: 1 ]

ちょっと欲が出て型とか定義してみた。演算子とか

まあ素直にタプル使えば当然もっっっと短かくなるだろうけど。


で、Graphviz 版。動作には fgl パッケージ のインストールが必要。

module Main where
import Control.Monad.State
import Data.Graph.Inductive.Graph
import Data.Graph.Inductive.NodeMap
import Data.Graph.Inductive.Graphviz
import Data.Graph.Inductive.Tree
import Data.List (sort)

import System.Environment (getArgs)
import System.Process
import System.IO


type MyGraph = Gr Frac ()

data Frac = Int :%: Int deriving Eq
instance Ord Frac where
  (a1:%:a2) <= (b1:%:b2) =  (a1*b2) <=  (b1*a2)

instance Show Frac where
  show (a :%: b) = show a ++ "/" ++ show b

(.+.) :: Frac -> Frac -> Frac
(a :%: b) .+. (c :%: d) = (a+c) :%: (b+d)

seed :: MyGraph
seed = mkGraph (zip [0..] [0 :%: 1, 1 :%: 1]) []

stern :: NodeMapM Frac () Gr MyGraph
stern = do  (_, gr) <- get
            sub $ sort $ map snd $ labNodes gr
            return gr
  where sub t@(x:y:xs) = do
          (d,n) <- insMapNodeM (x .+. y)
          insMapEdgesM [(x, n, ()), (y, n, ())]
          sub (y:xs)
        sub xs = return ()

toDot gr = graphviz gr "SternBrocotTree" (0,0) (0,0) Portrait

main = do args <- getArgs
          case args of
            (n:_) -> let src = toDot $ iterate (flip run_ stern) seed !! (read n)
                      in putStr src
            _     -> putStrLn "usage: stern [STEP] | dot -T[fmt] > file.fmt"

実行例。

./stern 4 | dot -Tjpg > stern5.jpg

で、こんなんが出来る。

f:id:mr_konn:20090405202540j:image

欲を云えば、上の(0/0)と(1/1)は左右の端の方にレイアウトして左から右に小→大の順番に並んでる様にできたらよかったんだけど、まあ、そこは自動生成だし……と云うことで。

暇があったら調節してみる。


パイプつかわないでやるには main を下記に書き換えれば良い(フォーマットはjpg決め打ちになっちゃうけど)。

main = do args <- getArgs
          case args of
            (r:_) -> do let n = read r
                            src = toDot $ iterate (flip run_ stern) seed !! n
                        (din,dout,_,_) <- runInteractiveCommand "dot -Tjpg"
                        hPutStr din src
                        hClose din
                        jpg <- hGetContents dout
                        writeFile ("stern"++show n++".jpg") jpg
            _     -> putStrLn "usage: stern [STEP]"

これであとは

./stern 4

とやると stern4.jpg ができてるはず。

Haskell から GraphViz の使い方はちょっと癖があるけど、まあその解説は今度と云うことで……。


ところで、GraphViz で 分数を"1/2" じゃなくて "\frac{1}{2}" みたいにちゃんとした数式っぽくタイプセットするにはどうすればいいんだろう?

トラックバック - http://haskell.g.hatena.ne.jp/mr_konn/20090405
 |