2021-06-05 Sat. — リストの操作、クイックソート
勉強
プログラミング in OCaml (–§5.4 Case Study: 整列アルゴリズム)
畳み込み関数fold
何やってるのかわからないのでトレースする。
let rec fold_right f l e = match l with [] -> e | x :: rest -> f x (fold_right f rest e);;
fold_right ( + ) [3; 4; 5] 0 (* [3; 4; 5] match with 3 :: [4; 5] *) --> ( + ) 3 (fold_right ( + ) [4; 5] 0) (* [4; 5] match with 4 :: [5] *) --> ( + ) 3 ( ( + ) 4 (fold_right) ( + ) [5] 0) (* [5] match with 5 :: [] *) --> ( + ) 3 ( ( + ) 4 ( ( + ) 5 (fold_right ( + ) [] 0))) (* [] match with [] *) --> ( + ) 3 ( ( + ) 4 ( ( + ) 5 ( 0 ) ) ) --> ( + ) 3 ( ( + ) 4 5) (* 一番右から ( + ) されていく *) --> ( + ) 3 9 --> 12
もう1個わかんないやつ
let rec nth n l = match with (n, l) (1, a :: _) -> a | (_, _ :: rest) -> nth (n - 1) rest;;
nth 3 [3; 1; 4; 1] (* match with (_, _ :: [1; 4; 1]) *) --> nth 2 [1; 4; 1] (* match with (_, _ :: [4; 1]) *) --> nth 1 [4; 1] (* match with (1, 4 :: [1]) *) --> 4
書いたら動作はわかるけど、自分でこの再帰関数を定義するの難しそう。
§5.4 Case Study: 整列アルゴリズム
ここで生成している擬似乱数列(一見ランダムに並んでいるように見える数列)は、乗算合同法(Lehmer乱数生成)というやり方。
https://en.wikipedia.org/wiki/Lehmer_random_number_generator
に載っている。
漸化式で定義される数列で、
と定義される。これを並べるとランダムに見える数列ができる。数列中に同じ数が出てくると以下ループする。うまいこと初期値(シードとも呼ぶ)と と を決めれば長いことループしない手頃な擬似乱数列ができる。
( ) 、 ( ) とするとうまいこといくらしい。
let nextrand seed = let a = 16807. and m = 2147483647. in let t = a *. seed in t -. m *. floor (t /. m);;
で漸化式を定義して、
let rec randlist n seed tail = if n = 0 then (seed, tail) else randlist (n - 1) (nextrand seed) (seed::tail);;
で、 項目までの擬似乱数列のリストを返す。
クイックソート
リストを適当な要素(pivot
とする)を基準に2つに分ける関数partition
を考える。
素朴にpartition
するとき:
let rec partition pivot = function [] -> ([], []) | y :: rest -> let (left, right) = partition pivot rest in if pivot < y then (left, y::right) else (y::left, right);;
これだと末尾再帰的ではない。
これを末尾再帰的にするために、分割の途中までの結果をストアしておく変数を明示的に与えよう。
let rec partition left right pivot = function (* 末尾再帰的 *) [] -> (left, right) | y :: rest -> if pivot < y then partition left (y::right) pivot rest else partition (y::left) right pivot rest;;
then``else
以下、最後に再帰を呼び出せるようになった。
let rec quick_sort = function [] -> [] | pivot :: rest -> let (left, right) = partition [] [] pivot rest in quick_sort left @ (pivot :: quick_sort right);;
ここで、partition
を、quick_sort
のpivot
の有効範囲内で局所関数として組み込んじゃう。
let rec quick_sort = function [] -> [] | pivot :: rest -> (* ここで partition を定義する *) let rec partition left right = function (* pivot はもう渡されているので定義には不要 *) [] -> (left, right) | y :: ys -> (* rest は既使用なので ys にする *) if pivot < y then partition left (y::right) ys (* ここにも pivot 不要 *) else partition (y::left) right ys (* partition の局所定義終了 *) in let (left, right) = partition [] [] rest in quick_sort left @ (pivot :: quick_sort right);;
これでもいいのだが、最後にまだ工夫する。
let rec quick_sort = function ( [] | [_] ) as l -> l (* 1要素のときも自明な変形なので特別扱いする。後述 *) | pivot :: rest -> let rec partition left right = function [] -> (quick_sort left) @ (pivot :: quick_sort right) (* 呼び出したらすぐくっつけるので、その処理を partition の中に組み込む *) | y :: ys -> if pivot < y then partition left (y::right) ys else partition (y::left) right ys in partition [] [] rest;;
- この関数の冒頭で( [] | [_] ) と書いてあるが、これは複数のパターンを
|
で区切って並べる or パターン。|
で繋がれている各パターンで宣言されている変数は同じでなくてはならない(( [] | [x] )
とかはダメ)。
パターン as 変数
は as パターン。内側のパターンがマッチしたときのみas
パターン全体がマッチする。そして変数
を内側のパターンにマッチした値に束縛する。- この関数では、
l
が長さ 1 以下のリスト全体に束縛される。
- この関数では、
Tips
- シェルでは
clear
コマンドで画面を一掃して一番上から書き始められる。 OCamlではSys.command "clear";;
。 - 「コンテキスト」の意味 https://stackoverflow.com/questions/6145091/the-term-context-in-programming
日記
断酒25日目。
今日は甥っ子の世話をしていた。昼寝をした。
関数型プログラミングを学び始めて、見えている地平が少しずつ広がっていく。
明日やりたいこと
- プログラミング in OCaml 5章の練習問題
- RDF問い合わせ言語 SPARQL を少し調査。https://www.w3.org/TR/rdf-sparql-query/
- WordPressレッスンブック