A.ウェッブの答えに追加するには:
純粋に機能的な実装は CPS で記述する必要がありますが、ツリーを機能的なラッパー + 可変ノードとして実装することもできます。変更は次のように行われます。
ラッパーは新しい空のルートを作成し、現在のルートに変更を実行して結果を新しいルートに入れるように指示します。
新しいルートが自身の値を変更する必要があると判断した場合、その子を新しいルートにコピーし、新しい値を新しいルートに入れ、戻ります。
それ以外の場合は、その値と変更を必要としないブランチを新しいルートにコピーし、変更が必要な (または可能性がある) ブランチの代わりに、新しいルートに新しい (空白の) ノードをインストールします。
現在のルートは、変更が必要なブランチにそれ自体を変更するように指示し、新しい空白ノードも渡します。
ブランチは、手順 2 ~ 5 を繰り返す際のルートとして機能します。
ラッパーは、新しいルートで新しいラッパーを作成します。トップレベルの操作の結果として、新しいラッパーが返されます。
上記のポイント 2 から 5 は再帰手順を説明していますが、実際には末尾再帰であるため、ループとして書き直すことができます。
すべてが完了した後、もちろん、古いラッパーは完全に正常であり、同じツリーを保持しています (すべての変更は新しいノードのみに関係しているため)。
実際、Clojure データ構造の多くは、包含可変性 (主に配列を含む) を常に使用しています。(ただし、ツリー マップではありません。ミュータビリティを使用する実際のパターンも異なりますが、包含ミュータビリティを使用して内部で物事を高速化し、外部で機能的なインターフェイスを維持するという基本的な前提は似ています。)
また、接線方向の 2 つのコメントを追加します。
まず、実装compare
では -1、0、1 のいずれかが返されると想定していますが、実際には、「より小さい」の場合は負の数を、「より大きい」の場合は正の数を自由に返すことができます (は私の REPL で(compare "foo" "bar")
評価され、おそらく4
正確な値は指定されていませんが、任意の JVM REPL です)。
次に、Clojure で実装された自己均衡ツリーの実例に興味がある場合は、 ClojureとClojure の実装であるsorted.cljを参照してください。これは ClojureScript 実装に基づいており、これは Clojure の Java 実装の移植版です。現時点では、純粋に実験的なものだと思います1が、動作しているように見え (CLJS テスト スイートに合格し、REPL で期待したことを実行します)、単純な PDS 実装の例として役立つ場合があります。 Clojureのように見えます。PersistentTreeMap
PersistentTreeSet
1基本的に ClojureScript の実装を作成した後、同じデータ構造の Clojure 実装を作成するのにどれだけの追加作業が必要かを確認したかったのです。Javaインターフェースを実装する必要があるため、いくつかの追加作業があることは明らかですが、コードを処理する配列を微調整する必要がありますが、あまり多くないことを望んでいました. その基本的な期待が経験によって裏付けられたことを喜んで報告します。パフォーマンスに関しては、Java 実装の標準に完全には達していません (ほとんどのベンチマークで 1.5 倍の速度低下を覚えているようですが、再確認する必要があります)。現時点では、 core.rrb-vectorのパフォーマンス チューニングが優先されますが、最終的にはこれを改善したいと考えています。