Purely Functional Data Structures 輪講第 10 章
“ Data-Structural Bootstrapping”
2006/07/21 いなば[email protected]
今日の内容 Data-Structural Bootstrapping
ある抽象データ構造を作るときに、実装の中でその抽象データ構造自身を利用すること
① Structural Decomposition 実装の中で自分自身を再帰的に使用するパターン
( やや微妙な ) 例: 普通の List
② Structural Abstraction 同じインターフェイスを持つ別の実装をラップして
機能を追加したり計算量を改善したりするパターン 例 : ExplicitMin Heap (Ch3):
③ Bootstrapping To Aggregate Types 型 t に関する実装を使って t list などに関する
効率的な実装を作るパターン
① Structural Decomposition
実装の中で自分自身を再帰的に使用Uniform
Non-uniform
type ‘a list = Nil | Cons of ‘a * ‘a list
type ‘a seq = Nil | Cons of ‘a * (‘a*’a) seq
注意事項 : Non-Uniform Recursion(Polymorphic Recursion) このようなデータ型は、 ML の型システム
ではあまりうまく扱えないtype ‘a seq = Nil | Cons of ‘a * (‘a*’a) seq
let rec length s = match s with Nil -> 0 | Cons(_,t) -> 1 + 2*(length t)
File "test.ml", line 7, characters 36-37:This expression has type ('a * 'a) seq but is here used with type 'a seq
注意事項 : Non-Uniform Recursion(Polymorphic Recursion) 方法1 : Coercion
方法2 : Explicit Type Annotation (Haskell)
type ‘a EP = E of ‘a | P of ‘a EP * ‘a EPtype ‘a seq = Nil | Cons of EP ‘a * ‘a seq
len :: Seq a -> Intlen s = case s of Nil -> 0 Cons _ t -> 1 + 2*(len t)
例1Binary Random-Access List Rivisited
さっきの
は実用上はあまり意味がない。(サイズ 2n-1 のリストしか表現できない)
→ Numerical Representation の考え方で→ “ BinRan.hs”
type ‘a seq = Nil | Cons of ‘a * (‘a*’a) seq
例1Binary Random-Access List Rivisited
第9章の RList と何が違うのか?type ‘a tree = Leaf of ‘a | Node of ‘a tree * ‘a treetype ‘a digit = Zero | One of ‘a treetype ‘a rlist = (‘a digit) list本質的な違いはない
cons, head, tail, get, set は O(log n)
Non-Uniform Recursion によって 桁ごとの weight が倍々になっていることを型で保証 「次の桁」を得るのが簡単
Exercise 10.2Zeroless 表現 前回の「 Zeroless 表現」も同様に non-u
niform recursion で実現できるcons, head, tail が amortized O(1)
type ‘a seq = Nil | One of ‘a * (‘a*’a) seq | Two of ‘a * ‘a * (‘a*’a) seq | Three of ‘a * ‘a * ‘a * (‘a*’a) seq
例2Bootstrapped Queues 復習 : Banker’s Queue
type ‘a queue = int * ‘a list lazy_t * int * ‘a list lazy_tlet rotate (fl,f,rl,r) = (fl+rl, f @ (rev r), 0, [])
Banker’s Queue の問題点Append が Left-Associative に使われる
計算量的には問題ないが、実用面では少し遅い
(((f @ (rev r1)) @ (rev r2)) @ (rev r3)) @ ..
例2Bootstrapped Queues どうするか?
実際に append はしないで、リストのリストとしてとっておく。
{ (rev r1), (rev r2), (rev r3), ... }
このリスト↑に適用したい演算は?先頭からの取り出し末尾への追加
(((f @ (rev r1)) @ (rev r2)) @ (rev r3)) @ ..
Queue !!
例2Bootstrapped Queues 実装の概要
type ‘a queue = int * ‘a list lazy_t * (‘a list lazy_t) queue * int * ‘a list lazy_t
let rotate (fl,f,m,rl,r) = (fl+rl, f, snoc m (rev r), 0, [])
let tail (fl,_::[],m,rl,r) = (fl-1, head m, tail m, rl, r)
例2Bootstrapped Queues 計算量
(rev r) の suspension が作られるタイミング・評価されるタイミングは Banker’s Queue と同じ
rev の分に関しては、 Amortized O(1)
snoc,tail は内部 Queue の snoc,tail を呼び出す 内部 Queue の長さは O(log n) よって nest の深さは Hyper-logarithm O(log* n)
snoc, tail は O(log* n) log* (265536) = 5 なので、実質的に定数
② Structural Abstraction
同じインターフェイスを持つ別の実装をラップして機能を追加したり計算量を改善する
Queue をラップして効率的に append できるQueue
Heap をラップして効率的に merge できるHeap
例3Catenable Queues head, tail, cons, snoc, append が Amortized
O(1) な Queuehead, tail, cons ができるので List とも呼べる
発想はさっきの Bootstrapped Queues と同じすでに既存の ‘ a queue (head, tail, snoc が amorti
zed O(1) なもの ) が実装済みと仮定
→ “catQ.ml”
type ‘a cat = E | C of ‘a * ‘a cat queue
例3Catenable Queues 計算量
tail 以外は自明に O(1) tail も頑張って証明すると、償却 O(1)
直感的には、 tail は O(Queue のサイズ ) でQueue のサイズ = それまでに append した回数なので append で将来の tail の分の借金を払えばOK
Persistent に使っても償却 O(1) にしたい時はtype ‘a cat = E | C of ‘a * ‘a cat lazy_t queue
例4Heaps with Efficient Merging Heaps with Efficient Merging
insert, merge, findMin : 最悪 O(1) deleteMin : 最悪 O(log n)
実装済みと仮定する Heap insert : 最悪 O(1) mergin, findMin, deleteMin : O(log n) たとえば
Scheduled Binomial Heap (7.3) Skew Binomial Heap (9.3.2)
例4Heaps with Efficient Merging 発想はやはりさっきと同じ
→ “emheap.ml”
ただし、‘ a emheap 間の比較演算を、「最小要素が小さい方が小さい」と定義
type ‘a emheap = E | H of ‘a * ‘a emheap heap
② Structural Abstraction まとめ (1) 元にするデータ構造
どこか1カ所に関する、効率的な要素追加関数どこか1カ所に関する、効率的な要素取得関数type ‘a Baseval add : ‘a -> ‘a Base -> ‘a Base val get : ‘a Base -> ‘a例
‘a Queue と snoc と head ‘a Heap と insert と findMin
② Structural Abstraction まとめ (2) Bootstrap されたデータ構造
要素追加要素取得
type ‘a BootStrapped = E | B of ‘a * (‘a BootStrapped) Base
let add x b = join (Base.add x Base.empty) blet get (B(x,_)) = x
② Structural Abstraction まとめ (3) Bootstrap されたデータ構造
二つの Bootstrapped 構造を「結合」 Queue の append 、 Heap の merge
let join (B(x1,bs)) b = B(x1, Base.add b bs)
Structural Abstraction によって、このような「結合」演算を効率化できる
③ Bootstrapping To Aggregates
型 t に関する実装を使って t list などに関する効率的な実装を作る例 : t を key とする Map を使って、
t list を key とする Map を作るmodule type Map = sig type ‘a map; type key val empty : ‘a map val lookup: key -> ‘a map -> ‘a val bind : key->‘a->‘a map->‘a mapend
例Trie 一般的な Map
key 上の順序比較を使ったバランス木による実装 O( log(Map のサイズ ) ) 回の比較で検索
リストの比較 要素毎の比較を辞書式順序に拡張したものとするこ
とが多い 最悪 O( リストの長さ ) の時間
リストを Key とする Map 最悪 O( Key の長さ * log(Map のサイズ ) ) の検索時
間
例Trie 「リストを Key とする辞書式順による M
ap 」を 「要素を Key とする Map 」 から Bootstrap→ “trie.ml”module Trie(B : Map) : Map = struct type key = B.key list type ‘a map = Trie of ‘a option * (‘a map) B.map ...
おはなしGeneralized Tries Trie の考え方は、
リストに限らず一般の product と sum による型に適用できるα MapFrom_β×γ = (α MapFrom_β) MapFrom_γ
α MapFrom_β + γ = (α MapFrom_β) × (α MapFrom_γ)
おはなしGeneralized Triestype ‘a tree = E | T of ‘a * ‘a tree * ‘a treemodule TrieOfTree(B:Map) : Map = struct type key = B.key tree type ‘a map = Some ‘a * ‘a map map B.map
let rec lookup t mp = match t, mp with | E, (None, _) -> raise Not_found | E, (Some v,_) -> v | T(e,a,b) (_,m) -> lookup b (lookup a (B.lookup e m))end