フィンガーツリー構造のブートストラップ


16

2-3本のフィンガーツリーをかなりの時間使用した後、ほとんどの操作でそのスピードに感銘を受けました。ただし、私が遭遇した1つの問題は、大きなフィンガーツリーの最初の作成に関連する大きなオーバーヘッドです。構築は一連の連結操作として定義されるため、不必要な多数のフィンガーツリー構造を構築することになります。

2本から3本の指の木は複雑であるため、それらをブートストラップするための直感的な方法は見当たらず、すべての検索が空になりました。質問は、2〜3本の指のツリーを最小限のオーバーヘッドでブートストラップする方法です。

明示的に:既知の長さnのシーケンスが与えられると、最小限の操作でSのフィンガーツリー表現を生成します。SnS

素朴な方法は、cons操作(文献では ' '演算子)を連続して呼び出すことです。しかしながら、これは、作成するn個のすべてのスライスを表す異なる指ツリー構造Sのための[ 1 .. I ]をnS[1..i]


1
単純な汎用データ構造:指の木を Hinzeとパターソンによっては、答えを提供しますか?
デイブクラーク

@Dave私は実際に彼らの論文から実装しましたが、彼らは効率的な作成に取り組んでいません。
-jbondeson

私もそう思いました。
デイブクラーク

この場合の「ビルド」の意味をもう少し詳しく教えてください。これは展開者ですか?
jbapple

@jbapple-より明確になるように編集しましたが、混乱して申し訳ありません。
-jbondeson

回答:


16

GHCのData.Sequencereplicate機能は 中2-3フィンガーツリー構築します時間と空間を、これがget-、外出先から指の木の右背に行くの要素を知ることによって有効になります。このライブラリは、2〜3本の指の木に関する元の論文の著者によって書かれました。Olgn

繰り返し連結してフィンガーツリーを構築する場合、スパインの表現を変更することで、構築中の一時的なスペース使用量を削減できる場合があります。2-3本の指の木の棘は、同期された単一リンクリストとして巧妙に保存されます。代わりに、スパインを両端キューとして保存すると、ツリーを連結するときにスペースを節約できる場合があります。アイデアは、同じ高さの2つのツリーを連結すると、ツリーのスパインを再利用することでスペースを取るということです。最初に説明したように2〜3本のフィンガーツリーを連結すると、新しいツリーの内部にあるスパインをそのまま使用できなくなります。O1

KaplanとTarjanの「分類可能なソート済みリストの純粋に機能的な表現」では、より複雑なフィンガーツリー構造について説明しています。このペーパー(セクション4)では、上記で作成したdequeの提案に似た構造についても説明します。彼らが記述する構造は、時間と空間で同じ高さの2つのツリーを連結できると信じています。フィンガーツリーを構築するために、これはあなたにとって十分なスペース節約ですか?O1

注意:「ブートストラップ」という言葉の使用は、上記の使用とは少し異なることを意味します。それらは、同じ構造のより単純なバージョンを使用してデータ構造の一部を保存することを意味します。


非常に興味深いアイデア。これを調べて、データ構造全体のトレードオフを確認する必要があります。
-jbondeson

この答えには2つのアイデアがあります。(1)アイデアの複製(2)ほぼ同じサイズのツリーをより速く連結します。入力が配列の場合、複製のアイデアはごくわずかな余分なスペースでフィンガーツリーを構築できると思います。
jbapple

はい、両方見ました。申し訳ありませんが、両方についてコメントしませんでした。私は最初に複製コードを調べていますが、Haskellの知識を間違いなく拡張しています。一見したところ、高速のランダムアクセスがあれば、私が抱えているほとんどの問題を解決できるように見えます。ランダムアクセスがない場合、高速連結はもう少し一般的なソリューションになります。
-jbondeson

10

jbappleの優秀な答えにリフに関してはreplicate、しかし、使用してreplicateA(そのreplicate代わりに、私は次のよう思い付いた上に構築されています):

--Unlike fromList, one needs the length explicitly. 
myFromList :: Int -> [b] -> Seq b
myFromList l xs = flip evalState xs $ Seq.replicateA l go
    where go = do
           (y:ys) <- get
            put ys
            return y

