20分でわかる Purely Functional Data Structures

Slides:



Advertisements
Similar presentations
Generic programming と STL
Advertisements

プログラミング演習II 2004年11月 30日(第6回) 理学部数学科・木村巌.
アルゴリズムとデータ構造 2012年6月27日
プログラミング言語としてのR 情報知能学科 白井 英俊.
第11回 整列 ~ シェルソート,クイックソート ~
アルゴリズムとデータ構造 2013年6月18日
基本情報技術概論(第4回) 埼玉大学 理工学研究科 堀山 貴史
第10回 ソート(1):単純なソートアルゴリズム
アルゴリズムとデータ構造 2012年6月14日
情報工学概論 (アルゴリズムとデータ構造)
全体ミーティング (6/13) 村田雅之.
アルゴリズムとデータ構造 第3回基本的なデータ構造(リスト) 2015/10/28 アルゴリズムとデータ構造 2015.
C言語講座 第4回 ポインタ.
2006/10/19 山下 諒蔵 佐藤 春旗 前田 俊行 大山 恵弘 佐藤 秀明 住井英二郎
岩井 儀雄 コンピュータ基礎演習  ー探索、整列ー 岩井 儀雄
プログラミング論 II 電卓,逆ポーランド記法電卓
データ構造とアルゴリズム 第13回 スタックとキュー
Stack & Queue & List 3.
INSERT(x,p,L)の例 (一部) 磯 直行 2009年5月5日
データ構造と アルゴリズム 第八回 知能情報学部 新田直也.
プログラミング演習Ⅰ 課題2 10進数と2進数 2回目.
ML 演習 第 7 回 新井淳也、中村宇佑、前田俊行 2011/05/31.
アルゴリズムとデータ構造 2011年6月14日
データ構造と アルゴリズム 第十一回 理工学部 情報システム工学科 新田直也.
データ構造と アルゴリズム 第四回 知能情報学部 新田直也.
テキストボックス、チェックボックス×2、コマンドボタンを配置する。 コマンドボタンに機能を与える
2009/10/16 いろいろなデータ構造 第3講: 平成21年10月16日 (金) 4限 E252教室 コンピュータアルゴリズム.
データ構造とアルゴリズム 第4回 リスト ~ データ構造(1)~.
PROGRAMMING IN HASKELL
“Purely Functional Data Structures” セミナー
オブジェクト指向 プログラミング 第十三回 知能情報学部 新田直也.
PROGRAMMING IN HASKELL
データ構造とアルゴリズム 第6回 キュー ~ データ構造(2)~.
プログラミング 4 記憶の割り付け.
データ構造と アルゴリズム第4回 知能情報学メジャー 和田俊和.
“Separating Regular Languages with First-Order Logic”
アルゴリズムとデータ構造 補足資料11-1 「mallocとfree」
第9回 優先度つき待ち行列,ヒープ,二分探索木
アルゴリズムとデータ構造1 2005年7月1日
アルゴリズムとデータ構造1 2009年6月29日
オブジェクト指向言語論 第八回 知能情報学部 新田直也.
データ構造と アルゴリズム 第五回 知能情報学部 新田直也.
Cプログラミング演習 第10回 二分探索木.
アルゴリズムとデータ構造1 2005年7月5日
プログラミング 4 整列アルゴリズム.
オブジェクト指向 プログラミング 第六回 知能情報学部 新田直也.
プログラミング 4 探索と計算量.
プログラミング 4 木構造とヒープ.
アルゴリズムとデータ構造 2011年7月8日課題の復習
Eliminating Amortizations PurelyFunctionalDataStrctures 輪講 第6回
アルゴリズムとデータ構造 2011年6月23日
6.データ構造入門 6-1.連結リスト(Linked List) 6-2.スタック(Stack) 6-3.キュー(Queue)
アルゴリズムとプログラミング (Algorithms and Programming)
データ構造とアルゴリズム (第3回) 静岡大学工学部 安藤和敏
オブジェクト指向 プログラミング 第六回 知能情報学部 新田直也.
第9回 優先度つき待ち行列,ヒープ,二分探索木
アルゴリズムとデータ構造 2012年7月2日
サブゼミ第7回 実装編① オブジェクト型とキャスト.
アルゴリズムとデータ構造1 2006年6月23日
PROGRAMMING IN HASKELL
アルゴリズムとデータ構造 2013年7月2日
アルゴリズムとデータ構造1 2009年6月15日
アルゴリズムとデータ構造 2012年6月25日
PROGRAMMING IN HASKELL
アルゴリズムとデータ構造 2010年6月17日
参考:大きい要素の処理.
データ構造と アルゴリズムI 第三回 知能情報学部 新田直也.
オブジェクト指向 プログラミング 第六回 知能情報学部 新田直也.
TList リスト構造とは? 複数のデータを扱うために、 データの内容と、次のデータへのポインタを持つ構造体を使う。
プログラミング 3 ポインタ(1).
Presentation transcript:

