2017年10月31日火曜日
2017年10月30日月曜日
手続き的なscanlの説明
手続き的なscanlの説明
foldの動作をxへの代入、再代入と捉えると分かりやすくなります。
直接foldの動きを見るのではなく、
まずscanlの動作を見てみます。
scanlの型は
Prelude> :t scanl
scanl :: (a -> b -> a) -> a -> [b] -> [a]
となっています。
scanlは、2引数の関数と初期値と何らかのリストを引数にとり、リストを返す関数です。
実際に動かしてみましょう。
Prelude> scanl(\x y -> x + y) 0 [1..3]
[0,1,3,6]
scanlが内部で何をやっているのか想像してみます。
まずscanlが呼び出されたとき、変数xに初期値、(この場合は「0」)を代入します。
次に変数yにリストの最初の要素(この場合は「1」)を代入します。
次にx+yを計算して結果の「1」をxに再代入します。
次にyにリストの次の要素「2」を代入し、x+yを計算します。この時xは1になっていますので, 結果は「3」です。これをxに再代入します。
次にyにリストの次の要素「3」を代入し、x+yを計算します。この時xは3になっていますので,結果は「6」です。これをxに再代入します。
リストの終わりに到達したので、リスト[0,1,3,6]を出力して終了します。
scanlはxに値が代入された時はすべてその値をリストに保存するようです。
こう見ますとfoldlはscanlと動作は同じで終了時にxの値のみを出力して終了する、と捉えることができます。
Prelude> :t foldl
foldl :: (a -> b -> a) -> a -> [b] -> a
foldlの型です。
同様にscanrの動作もみてみます。
scanrはxとyの役割が逆転しており、リストの作り方も異なっています。
Prelude> scanr (\x y -> x / y) 2 [3..5]
[1.875,1.6,2.5,2.0]
pythonで動きを追ってみると
>>> x=5.
>>> y=x/y
>>> y
2.5
>>> x=4
>>> y=x/y
>>> y
1.6
>>> x=3
>>> y=x/y
>>> y
1.875
代入、再代入はyに対しておこなわれます。
yに代入があった時、リストの先頭に値を保存します。
Prelude> 1.875:1.6:2.5:2.0:[]
[1.875,1.6,2.5,2.0]
haskellはソースコード上での、変数への値の再代入を許していません。
そのため繰り返しのための構文が用意されていません。
それが可能な言語、例えばPythonでは次のようなコードが書けます。
x=0
for y in [1,2,3]:
x=x+y
print x
これは上記の説明のfoldと全く同じです。
つまりfoldは一般的な言語の繰り返し処理をHaskellで書くための一つの方法になっているわけです。
(PythonのFor文は他の凡百の言語のFor文とはまったく異なります。どちらかというとMapに近い。ただ書き換えのできるグローバル変数も使えるので非常に自由度が高い。)
fold を手続き的にとらえると考えやすくなる処理があります。
Data.List モジュールには insert という関数が用意されています。
Prelude Data.List> :t insert
insert :: Ord a => a -> [a] -> [a]
ソート済みのリストに一つ要素を加えてソート順を崩さずに新たなリストを返す関数です。
他の関数が一般的なリスト処理の関数なのに比べてやや特殊な感じがあります。
たぶん「挿入ソートを実装してください」というメッセージだと思います。
挿入ソートは再起でも書けるとは思うのですが、いきなり fold を使ったほうがわかりやすい。
Prelude Data.List> foldl (\x y -> insert y x) [] [2,7,4,0]
[0,2,4,7]
まず初期値は空リストです。
x に 代入されます。
y には リストの第一要素が代入されます。
x y をひっくり返して insert 関数を適用します。
結果 x に [2] が y に 7 が代入されます。
7 を [2] に insert し [2,7] ・・・・(略)
この操作をリストの終わりまで繰り返します。 これでソート完了。
flip を使って書き直すと
Prelude Data.List> foldl (flip insert) [] [2,7,4,0]
[0,2,4,7]
さらに
Prelude Data.List> foldr insert [] [2,7,4,0]
[0,2,4,7]
foldl と foldr では動作が異なりますが、この場合は結果は同じです。
も参照ください。
フィボナッチ数
f6 の実行例
*Main> last $ take 10 f6
[55,34,21,13,8,5,3,2,1,1,0]
これは
foldl (\x@(a:b:_) _ -> (a+b):x) [1,0] [0..8]
と同じです。
フィボナッチ数列の隣り合う 2 項の比は黄金比に収束Wします。
$ runghc fibo2.hs | runghc gr.hs
~
~
~
1.618033988749895
1.618033988749895
1.618033988749895
~
~
~
Ctrl-Cを押して終了。
2017年10月28日土曜日
シェルピンスキーのギャスケット
まずパスカルの三角形をつくります。
$ runghc -XParallelListComp pascal.hs 3
[1]
[1,1]
[1,2,1]
[1,3,3,1]
[1,4,6,4,1]
[1,5,10,10,5,1]
[1,6,15,20,15,6,1]
[1,7,21,35,35,21,7,1]
奇数部を1、偶数部を0に置き換えます。
$ runghc -XParallelListComp sierpinski.hs 10 | display
描画には10数秒かかります。
コンパイルすればほぼ一瞬で描画するようになります。
http://senjounosantafe.blogspot.com/2019/02/blog-post.html
上記のページに
Python のコードを出力し
Turtle グラフィックスで
シェルピンスキーのギャスケットを再帰的に描画するHaskellのコードを置いています。
また1次元のセル・オートマトンにルール90を適用することでもシェルピンスキーのギャスケットを描画できるそうです。
実行例
$ runghc ca.hs
main を
main = loop0 fs 31 $ reverse "11100001"
と書き換えるとルール225を描画します。
Intから2進数の文字列に変換するには
「2進、8進、10進、16進の各表現を相互に変換する」のページ
https://github.com/haskell-jp/recipe-collection/blob/master/%E6%95%B0%E5%80%A4/2%E9%80%B2%E3%80%818%E9%80%B2%E3%80%8110%E9%80%B2%E3%80%8116%E9%80%B2%E3%81%AE%E5%90%84%E8%A1%A8%E7%8F%BE%E3%82%92%E7%9B%B8%E4%BA%92%E3%81%AB%E5%A4%89%E6%8F%9B%E3%81%99%E3%82%8B.md
を参考に。
具体的には
ghci> import Numeric
ghci> import Data.Char
ghci> showIntAtBase 2 intToDigit 123 "" -- 123を2進数表記
"1111011"
ただし8桁になるように先頭にゼロを埋めること。
"1111011" -> "01111011"
参考ページ
Elementary Cellular Automata
http://atlas.wolfram.com/01/01/
その他のルールも適用可能にし精細な画像を描くにはこちら。
http://hhg2the-haskell.blogspot.com/2019/02/blog-post.html
マンデルブロ集合
pbmファイルに書き出してマンデルブロ集合を描きます。(画像サイズは1000x1000に固定しています。)
コンパイルします。
$ ghc -O2 -threaded -rtsopts --make -XFlexibleContexts -eventlog mandel.hs
$ ./mande -2 2 2 3000 +RTS -N2 -l | display
displayコマンドにパイプします。
動作を確認。
$ threadscope mamdel.eventlog
殆どの時間2Coreで動いています。
Data.Array.Repa.IO.BMP
モジュールには
readImageFromBMP :: FilePath -> IO (Either Error (Array U DIM2 (Word8, Word8, Word8)))
writeImageToBMP :: FilePath -> Array U DIM2 (Word8, Word8, Word8) -> IO ()
という関数がありBMPファイルの読み書きができる。
画像サイズもコマンドラインから与えます。
$ ./mandel2 -2 2 2 1000 3000 +RTS -N2 -l
1次元の配列に対し map し、 reshape すればよいので、コード自体はわかりやすい。
index のTable (Int のタプルの配列)を作るのに時間がかかっているようなので、
配列を連結するやり方も試みた。
また mandel2.hsにおいてIndexをIntではなくWord16に置き換えてみたが効果は微妙だった。
世の中にはGPUを使ってマンデルブロー集合を極めて高速に描画するプログラムもあるらしい。
コンパイルします。
$ ghc -O2 -threaded -rtsopts --make -XFlexibleContexts -eventlog mandel.hs
$ ./mande -2 2 2 3000 +RTS -N2 -l | display
displayコマンドにパイプします。
動作を確認。
$ threadscope mamdel.eventlog
殆どの時間2Coreで動いています。
Data.Array.Repa.IO.BMP
モジュールには
readImageFromBMP :: FilePath -> IO (Either Error (Array U DIM2 (Word8, Word8, Word8)))
writeImageToBMP :: FilePath -> Array U DIM2 (Word8, Word8, Word8) -> IO ()
という関数がありBMPファイルの読み書きができる。
画像サイズもコマンドラインから与えます。
$ ./mandel2 -2 2 2 1000 3000 +RTS -N2 -l
index のTable (Int のタプルの配列)を作るのに時間がかかっているようなので、
配列を連結するやり方も試みた。
また mandel2.hsにおいてIndexをIntではなくWord16に置き換えてみたが効果は微妙だった。
世の中にはGPUを使ってマンデルブロー集合を極めて高速に描画するプログラムもあるらしい。
2017年10月27日金曜日
画像ビューアー
この画像(IMGP2850.JPG)を表示します。
ただこのやり方だと元画像の大きさそのままで表示されます。
サイズを変更して表示するには一旦Pixbufのインスタンスをつくってから
Buttonに貼り付けます。
かんたんな画像のビューアーをつくります。 まずディレクトリーを選択するダイアログ。
dirというファイルにリダイレクトしておきます。
$ runghc dirchooser.hs > dir
ビューアー
ただ表示するだけではあまりにも芸がないのでちょっとした操作をしてみます。
一括処理を選択します。
画像の回転指定
確認
MyTable モジュール
最後にConvertコマンドを実行します。
スクリプトは
runghc dirChooser.hs > dir
runghc select.hs > tmp
cat tmp | runghc image.hs > tmp1
cat tmp1 | runghc confirm.hs > tmp2
cat tmp2 | runghc mkCom.hs
gtk2hs
gtk2hsのインストールは
sudo apt install libghc-gtk-dev
先にGHCを単独でインストールしてしまうとgtk2hsを加えるときに非常に時間がかかります。
Gtk2hsを使う予定がなくてもいきなり入れてしまったほうが良い。
もちろんこれでGHCも使えるようになります。
Gtk2hsを使う予定がなくてもいきなり入れてしまったほうが良い。
もちろんこれでGHCも使えるようになります。
このコードを実行してウィンドウが表示されればインストールは成功です。
2017年10月26日木曜日
ハノイの塔
ハノイの塔の由来や、ルール、解き方のアルゴリズムは省略します。
hanoi.hs
実質3行のプログラムです。
haskellが提供する再帰とdo記法で極めて簡潔に書くことができます。
実行例
$ runghc hanoi.hs 3
A-B
A-C
B-C
A-B
C-A
C-B
A-B
最短の解き手順を表示します。
円盤3枚の場合です。
A-Bというのは塔Aの一番上の円盤を塔Bに移動するという意味です。
GUI版
$ runghc hanoi.hs 3 | runghc guihanoi.hs 3 100
labelhanoi.hsへの第2引数はタイミングです。ミリ秒で指定します。
同じ引数を渡すのは面倒なのでhanoi という名前でスクリプトを書いておきます。
runghc hanoi.hs $1 | runghc guihanoi.hs $1 $2
$ ./hanoi 9 500
コンソール版
スクリプト
runghc hanoi.hs $1 | runghc -XFlexibleContexts doHanoi.hs $1
実行例
./dohanoi 64
こちらも参照ください。
πの計算
ライプニッツの公式
実行結果
$ runghc l.hs
3.140592653839793
マチンの公式によるπ
参考にしたページ
http://handasse.blogspot.com/2011/12/1.html
Parallel版
1万桁では差がでないので
10万桁ほど計算します。
コンパイルします。
ghc --make -O2 pm.hs -rtsopts -threaded -eventlog
CPUはCore2Duo。デュアルコアなので -N2 オプションで実行します。
$./pm +RTS -N2 -l
threadscope で動作を確認します。
$ threadscope pm.eventlog
spigot アルゴリズムをLoopで書いたコード
kk62526 さんが10進Basicで書いたコードをそのまま移植しています。
グローバル変数 「i(注)」に現在何桁目を計算しているかが保持されてますので(ただし+2された数字)
(注: 本当は無名ですが、こう捉えたほうが分かりやすいです。)
if (i-2) `mod` 100 == 0 then ・・・・・・
のようなコードを挿めば100桁ごとに区切ったりできます。
実行結果
$ runghc p.hs > a
$ less a
3 1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679()
100 8214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196()
200 4428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273()
300
~
~
~
(略)
ちなみに10000桁を計算し終えるのに2秒少々でした。
ギボンズ博士が spigot アルゴリズムに対し
"not competitive with state-of-the-art arithmetic-geometric mean algorithms"
と言うとおり決して高速とはいえませんが、いきなり1桁目から表示を始めますので、
体感的には超高速です。
(注:10000桁の計算では8万数千桁のIntegerが3つ、作られます。)
ギボンズ博士のspigot アルゴリズムは
https://www.cs.ox.ac.uk/jeremy.gibbons/publications/spigot.pdf
コードだけ紹介しますと
実行結果
$ runghc l.hs
3.140592653839793
マチンの公式によるπ
参考にしたページ
http://handasse.blogspot.com/2011/12/1.html
Parallel版
1万桁では差がでないので
10万桁ほど計算します。
コンパイルします。
ghc --make -O2 pm.hs -rtsopts -threaded -eventlog
$./pm +RTS -N2 -l
threadscope で動作を確認します。
$ threadscope pm.eventlog
僅かですが速くなっています。
並列計算を効率よく利用するには最初から並列化を意識してコードを書く必要があるようです。
spigot アルゴリズムをLoopで書いたコード
kk62526 さんが10進Basicで書いたコードをそのまま移植しています。
グローバル変数 「i(注)」に現在何桁目を計算しているかが保持されてますので(ただし+2された数字)
(注: 本当は無名ですが、こう捉えたほうが分かりやすいです。)
if (i-2) `mod` 100 == 0 then ・・・・・・
のようなコードを挿めば100桁ごとに区切ったりできます。
実行結果
$ runghc p.hs > a
$ less a
3 1415926535897932384626433832795028841971693993751058209749445923078164062862089986280348253421170679()
100 8214808651328230664709384460955058223172535940812848111745028410270193852110555964462294895493038196()
200 4428810975665933446128475648233786783165271201909145648566923460348610454326648213393607260249141273()
300
~
~
~
(略)
ちなみに10000桁を計算し終えるのに2秒少々でした。
ギボンズ博士が spigot アルゴリズムに対し
"not competitive with state-of-the-art arithmetic-geometric mean algorithms"
と言うとおり決して高速とはいえませんが、いきなり1桁目から表示を始めますので、
体感的には超高速です。
(注:10000桁の計算では8万数千桁のIntegerが3つ、作られます。)
ギボンズ博士のspigot アルゴリズムは
https://www.cs.ox.ac.uk/jeremy.gibbons/publications/spigot.pdf
コードだけ紹介しますと
平方根、立方根
2の平方根
$ runghc nroot.hs 2 2 | tee a
2の立方根
$ runghc nroot.hs 2 3 | tee b
猛列な勢いで計算しますので適当なファイルに保存もしています。(Ctrl-Cで停止)
なお√2に限ればこう書けます。
One liner:
loop a b n = return (a*2+1) >>= \c -> if c<b then loop (a+1)(b-c)(n+1) else putStr (show n) >> loop (a*10)(b*100)0; main = loop 1 1 1
平方根は正方形の色紙を百分割して並べることで、
立方根は正四角形のサイコロを千分割して貼り付けていくことで深い桁まで計算をすすめることができます。
参照ページ
http://senjounosantafe.blogspot.com/2018/05/2.html
xgamma
画面が明る過ぎるとき、いちいちモニターを調節するのは面倒。
そんなときはガンマ調整で済まします。
ディストリビューションによっては
xbrightness-gamma
というプログラムが用意されています。
またxgamma コマンドでも可能。
% xgamma -gamma 0.3
などとします。
Haskellでフロントエンドを作っておきます。
スポーク長計算
https://github.com/index333/spcal
からソースがDLできます。
新バージョンは
https://github.com/index333/spcal2
各寸法の測り方は
https://www.sheldonbrown.com/spoke-length.html
を参考にしてください。
まずサンプルファイルを作っておきます。
426.0
45.0
37.3
45.0
20.7
次のスクリプトを実行します。
$ cat sample | runghc spcal.hs
MySpinBox モジュール Round モジュールも必要です。
Failed to load module “canberra-gtk-module” のようなエラーメッセージがでるかも知れません。
無視してもかまいませんが
libcanberra-gtk-module
をインストールすると解消します。
sudo apt-get install libcanberra-gtk-module
スポーク長の計算式は
aはErdの2分の1、bはpcdの2分の1、dはフランジとセンター間の距離。 hはホール数、kは組数です。
Haskellでは
calLen a b d k h = sqrt ((a^2+b^2+d^2) - (2*a*b*(cos $ alfa h k)))
where alfa h k = rad (360 / h * k)
この計算は同心円上の2点間の距離の問題に還元できます。このような計算は複素数で行うととても楽になります。
ハブ軸を原点に、交差する2本のスポークの交点を複素平面上の実数軸にあわせます。
(32ホール、6本組)
(見やすいように、ハブは実際より大きく描いています。)
スポークの両端をハブ側をA点,リム側をB点としますと
360度➗(リムの穴数➗2) x (組数ー1)➗2
32ホール、6本組の場合、22.5度×2.5=56.25度
絶対値(原点までの距離)はハブのPCDの半分
点Bの位相は組み方に関係なく
360度➗リムの穴数×(−1)
32ホール、6本組の場合、ー11.25度
絶対値はリムのERDの半分。
これにハブの厚みとスポークの長さ表示方法を考慮して関数化すると
dist :: RealFloat a => Complex a -> Complex a -> a -> a
dist x y z = sqrt (l * l + z * z) where l = magnitude (x - y)
calLen a b d k h = dist x y d - hosei
where x = mkPolar b (yen / (h / 2) * (k - 1) / 2)
y = mkPolar a (yen / h * (-1))
yen = pi * 2
なおラジアル組みの場合、簡易な計算で求められますが、
たまたま位相が一致する特殊なケースとして一般化できます。
Javaなどのオブジェクト指向言語ではデータをオブジェクトのまま保存します。
Haskellではdata型をShowして保存し、読み込んでReadすると元のdata型に戻してくれます。
このやり方で新しく書き換えました。
$ runghc menu.hs
もう一度ソースの置き場所を・・・
https://github.com/index333/spcal2
なおリムやハブのデータはテストのための仮データです。
同じような画面構成の小さなプログラムを復数書かなければいけないとき、継承が恋しくなる。
一応テストはしてますが他のサイトでも検算して下さい。
http://spoke.gzmapss.com/
が使いやすいです。
後記:
しばらくたってから実際に使ってみると、復数開くウインドウの閉じ方に難があることがわかりました。
(^_^;)
実用上は問題ないのですが・・・
Guiのプログラムを復数立ち上げるというのは独立したスレッドを生むことになるので制御がむつかしいケースが生じます。
気になる場合は、
runghc mkRim.hs
runghc selector.hs
runghc selectRim.hs
runghc selectHub.hs
runghc spcal.hs
のようなスクリプトを作ります。
stern-brocot 木
stern-brocot 木
Stern-Brocot 木の「親」の値は数直線の区間です。
頂点の親「根」の値は
0〜∞
左端が0で、右は無限大です。
それを分数で0/1、1/0とそれぞれ表します。
さらにHaskellではタプルで
(0,1) ,(1,0)
と表します。
つまりHaskellでStern-Brocot 木の根はタプルのタプルで
((0,1) ,(1,0))
と表せます。
親はいつでも自動で左の子、右の子を生成することができます。
Haskellで書くとこのような関数になります。
sb ((x,y),(z,w)) = (a,b) where a = ((x,y),c) ; b = (c,(z,w)); c = (z+x,y+w)
aが左の子bが右の子です。左の子の右端と右の子の左端は同じです。分割点になります。
main = do
let a = (0,1)
let b = (1,0)
let r = (a,b)
print r
print $ sb r
((0,1),(1,0))
(((0,1),(1,1)),((1,1),(1,0)))
数直線の区間
0〜∞
を
0〜1
と
1〜∞
の2つの区間に分割したのと同じです。
もう一世代増やしてみましょう。
main = do
let a = (0,1)
let b = (1,0)
let r = (a,b)
let (a,b) = sb r
print $ sb a
print $ sb b
(((0,1),(1,2)),((1,2),(1,1)))
(((1,1),(2,1)),((2,1),(1,0)))
これは
数直線の区間
0〜1
を
0〜0.5
と
0.5〜1
に
1〜∞
を
1〜2
と
2〜∞
に分割したのと同じです。
これを繰り返せば数直線を無限に分割できます。
Stern-Brocot 木の木構造としての性質等は
Spaghetti Source - Stern-Brocot 木
http://www.prefield.com/algorithm/math/stern_brocot_tree.html
を参照してください。
さてStern-Brocot木を2分探索してゆけば全ての少数を分数に変換できるのですが、
そのためにはStern-Brocot木を前もって作成しておく必要はありません。
与えられた少数と分割点とを比較しながら片側の子のみを辿ってゆけばよい。
この辺はコードにしたほうがわかりやすい。
実行結果
245850922 % 78256779
Stern-Brocot 木の「親」の値は数直線の区間です。
頂点の親「根」の値は
0〜∞
左端が0で、右は無限大です。
それを分数で0/1、1/0とそれぞれ表します。
さらにHaskellではタプルで
(0,1) ,(1,0)
と表します。
つまりHaskellでStern-Brocot 木の根はタプルのタプルで
((0,1) ,(1,0))
と表せます。
親はいつでも自動で左の子、右の子を生成することができます。
Haskellで書くとこのような関数になります。
sb ((x,y),(z,w)) = (a,b) where a = ((x,y),c) ; b = (c,(z,w)); c = (z+x,y+w)
aが左の子bが右の子です。左の子の右端と右の子の左端は同じです。分割点になります。
main = do
let a = (0,1)
let b = (1,0)
let r = (a,b)
print r
print $ sb r
((0,1),(1,0))
(((0,1),(1,1)),((1,1),(1,0)))
数直線の区間
0〜∞
を
0〜1
と
1〜∞
の2つの区間に分割したのと同じです。
もう一世代増やしてみましょう。
main = do
let a = (0,1)
let b = (1,0)
let r = (a,b)
let (a,b) = sb r
print $ sb a
print $ sb b
(((0,1),(1,2)),((1,2),(1,1)))
(((1,1),(2,1)),((2,1),(1,0)))
これは
数直線の区間
0〜1
を
0〜0.5
と
0.5〜1
に
1〜∞
を
1〜2
と
2〜∞
に分割したのと同じです。
これを繰り返せば数直線を無限に分割できます。
Stern-Brocot 木の木構造としての性質等は
Spaghetti Source - Stern-Brocot 木
http://www.prefield.com/algorithm/math/stern_brocot_tree.html
を参照してください。
さてStern-Brocot木を2分探索してゆけば全ての少数を分数に変換できるのですが、
そのためにはStern-Brocot木を前もって作成しておく必要はありません。
与えられた少数と分割点とを比較しながら片側の子のみを辿ってゆけばよい。
この辺はコードにしたほうがわかりやすい。
実行結果
245850922 % 78256779
登録:
投稿 (Atom)
Haskell Process
Haskellの System.Processは便利ですが、問題もあります。 単一スレッドでの逐次処理を保証していない。(想像です。) 次のようなスクリプトを書いてみた。 --a.hs main = print [1..10] --t.hs import Sy...
-
ソースコードは下記アドレス https://github.com/index333/cellA 1次元のセル・オートマトンにルール30を適用して得た画像。 一部Repaを使ってみました。 以下のインストール が必要。 $ cab...
-
https://github.com/index333/spcal からソースがDLできます。 新バージョンは https://github.com/index333/spcal2 各寸法の測り方は https://www.sheldonbrown.com/...
-
Pythonは複素数を直接書くことができます。 Python 2.7.12 (default, Dec 4 2017, 14:50:18) [GCC 5.4.0 20160609] on linux2 Type "help", "copyr...