myFromList(もう少し効率的なバージョン)は、ソートの結果であるフィンガーツリーを構築するために内部で既に定義され、使用されData.Sequenceています。

一般的に、直感replicateAは簡単です。applicativeTree関数のreplicateA上に構築されます。大きさの木片を取りますapplicativeTreemnこのコピーを含むバランスの取れたツリーを生成します。用ケースn8まで(シングルDeepフィンガー)がハードコーディングされています。これより上にあるものはすべて、それ自体を再帰的に呼び出します。「適用可能な」要素とは、上記のコードの場合のように、ツリーの構築をスレッド効果でインターリーブすることです。

go複製された機能は、単に現在の状態を取得する動作であり、上から要素をポップし、そして残りを置き換えます。したがって、呼び出しごとに、入力として提供されたリストをさらに下に進みます。

より具体的なメモ

main = print (length (show (Seq.fromList [1..10000000::Int])))

いくつかの簡単なテストで、これは興味深いパフォーマンスのトレードオフをもたらしました。上記のメイン関数は、myFromListを使用した場合よりも約1/3低く実行されましたfromList。一方、myFromList標準のながら、2メガバイトの一定のヒープを使用するfromList926メガバイトまで使用。926MBは、リスト全体を一度にメモリに保持する必要があるために発生します。一方、このソリューションmyFromListは、遅延ストリーミング形式で構造を使用できます。速度の問題myFromListは、(状態モナドの構築/破棄のペアの結果として)約2倍の割り当てを実行する必要があるという事実に起因しますfromList。CPS変換された状態モナドに移動することにより、これらの割り当てを削除できますが、遅延が発生するとリストを非ストリーミングでトラバースする必要があるため、常により多くのメモリを保持することになります。

一方、ショーでシーケンス全体を強制するのではなく、頭または最後の要素を抽出するだけに移り、myFromListすぐに大きな勝利を提示する場合-頭の要素の抽出はほぼ瞬時であり、最後の要素の抽出は0.8秒です。一方、標準fromListでは、先頭要素または最後の要素のいずれかを抽出するには、約2.3秒かかります。

これはすべて詳細であり、純度と怠lazの結果です。突然変異とランダムアクセスのある状況でreplicateは、ソリューションの方が厳密に優れていると思います。

しかし、それは書き換える方法があるかどうかの問題提起しapplicativeTree、そのようなmyFromList厳密に、より効率的であるが。問題は、適用アクションがツリーが自然にトラバースされる順序とは異なる順序で実行されることだと思いますが、これがどのように機能するか、またはこれを解決する方法があるかどうかについては完全に取り組んでいません。


4
(1)興味深い。これはこのタスクを実行する正しい方法のように見えます。fromListシーケンス全体を強制する場合よりも遅いと聞いて驚いています。(2)cstheory.stackexchange.comにとって、この回答はコードが多すぎて言語に依存している可能性があります。replicateA言語に依存しない方法でどのように機能するかの説明を追加できれば素晴らしいと思います。
伊藤剛

9

多数の中間的なフィンガーツリー構造が作成される一方で、それらは構造の大部分を互いに共有します。最終的には、理想的な場合と比べて最大で2倍のメモリを割り当て、残りは最初のコレクションで解放されます。最終的にn個の値で満たされたフィンガーツリーが必要になるため、これの漸近性は得られるものと同じです。

を使用してフィンガーツリーを構築できData.FingerTree.replicateます。それらを使用FingerTree.fmapWithPosして、有限シーケンスの役割を果たす配列内の値を検索するか、traverseWithPos、リストまたはその他の既知のサイズのコンテナーから値を剥離します。

OログnOnOログnられ、ガベージコレクションがクリーンアップするまでメモリをし、したがって、最適な〜1000ノードの代わりに、cons'd構造の〜2000の代わりに、〜1010を支払う必要があります。

OログnreplicateAmapAccumL状態モナドにたり、状態モナドを横断したりすることは、実際に比例して同様に導入されます余分な製品セルをすべて支払うだけのオーバーヘッド。

TL; DRこれをしなければならなかった場合、私はおそらく使用します:

rep :: (Int -> a) -> Int -> Seq a 
rep f n = mapWithIndex (const . f) $ replicate n () 

固定サイズの配列にインデックスを付けるために(arr !)f上記で提供します。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.