讀FingerTree論文榔昔,記筆記 (上)

所有權(quán)利歸論文原作者所有蜈项,如果侵權(quán)聘殖,本人將立刻刪除這篇文章晨雳。

本文中的代碼可以在這里找到https://github.com/hgztheyoung/CopiedCodes/blob/master/ft.hs
論文中的實(shí)現(xiàn)見 http://hackage.haskell.org/package/fingertree
這篇博客里的圖不錯(cuò),值得參考
http://www.dalnefre.com/wp/2011/09/finger-tree-a-functional-value-object/

看了[1]里的答案奸腺,知道了FingerTree這么個(gè)數(shù)據(jù)結(jié)構(gòu)餐禁,十分好奇。
于是從這里[2]找了篇論文,抄抄代碼突照,寫寫筆記

2 Preliminaries

2.1 Monoid

class Monoid a where
  mempty  :: a
  mappend :: a->a->a

A type with an associative operation and an identity forms a monoid.
具有一個(gè)可結(jié)合運(yùn)算和單位元的類型構(gòu)成一個(gè)Monoid[3]

 可結(jié)合  
    (A `mappend` B) `mappend` C = A `mappend` (B `mappend` C)
 單位元  
    A `mappend` mempty = A
    mempty `mappend` A  = A

具有矩陣乘法和單位矩陣的n*n矩陣構(gòu)成一個(gè)Monoid
具有字符串連接和空字符串的字符串構(gòu)成一個(gè)Monoid

2.2 Right and left reductions

A reduction with a monoid yields the same result
however we nest the applications of mappend, but for a reduction with an arbitrary
constant and binary operation we must specify a particular nesting.

對(duì)monoid做reduction帮非,無(wú)論運(yùn)算順序如何,總會(huì)得到相同的結(jié)果 (因?yàn)閙onoid滿足結(jié)合律)
而對(duì)于任意的常量和二元運(yùn)算做reduction,我們必須指定一種特定的運(yùn)算順序(foldr和foldl的區(qū)別)

class Reduce f where
  reducer :: (a -> b -> b) -> (f a -> b -> b)
  reducel :: (b -> a -> b) -> (b -> f a -> b)

這個(gè)reducer就是說(shuō)末盔,輸入一個(gè) (a->b->b)的二元運(yùn)算筑舅,就能輸出一個(gè)(f a->b->b)的函數(shù) (柯里化了,變成這種形式)
可以把這里的f理解成某種泛型的容器陨舱,比如list翠拣。f a->b->b就是說(shuō),給個(gè)a類型的容器游盲,比如a的列表误墓,a類型的樹等等。
給個(gè)b類型的初始值背桐,一般是mempty优烧。就能遍歷整個(gè)容器,最終得到一個(gè)b類型的結(jié)果

toList :: (Reduce f) => f a -> [a]
toList s = consbar s [] where
  consbar = reducer (:)

toList的簽名是說(shuō)链峭,只要f是實(shí)現(xiàn)了Reduce的容器畦娄,就能從f a類型得到a類型的列表
(:)是 a->[a]->[a]類型,所以 reducer (:)是 f a->[a]->[a]類型弊仪。

3 Simple sequences

As a starting point, consider ordinary 2-3 trees, like the one
below:

raw_2-3_tree.PNG

只在最后一層存放數(shù)據(jù)

Operations on such trees typically take time logarithmic in the size of the tree,
but for a sequence implementation we would like to add and remove elements from
either end in constant time.

想要實(shí)現(xiàn)O(1)的在兩端增加和刪除元素熙卡,怎么辦呢?現(xiàn)在是O(logN)

3.1 Finger Tree

A structure providing efficient access to nodes of a tree near a distinguished location
is called a finger.To provide efficient access to the ends of a sequence, we wish to place fingers at
the left and right ends of this tree. Imagine taking hold of the end nodes of the
example tree above and lifting them up together.