20分でわかる Purely Functional Data Structures k.inaba (http://www.kmonos.net/) Apr. 4, 2010

あらすじ 遅い イミュータブル データ構造は

Immutable Object だけで作るデータ構造 この本の 内 容を 全速力で 布教する

お題:キュー (Queue) FIFO (First-In First-Out) pushBack(e) でデータeを入れる popFront() で取り出せる 入れた順に出てくる 以上

代入 Immutable Object でない 打倒すべき目標 破壊的キュー

手続き型でよくある interface Queue<E> { void pushBack(E e); E popFront(); }

よくある実装 class HakaiQueue<E> implements Queue<E> { class Cell { E e; Cell next; } Cell fst, last; void pushBack(E e) {last=last.next=new Cell(e,null);} E popFront() {E e=fst.e; fst=fst.next; return e;} } 1 2 3 ・ 4 ・

破壊的キューの特徴 Mutable Object を使用 Persistent でない リスト末尾を指すポインタをもっておき 末尾のセルを pushBack 時に書換 Persistent でない 操作前の状態をとっておく には全コピーしかない pushBack, popFront の最悪実行時間は O(1) 1 2 3 ・ 4 ・ HakaiQueue<E> q = 略; HakaiQueue<E> p = q; q.pushBack(e); //pも変化!

計算量 儚 永 O(1) n/a 破壊的 2リスト 銀行家 実時間 A W Ephemeral (儚い) 使い方での計算量 (比較対象) 破壊的 2リスト 銀行家 実時間 儚 A O(1) W 永 n/a 詳しくは あとで Amortized(償却)計算量 Worst-Case(最悪)計算量 Persistent (永続的な) 使い方での計算量

償却計算量! Immutable な実装 2リストキュー

非破壊的キュー フィールドの書き換えを使わないキュー pushBack, popFront は「操作後のキュー」 を別のオブジェクトを作って返す interface ImuQueue<E> { ImuQueue<E> pushBack(E e); Pair<E, ImuQueue<E>> popFront(); }

非破壊キュー 単純にやると全コピー:計算量 O(n) 1 2 3 ・ 4 ・ コピー コピー コピー 1 2 3 4 ・

2リストキュー ちょっと工夫 キューの後ろの方は逆順で持つ pushBack がリスト先頭への追加なのでO(1)に! 1 2 3 ・ 5 4 data Queue a = Q [a] [a] --ここからコードはHaskellです pushBack (Q front rear) e = Q front (e:rear) popFront (Q [] r) = popFront (Q (reverse r) []) popFront (Q (e:f) r) = (e, Q f r)

2リストキューの特徴 len(front) == 0 になったら rear を reverse 1 2 3 ・ 5 4 ・ Persistent である 最悪実行時間は、reverseが発生する瞬間 O(n) でも、償却実行時間は O(1) なので、トータルの実行時間的に考えると 計算時間は O(1) と思って問題ない! 1 2 3 ・ 5 4 ・

償却計算量とは? pushBack 1  [] [1] pushBack 2  [] [2,1] 操作列全体でコストを 平均化して見たときの 計算量 pushBack 1  [] [1] pushBack 2  [] [2,1] pushBack 3  [] [3,2,1] popFront  [1,2,3] []  [2,3] [] popFront  [3] [] pushBack 4  [3] [4] popFront  [] [4] popFront  [4] []  [] [] reverseは「たまにしか」 起きない 時間 t かかるreverseが 発生する前に、必ず t 回 pushBack している

全部 O(1) だし トータル計算量も 変わらないので 問題ない! 償却計算量とは? 現実の計算量 pushBack 1 popFront(軽) 1 popFront (重) t+1 分担をごまかした計算量 pushBack 2 popFront(軽) 1 popFront (重) 1 時間 t かかるreverseが 発生する前に、必ず t 回 pushBack している pushBack 1  [] [1] 1 2 pushBack 2  [] [2,1] 1 2 pushBack 3  [] [3,2,1] 1 2 popFront  [1,2,3] []  [2,3] [] 3+1 1 popFront  [3] [] 1 1 pushBack 4  [3] [4] 1 2 popFront  [] [4] 1 1 popFront  [4] []  [] [] 1+1 1 合計 12 12 こう思えば 全部 O(1) だし トータル計算量も 変わらないので 問題ない!

計算量 (比較対象) 破壊的 2リスト 銀行家 実時間 儚 A O(1) W O(n) 永 n/a !?

2リスト キュー 実時間 キュー 銀行家 キュー ブートスト ラップキュー 銀行家キュー

2リストキューは本当に Persistent と言えるのか??? Persistent なら、同じバージョンを取っておいて何回も使えるはず let q = loadSomeQueue () in … (doHoge q) … (doFuga q) … (doPiyo q) … (doHige q) …

破滅的な例 reverseが起きる寸前のキューを何回も使う let q = needReverseQueue () in … (popFront q) … (popFront q) … (popFront q) … (popFront q) …

ごまかしきれてない reverseが起きる寸前のキューを何回も使う let q = pushFront (pushFront (pushFront (Queue [] []) 1) 2) 3 in … (popFront q) … (popFront q) … (popFront q) … (popFront q) … 計算時間は 2*3+4 = 10 です! 実際の計算時間は 1*3+4*4 = 19 ! 現実の計算量 pushBack 1 popFront(軽) 1 popFront (重) 1+t 分担をごまかした計算量 pushBack 2 popFront(軽) 1 popFront (重) 1

どうしましょう Persistent な使い方(同じバージョンを何回も使い回す)をしても償却計算量をO(1)にするには?

frontの方が 短くなったら 早めのreverse さらなる工夫 len(front)==0 になったら reverse len(front)+1 == len(rear) になったら 遅延評価で reverse frontの方が 短くなったら 早めのreverse f 1 2 r 5 4 3 ・ data Queue a = Q [a] Int [a] Int pushBack(Q f fl r rl) e = chk (Q f fl (e:r) (rl+1)) popFront(Q (e:f) fl r rl)= (e, chk (Q f (fl-1) r rl))

さらなる工夫 len(front)+1 == len(rear) になったら 遅延評価で reverse data Queue a = Q [a] Int [a] Int pushBack(Q f fl r rl) e = chk (Q f fl (e:r) (rl+1)) popFront(Q (e:f) fl r rl)= (e, chk (Q f (fl-1) r rl)) chk (Q f fl r rl) = if fl+1 == rl then (Q (f++reverse r) (fl+rl) [] 0) else (Q f fl r rl) Haskellなので デフォルト遅延

特徴 f 1 2 r 5 4 3 ・ front側とrear側の2つのリストで表現 Persistent である len(front)+1 == len(rear) になったら遅延 reverse Persistent である 最悪実行時間は、reverseが発生するとき O(n) 償却実行時間は (persistentな使い方でも) O(1) f 1 2 r 5 4 3 ・

計算量の見積もり方 積み立て金メソッド から 借金メソッド へ 「時間tかかるreverseの前に必ずt回pushBackしてる」  「pushBackのコストを 2 にして、先に reverseの負担の分を払っておけばOK」 から 借金メソッド へ 「早めの遅延評価reverseしておけば、 値が本当に必要になるまでに時間がある」  「本当に必要になる支払期限までに、t 回の popFrontで負担金を用意できれば問題ない」

償却計算量とは? pushBack 1 popFront(軽) 1 popFront (重) x+1 pushBack 1 現実の計算量 pushBack 1 popFront(軽) 1 popFront (重) x+1 分担をごまかした計算量 pushBack 1 popFront 2 長さtのreverse結果が 必要となるまでにt回はpopFrontするはず pushBack 1  [] [1]  []r[1] [] 1 1 pushBack 2  []r[1] [2] 1 1 pushBack 3  []r[1] [3,2]  []r[1]r[3,2] [] 1 1 popFront  r[1]実行  r[3,2] [] 1+1 2 popFront  r[3,2]実行  [3] [] 2+1 2 pushBack 4  [3] [4] 1 1 popFront  [] [4]  []r[4] [] 1 2 popFront  r[4]実行  [] [] 1+1 2 合計 12 12

少し大きい例:pushBack 1~15 [] [1]  []r[1] [] []r[1] [2] []r[1] [3,2]  []r[1]r[3,2] [] []r[1]r[3,2] [4] []r[1]r[3,2] [5,4] []r[1]r[3,2] [6,5,4] []r[1]r[3,2] [7,6,5,4]  []r[1]r[3,2]r[7..4] [] … []r[1]r[3,2]r[7..4] [15..8]  []r[1]r[3,2]r[7..4]r[15..8] []

popFront [] [1]  []r[1] [] []r[1] [3,2]  []r[1]r[3,2] [] []r[1]r[3,2] [7,6,5,4]  []r[1]r[3,2]r[7..4] [] []r[1]r[3,2]r[7..4] [15..8]  []r[1]r[3,2]r[7..4]r[15..8] [] popFront: r[1]実行  r[3,2]r[7..4]r[15..8] 1+1 popFront: r[3,2]実行  [3]r[7..4]r[15..8] 1+1 popFront: r[7..4]r[15..8] 1+1 popFront: r[7..4]実行  [5,6,7]r[15..8] 1+1 popFront: [6,7]r[15..8] 1+1 popFront: [7]r[15..8] 1+1 popFront: r[15..8] 1+1 popFront: r[15..8]実行  [9..15] 1+1 借金返済 間に合ってる

なぜ借金メソッド? 積立金は一度使うとなくなるけど 3億円貯金 let q = pushFront (pushFront (pushFront (Queue [] []) 1) 2) 3 in … (popFront q) … (popFront q) … (popFront q) … (popFront q) … revに 3億円使う revに3億円使う revに3億円使う revに3億円使う

なぜ借金メソッド? 借金は、一度返せば大丈夫! 遅延評価でメモ化されてるから 15億円借金 let q = []r[1]r[3,2]r[7..4][15..8] [] in … (popFront q) … (popFront q) … (popFront q) … (popFront q) … rev実行 返済 メモ化 されてるので もうrev不要 メモ化 されてるので もうrev不要 メモ化 されてるので もうrev不要

計算量 (比較対象) 破壊的 2リスト 銀行家 実時間 儚 A O(1) W O(n) 永 n/a

※注釈 銀行家キューという名前はなんですか 本には両方の手法が紹介されています 償却計算量の評価の方法として(Functional データ構造に限らず一般の話として) 銀行家 (Banker) の方法 物理学者 (Phyisicist) の方法 の二つがあって、その「銀行家の方法」で 設計したキューという意味だそうです。 本には両方の手法が紹介されています

悪計算量 定 数 実時間キュー

全部 O(1) だしトータル計算量も 変わらないので問題ない! … 償却計算量とはなんだったのか 仮想的に「積立金」や「借金」を 考え仮想的に返済する 分担をごまかした計算量 pushBack 2 popFront(軽) 1 popFront (重) 1 こう思えば 全部 O(1) だしトータル計算量も 変わらないので問題ない!

仮想世界を現実にする popFront で、仮想的にではなく 実際に「借金」を返す = popFront のたびに、reverse 処理を 実際に「ちょっとずつ実行」する

やりかた 1: 借金ポインターを追加 (遅延評価サンクを指しておく) 2: chk 関数はreverseチェックのついでに 無駄に遅延サンクをちょっとづつ実行 (chk自体は遅延しないようにeager実行) data Queue a = Q [a] Int [a] Int [a] pushBack(Q f fl r rl s) e = seq chk (Q f fl (e:r) (rl+1) s) popFront(Q (e:f) fl r rl s) = (e, seq chk (Q f (fl-1) r rl s))

「セル1個だけ実行」 しやすいreverse やりかた 2.1: 借金ポインタに対してパターンマッチ = consセル1個分だけ実行される 2.2:rotate xs ys zs は xs++rev ys++zs する関数 chk (Q f fl r rl (_:s)) = (Q f fl r rl s) chk (Q f fl r rl []) = -- 実は fl+1 == rl のときだけこっちに来る let ff = rotate f r [] in (Q ff (fl+rl) [] 0 ff) rotate [] (y:_) zs = y : zs rotate (x:xs) (y:ys) ff = x : rotate xs ys (y:zs) 「セル1個だけ実行」 しやすいreverse

計算量 (比較対象) 破壊的 2リスト 銀行家 実時間 儚 A O(1) W O(n) 永 n/a

ニューメリカル表現の 再帰 多相再帰 スローダウンが データ構造 ブートストラップ そのほかの話題

目次(紹介した部分) 2~3章 :: 簡単な関数型データ構造の紹介 4章 :: 遅延評価とは 5章 :: 償却計算量とは 2リストキュー、赤黒木、二項ヒープ、… 4章 :: 遅延評価とは 5章 :: 償却計算量とは 6章:: 遅延評価を駆使して、永続性と  償却計算量を両立(キュー, ヒープ, …) 7章:: リアルタイム化(キュー, ヒープ, …) 8~11章 :: 関数型データ構造汎用技法集 「n進数表記に学ぶ」「ブートストラップ」 「再帰スローダウン」

あとは みんな を読もう!

Thank you for listening! ※スライド内の漫画はすべて   増田こうすけ「増田こうすけ劇場 ギャグマンガ日和 (巻の5) 」 からの引用です。