2-3(2-3 finger treefinger tree)2006Ralf HinzeRoss Paterson[1][2]

使Haskellcontainers[3]Data.Sequence[4]fingertree[5]Scalascalaz[6]

構造

編集

2-323(finger)[7] 辿辿辿(2-3)
 

辿22

2-3232314132-3
 
2-3

2-3Haskell
-- 2-3フィンガーツリー
data FingerTree a = Empty -- 空の木
                  | Single a -- 1つだけ要素を持つ木
                  | Deep (Digit a) (FingerTree (Node a)) (Digit a) -- より深い木

-- 左右の木の根。1から4つの要素を持つ。
data Digit a = One a | Two a a | Three a a a | Four a a a a

-- 左右の木の根以外。2つまたは3つの要素を持つ。
data Node a = Node2 a a | Node3 a a a

1DigitNode23DigitNodeNode Integer1Node (Node Integer)2Digit()

演算

編集

フィンガーツリーに対する要素の追加等の各種演算を示す。ここでは次のように図を混ぜた式でも表現する。

 

追加

編集



1
Empty ▷ a = Single a
 
(Single a) ▷ b = Deep (One a) Empty (One b)
 

Digit4Digit1
 (Deep pr m (One b)) ▷ c = Deep pr m (Two b c)
 

Digit4
 (Deep pr m (Four b c d e)) ▷ f = Deep pr (m ▷ (Node3 b c d)) (Two e f)
 

1Node

削除

編集

popRpopL

1
 popR (Single a) = (Empty, a)
 

Digit1
 popR (Deep pr m (Two b c)) = (Deep pr m (One b), c)
 

Digit1
 popR (Deep pr m (One b)) = (borrowR pr m, b)


 borrowR pr Empty = toTree pr