為了高效的訪問(wèn)兩端励饵,把樹往中間對(duì)折驳癌。原來(lái)的左右兩端成為根,原來(lái)的根成為最中間最下方的節(jié)點(diǎn)

ft-step1.PNG
ft-step2.PNG
ft-step3.png

這里[4]有更多例子役听。

注意到原來(lái)在底端的最左邊和最右邊颓鲜,現(xiàn)在在頂端,可以快速訪問(wèn)到典予。
且越是原來(lái)靠近邊緣的節(jié)點(diǎn)甜滨,現(xiàn)在就越接近頂端。在中間的節(jié)點(diǎn)可能訪問(wèn)代價(jià)會(huì)變大瘤袖。
不過(guò)沒(méi)關(guān)系衣摩,最多變大一倍,還是O(logN)復(fù)雜度捂敌。

而且finger tree每向下一層艾扮,節(jié)點(diǎn)就變得更大。

定義

data FingerTree a = Empty
  | Single a
  | Deep (Digit a) (FingerTree (Node a)) (Digit a)

type Digit a = [a]
data Node a = Node2 a a | Node3 a a a

Before moving on, we instantiate the generic definition of reduction to these
types. Reduction of nodes is trivial:

instance Reduce Node where
  reducer (-<) (Node2 a b) z = a -< (b -< z)
  reducer (-<) (Node3 a b c) z = a -< (b -< (c -< z))
  reducel (>-) z (Node2 b a) = (z >- b) >- a
  reducel (>-) z (Node3 c b a) = ((z >- c) >- b) >- a

Reduction of finger trees uses single and double liftings of the binary operation:

instance Reduce FingerTree where
  reducer _ Empty z = z
  reducer (-<) (Single x) z = x -< z
  reducer (-<) (Deep pr m sf ) z = pr -<< (m -<<< (sf -<< z))
    where (-<<) = reducer (-<)
          (-<<<) = reducer (reducer (-<))
  reducel _ z Empty = z
  reducel (>-) z (Single x) = z >- x
  reducel (>-) z (Deep pr m sf ) = ((z >>- pr) >>>- m) >>- sf
    where (>>-) = reducel (>-)
          (>>>-) = reducel (reducel (>-))

-<,-<<,-<<<占婉,只是一些函數(shù)名而已泡嘴。
在這段里頭,-<<是 [a]->b->b類型逆济, -<<<是(FingerTree (Node a))->b->b類型

這塊是我瞎猜的

-< :: a -> b -> b

reducer :: (a -> b -> b) -> (f a -> b -> b)

reducer (-<) f0 a -> b -> b

reducer (reducer (-<))  (f1 (f0 a)) -> b -> b

然后f0匹配到Node磕诊,f1匹配到FingerTree填物。所以-<<<就能處理FingerTree(Node a)類型了
當(dāng)然前提是FingerTree和Node都instance了Reduce。

試一下是不是真的能Reduce

*Main> toList Empty
[]
*Main> toList (Single 1)
[1]
*Main> toList (Deep [1,2] Empty [3,4])
[1,2,3,4]
*Main> toList (Deep [1,2] (Single (Node3 3 4 5)) [6,7])
[1,2,3,4,5,6,7]

3.2 Deque operations

入隊(duì)

infixr 5 <|
(<|) :: a -> FingerTree a -> FingerTree a
a <| Empty = Single a
a <| Single b = Deep [a] Empty [b]
a <| Deep [b,c,d,e] m sf = Deep [a,b] (Node3 c d e <| m) sf
a <| Deep pr m sf = Deep ([a] ++ pr) m sf

infixl 5 |>
(|>)  :: FingerTree a -> a -> FingerTree a
Empty               |> a = Single a
Single b            |> a = Deep [b] Empty [a]
Deep pr m [e,d,c,b] |> a = Deep pr (m |> Node3 e d c) [b,a]
Deep pr m sf        |> a = Deep pr m (sf ++ [a])

(<||) :: (Reduce f) => f a -> FingerTree a -> FingerTree a
(<||) = reducer (<|)

