行列をプリントする

Haskell で [[1,2,3], [4,5,6]] のような行列をプリントするプログラムを作ってみた。最初は普通に行のプリントを繰り返すプログラムにしてみた。
display1.hs

print_row x = putStrLn $ unwords $ map show x
display [] = return ()
display (x:xs) = do
  print_row x
  display xs
  return ()

実行例
Main> display [[1,2,3],[4,5,6]]
1 2 3
4 5 6

これでも動くが、行列を最初に改行を含む文字列にして最後にIOモナドの世界に渡す方式にしてみた。
display2.hs

print_row x = unwords $ map show x
display' [] = ""
display' (x:xs) = print_row x ++ "\n" ++ display' xs
display xs = putStr $ display' xs

少しプログラムが短くなった。しかし、display' (x:xs) は unlines と同じ意味だと気がついたので、unlines を利用したら次のようになった。
display3.hs

display xs = putStr $ unlines $ map (unwords . map show) xs

ひょっとして、Ruby でも同じような記述ができるのではないかと思ったのでやってみた。
irb(main):027:0> def display x
irb(main):028:1>   x.map {|y| y.join(" ")}.join("\n") + "\n"
irb(main):029:1> end
=> nil
irb(main):030:0> print display([[1,2,3],[4,5,6]])
1 2 3
4 5 6
=> nil

表示の全てをIOモナドで記述すると、長くなるし、戻り値がIO型になっているかどうかを気にしなくてはならない。データを通常の関数の世界でこしらえて、それをIOモナドに渡してやる方が記述が簡単になる。また、簡単な再帰的定義の関数は、畳み込みに変更することができる。unlines や unwords は foldr を使った次のような記述でも実現できる。

Hugs> foldr (\x y -> x ++ " " ++ y) "" ["hello", "world"]
"hello world "
Hugs> foldr (\x y -> x ++ "\n" ++ y) "" ["hello", "world"]
"hello\nworld\n"

words や unlines や foldr などの畳み込みを利用すると、再帰的定義の初期条件の記述が省略できるので、プログラムの記述が短くなる。Ruby の join も畳み込みだ。

再帰的定義自体が畳み込みなので、再帰的定義を使ったプログラミングは簡潔になる傾向がある。Rubyにも foldr があれば便利だと思ったら、ここに書いてあった。

module Enumerable
  def foldr(o, m = nil)
    reverse.inject(m) {|m, i| m ? i.send(o, m) : i}
  end

  def foldl(o, m = nil)
    inject(m) {|m, i| m ? m.send(o, i) : i}
  end
end

[1, 2, 3, 4, 5].foldl(:+) #=> 15
[1, 2, 3, 4, 5].foldl(:*) #=> 120

[1, 2, 3, 4, 5].foldr(:-, 0) #=> 3
[1, 2, 3, 4, 5].foldl(:-, 0) #=> -15


今日の Haskell
単語の並べ替え
Hugs> :l List
List> unwords $ sort $ words "this is a pen"
"a is pen this"
[PR]
by tnomura9 | 2010-11-28 01:16 | Haskell | Comments(0)
<< 畳み込み 思考補助電卓としてのHaskell >>