toTreepr


 borrowR pr m
   = let
       (m', node) = popR m
     in
       Deep pr m' (toDigit node)

toDigitNodeDigitNode23TwoThreeNode

popRpopRborrowR1
 


 



171Digit3
 

連結

編集

22append使

11
 append(pr, [a, b, c, d, e], Single f) = pr ▷ a ▷ b ▷ c ▷ d ▷ e ▷ f
 


 append(Deep pr m1 (Four a b c d), [e, f, g, h], Deep (Three i j k) m2 sf)
   = let
       m = append(m1, [Node3 a b c, Node3 d e f, Node3 g h i, Node j k], m2)
     in
       Deep pr m sf
 

分割

編集


class SizeMeasured a where size :: a -> Integer

instance SizeMeasured FingerTree where
  size Empty = 0
  size (Single a) = size a
  size (Deep pr m sf) = size pr + size m + size sf

instance SizeMeasured Digit where
  size (One a) = size a
  size (Two a b) = size a + size b
  -- その他のDigitは省略

instance SizeMeasured Node where
  size (Node2 a b) = size a + size b
  size (Node3 a b c) = size a + size b + size c

newtype Leaf a = Leaf a -- 葉はLeafでくるんでおく。
instance SizeMeasured Leaf where
  size (Leaf _) = 1




iiiisplitTreeAt使

1i0
 splitTreeAt i (Single a) = (Empty, a, Empty)

ii
 splitTreeAt i (Deep pr m sf)
   | i <= size pr -- i番目の葉は左に含まれる
     = let
         (pr1, a, pr2) = splitDigitAt i pr
       in
         (pr1, a, Deep pr2 m sf)
   | i > size pr + size m -- i番目の葉は右に含まれる
     = let
         (sf1, a, sf2) = splitDigitAt (i - size pr - size m) sf
       in
         (Deep pr m sf1, a, pr2)
   -- 続く
 
 

iii
 splitDigitAt i (One a) = (Empty, a, Empty)
 splitDigitAt i (Two a b) | i < size a = (Empty, a, Single b)
                          | otherwise = (Single a, b, Empty)
 -- 他のDigitについては略

iiNodeNode
   -- 続き
   | otherwise -- i番目の葉はより深い木に含まれる
       = let
           -- m1とm3はFingerTree、m2はNode。
           (ml, xs, mr) = splitTreeAt (i - size pr) m
           -- lとrはMaybe (Digit a)とする。
           (l, a, r) = splitNodeAt (i - size pr - size ml) xs
         in
           -- deepR, deepLは、rlがNothingの場合に繰り下がり処理をしてFingerTreeを作る関数
           (deepR pr ml l, a, deepL r mr sf)
 
 splitNodeAt i node = -- 省略
 
 deepR pr m Nothing = borrowR pr m
 deepR pr m (Just sf) = Deep pr m sf
 
 -- deepLは省略
 

分割の一般化

編集

i

#e

i使

i使

p0使i0

measurexp
 splitTreeAt p x (Single a) = (Empty, a, Empty)
 splitTreeAt p x (Deep pr m sf)
   | p (x ⊕ measure pr)
     = let
         (pr1, a, pr2) = splitDigitAt p x pr
       in
         (pr1, a, Deep pr2 m sf)
   | p (x ⊕ measure pr ⊕ measure m)
     = let
         (sf1, a, sf2) = splitDigitAt p (i ⊕ measure pr ⊕ measure m) sf
       in
         (Deep pr m sf1, a, pr2)
   | otherwise
       = let
           -- m1とm3はFingerTree、m2はNode。
           (ml, xs, mr) = splitTreeAt p (i ⊕ measure pr) m
           -- lとrはMaybe (Digit a)とする。
           (l, a, r) = splitNodeAt p (i ⊕ measure pr ⊕ measure ml) xs
         in
           -- deepR, deepLは、lrがNothingの場合に繰り下がり処理をしてFingerTreeを作る関数
           (deepR pr ml l, a, deepL r mr sf)

1

実行時間

編集


implicit recursive slowdown

編集

2-3フィンガーツリーはimplicit recursive slowdownという構成手法に基づくデータ構造である。implicit recursive slowdownとは、recursive slowdownに遅延評価を導入し、計算量を最悪計算量から償却計算量ヘ緩めて簡略化したものである。recursive slowdownは親ノードをn回処理する間に子ノードをnより少ないm回処理する。そのため等比数列の性質により全体の計算量は親ノードの計算量のたかだか定数倍となる。この性質により2-3フィンガーツリーも要素の追加演算や削除演算の計算量が償却定数時間となっている。


応用

編集

ここでは#分割の一般化で示した分割演算を使った応用例を示す。

優先度付きキュー

編集

measureHaskellscalaz


探索木

編集

探索木を実装する場合、関数measureはその部分木が含む最後のキーを返す。そして木にキーkを挿入する際は、木をkより小さい部分とk以上の部分に分割し、その間にkを入れて連結する。するとキーは昇順に並ぶようになり、平衡探索木を実装できる。

統計量の計算

編集

()[8]

measure×(en:Algorithms for calculating variance#Parallel algorithm)


参考文献

編集
  1. ^ Ralf Hinze and Ross Paterson, “Finger Trees: A Simple General-purpose Data Structure”, In Journal of Functional Programming Vol. 16, No. 2, 2006, pp. 197–217, doi:10.1017/S0956796805005769
  2. ^ Finger Trees: A Simple General-purpose Data Structure
  3. ^ containers: Assorted concrete container types
  4. ^ Data.Sequence
  5. ^ fingertree: Generic finger-tree structure, with example instances
  6. ^ FingerTree - scalaz-core_2.13 javadoc
  7. ^ Leo J. Guibas, Edward M. McCreight, Michael F. Plass, and Janet R. Roberts, “A new representation for linear lists”, In Proceedings of the ninth annual ACM symposium on Theory of computing (STOC '77), New York, NY, USA: ACM, 1977, pp. 49–60. DOI=10.1145/800105.803395 http://doi.acm.org/10.1145/800105.803395
  8. ^ sigfpe, “Statistical Fingertrees”, A Neighborhood of Infinity, http://blog.sigfpe.com/2010/11/statistical-fingertrees.html 2011年6月5日アクセス