(||>) :: (Reduce f) => FingerTree a -> f a -> FingerTree a
(||>) = reducel (|>)

toTree :: (Reduce f) => f a -> FingerTree a
toTree s = s <|| Empty

a <| Deep [b,c,d,e] m sf = Deep [a,b] (Node3 c d e <| m) sf

這一行比較關(guān)鍵霎终,意思是說(shuō)滞磺,這一層的Digit裝不下了,就把這個(gè)Digit里拿出3個(gè)元素莱褒,
做個(gè)大Node击困,作為一個(gè)元素,插入到下一層去广凸。這保證了FingerTree越往下阅茶,節(jié)點(diǎn)越大的性質(zhì)。也就保證了O(logN)復(fù)雜度

這個(gè)toTree的實(shí)現(xiàn)也很有意思谅海,

(<|) :: a -> FingerTree a -> FingerTree a
reducer (<|) 就變成了 f a -> FingerTree a -> FingerTree a

出隊(duì)

To deconstruct a sequence, we define a type that expresses a view of the left end
of a sequence as either empty or an element together with the rest of the sequence:

Then we can define separate functions isEmpty, head L , and tail L using the view:

In a lazy language like Haskell, the tail part of the view is not computed unless it
is used. In a strict language, it might be more useful to provide the three separate
functions as primitives.

data ViewL s a = NilL | ConsL a (s a)
data ViewR s a = NilR | ConsR (s a) a

viewL :: FingerTree a -> ViewL FingerTree a
viewL Empty = NilL
viewL (Single x) = ConsL x Empty
viewL (Deep pr m sf) = ConsL (head pr) (deepL (tail pr) m sf)

viewR :: FingerTree a -> ViewR FingerTree a
viewR Empty = NilR
viewR (Single x) = ConsR Empty x
viewR (Deep pr m sf) = ConsR (deepR pr m (init sf)) (last sf)

deepL :: [a] -> FingerTree (Node a) -> Digit a -> FingerTree a
deepL [] m sf  =
  case viewL m of
    NilL -> toTree sf
    ConsL a mbar -> Deep (toList a) mbar sf
deepL pr m sf = Deep pr m sf

deepR :: [a] -> FingerTree (Node a) -> Digit a -> FingerTree a
deepR pr m [] =
  case viewR m of
    NilR -> toTree pr
    ConsR mbar a -> Deep pr mbar (toList a)
deepR pr m sf = Deep pr m sf

isEmpty :: FingerTree a -> Bool
isEmpty x = case viewL x of
  NilL -> True
  ConsL _ _ -> False

headL :: FingerTree a -> a
headL x = case viewL x of ConsL a _ -> a

tailL :: FingerTree a -> FingerTree a
tailL x = case viewL x of ConsL _ x' -> x'

lastR :: FingerTree a -> a
lastR x = case viewR x of ConsR _ a -> a

initR :: FingerTree a -> FingerTree a
initR x = case viewR x of ConsR x' _ -> x'

3.3 Concatenation

The only difficult case is concatenation of two Deep trees.

We can
use the prefix of the first tree as the prefix of the result, and the suffix of the second
tree as the suffix of the result. To combine the rest to make the new middle subtree,
we require a function of type

FingerTree (Node a) → Digit a → Digit a → FingerTree (Node a) →
FingerTree (Node a)

中間的Digit a → Digit a可以簡(jiǎn)化成一個(gè) [a]

app3 :: FingerTree a -> [a] -> FingerTree a -> FingerTree a
app3 Empty ts xs = ts <|| xs
app3 xs ts Empty = xs ||> ts
app3 (Single x) ts xs = x <| (ts <|| xs)
app3 xs ts (Single x) = (xs ||> ts) |> x
app3 (Deep pr1 m1 sf1) ts (Deep pr2 m2 sf2) =
  Deep pr1 (app3 m1 (nodes (sf1 ++ ts ++ pr2)) m2) sf2

