250 likes | 395 Views
Purely Functional Data Structures 輪講 第 10 ç« â€œ Data-Structural Bootstrappingâ€. 2006/07/21 ã„ãªã° kinaba@is.s.u-tokyo.ac.jp. 今日ã®å†…容. Data-Structural Bootstrapping ã‚ã‚‹æŠ½è±¡ãƒ‡ãƒ¼ã‚¿æ§‹é€ ã‚’ä½œã‚‹ã¨ãã«ã€å®Ÿè£…ã®ä¸ã§ ãã®æŠ½è±¡ãƒ‡ãƒ¼ã‚¿æ§‹é€ 自身を利用ã™ã‚‹ã“㨠Structural Decomposition 実装ã®ä¸ã§è‡ªåˆ†è‡ªèº«ã‚’å†å¸°çš„ã«ä½¿ç”¨ã™ã‚‹ãƒ‘ターン ( やや微妙㪠) 例: 普通㮠List
E N D
Purely Functional Data Structures 輪講第10章“Data-Structural Bootstrapping” 2006/07/21 いなば kinaba@is.s.u-tokyo.ac.jp
今日の内容 • 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 EP type ‘a seq = Nil | Cons of EP ‘a * ‘a seq len :: Seq a -> Int len 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 tree type ‘a digit = Zero | One of ‘a tree type ‘a rlist = (‘a digit) list • 本質的な違いはない • cons, head, tail, get, set は O(log n) • Non-Uniform Recursion によって • 桁ごとのweightが倍々になっていることを型で保証 • 「次の桁」を得るのが簡単
Exercise 10.2Zeroless表現 • 前回の「Zeroless 表現」も同様にnon-uniform 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_t let 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 はしないで、リストのリストとしてとっておく。 (((f @ (rev r1)) @ (rev r2)) @ (rev r3)) @ .. { (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) な Queue • head, tail, cons ができるのでListとも呼べる • 発想はさっきのBootstrapped Queuesと同じ • すでに既存の ‘a queue (head, tail, snocがamortized 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 Base val 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) b let 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 map end
例Trie • 一般的な Map • key上の順序比較を使ったバランス木による実装 • O( log(Mapのサイズ) ) 回の比較で検索 • リストの比較 • 要素毎の比較を辞書式順序に拡張したものとすることが多い • 最悪 O( リストの長さ ) の時間 • リストをKeyとするMap • 最悪 O( Keyの長さ * log(Mapのサイズ) ) の検索時間
例Trie • 「リストをKeyとする辞書式順によるMap」を 「要素を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 Tries type ‘a tree = E | T of ‘a * ‘a tree * ‘a tree module 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