nodes :: [a] -> [Node a]
nodes [a,b] = [Node2 a b]
nodes [a,b,c] = [Node3 a b c]
nodes [a,b,c,d] = [Node2 a b,Node2 c d]
nodes (a:b:c:xs) = Node3 a b c : nodes xs

(><) :: FingerTree a -> FingerTree a -> FingerTree a
xs >< ys = app3 xs [] ys

Deep pr1 (app3 m1 (nodes (sf1 ++ ts ++ pr2)) m2) sf2
這一句話里脸哀,使用nodes把這一層的幾個(gè)元素,"包裝"成Node作為下一層的元素使用

順便實(shí)現(xiàn)讓FingerTree作為Monoid使用

instance Monoid (FingerTree a) where
  mempty = Empty
  mappend = (><)

參考鏈接

[1] https://www.zhihu.com/question/32163076

[2] http://www.staff.city.ac.uk/~ross/papers/FingerTree.pdf

[3] https://en.wikipedia.org/wiki/Monoid

[4] http://www.staff.city.ac.uk/~ross/papers/FingerTree/more-trees.html

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末扭吁,一起剝皮案震驚了整個(gè)濱河市撞蜂,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌侥袜,老刑警劉巖蝌诡,帶你破解...
    沈念sama閱讀 219,110評(píng)論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件寓盗,死亡現(xiàn)場(chǎng)離奇詭異譬圣,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)能庆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,443評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門九杂,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)颁湖,“玉大人,你說(shuō)我怎么就攤上這事例隆∫罚” “怎么了?”我有些...
    開封第一講書人閱讀 165,474評(píng)論 0 356
  • 文/不壞的土叔 我叫張陵裳擎,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我思币,道長(zhǎng)鹿响,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,881評(píng)論 1 295
  • 正文 為了忘掉前任谷饿,我火速辦了婚禮惶我,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘博投。我一直安慰自己绸贡,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,902評(píng)論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著听怕,像睡著了一般捧挺。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上尿瞭,一...
    開封第一講書人閱讀 51,698評(píng)論 1 305
  • 那天闽烙,我揣著相機(jī)與錄音,去河邊找鬼声搁。 笑死黑竞,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的疏旨。 我是一名探鬼主播很魂,決...
    沈念sama閱讀 40,418評(píng)論 3 419
  • 文/蒼蘭香墨 我猛地睜開眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼檐涝!你這毒婦竟也來(lái)了遏匆?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,332評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤骤铃,失蹤者是張志新(化名)和其女友劉穎拉岁,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體惰爬,經(jīng)...
    沈念sama閱讀 45,796評(píng)論 1 316
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡喊暖,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,968評(píng)論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了撕瞧。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片陵叽。...
    茶點(diǎn)故事閱讀 40,110評(píng)論 1 351
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖丛版,靈堂內(nèi)的尸體忽然破棺而出巩掺,到底是詐尸還是另有隱情,我是刑警寧澤页畦,帶...
    沈念sama閱讀 35,792評(píng)論 5 346
  • 正文 年R本政府宣布胖替,位于F島的核電站,受9級(jí)特大地震影響豫缨,放射性物質(zhì)發(fā)生泄漏独令。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,455評(píng)論 3 331
  • 文/蒙蒙 一好芭、第九天 我趴在偏房一處隱蔽的房頂上張望燃箭。 院中可真熱鬧,春花似錦舍败、人聲如沸招狸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,003評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)裙戏。三九已至乘凸,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間挽懦,已是汗流浹背翰意。 一陣腳步聲響...
    開封第一講書人閱讀 33,130評(píng)論 1 272
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留信柿,地道東北人冀偶。 一個(gè)月前我還...
    沈念sama閱讀 48,348評(píng)論 3 373
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像渔嚷,于是被迫代替她去往敵國(guó)和親进鸠。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,047評(píng)論 2 355

推薦閱讀更多精彩內(nèi)容