web-dev-qa-db-ja.com

依存データ構造を最新に保つにはどうすればよいですか?

解析ツリー、抽象構文ツリー、および制御フローグラフがあり、それぞれが前のものから論理的に派生しているとします。原則として、解析ツリーが与えられれば各グラフを構築するのは簡単ですが、解析ツリーが変更されたときにグラフを更新する複雑さをどのように管理できますか?私たちはツリーがどのように変更されたかを正確に知っていますが、変更を管理するのが難しくならない方法で他のツリーにどのように伝播することができますか?

当然ながら、依存グラフは最初のグラフが変更されるたびに最初から再構築するだけで更新できますが、依存グラフの変更の詳細を知る方法はありません。

現在、この問題を解決する方法は4つありますが、それぞれに問題があります。

  1. 従属ツリーのノードはそれぞれ、元のツリーの関連ノードを監視し、必要に応じて自身と元のツリーノードのオブザーバーリストを更新します。これの概念的な複雑さは困難になる可能性があります。
  2. 元のツリーの各ノードには、それに依存する従属ツリーノードのリストがあり、ノードが変更されると、従属ノードにフラグを設定して、従属ノードの親を含め、ダーティとしてマークします。ルートに。変更するたびに、依存グラフを最初から作成するアルゴリズムとよく似たアルゴリズムを実行しますが、クリーンノードをスキップして各ダーティノードを再構築し、再構築されたノードが実際にダーティノードと異なるかどうかを追跡します。これも注意が必要です。
  3. 元のグラフと従属グラフの間の論理接続を、おそらく宣言型言語を使用して設計された制約のリストのようなデータ構造として表すことができます。元のグラフが変更されたら、リストをスキャンして、違反している制約と、違反を修正するために依存ツリーをどのように変更する必要があるかを発見するだけです。
  4. 既存の依存グラフがないかのように、依存グラフを最初から再構築し、既存のグラフと新しいグラフを比較して、どのように変化したかを確認できます。違いを検出するために使用できるアルゴリズムがあることを知っているので、これが最も簡単な方法であると確信していますが、それらはすべて非常に計算コストが高く、原則として不要と思われるので、このオプションは意図的に避けています。

この種の問題に対処する正しい方法は何ですか?確かに、このすべてをほぼ簡単にするデザインパターンがなければなりません。この一般的な説明のすべての問題に対して適切な解決策があればいいでしょう。このクラスの問題には名前がありますか?


この問題が引き起こすトラブルについて詳しく説明しましょう。この問題は、プロジェクトの2つの部分がグラフで動作するたびにさまざまな場所で発生します。各グラフは、ソフトウェアの実行中に変化する同じものの異なる表現です。これはインターフェースのアダプターを作成するようなものですが、単一のオブジェクトまたは固定数のオブジェクトをラップする代わりに、任意のサイズのグラフ全体をラップする必要があります。

私がこれを試す度に、私は混乱して維持不可能な混乱に陥ります。オブザーバーの制御フローは複雑になると追跡が困難になる可能性があり、あるグラフを別のグラフに変換するアルゴリズムは、通常、レイアウトが明確で複数のクラスに広がっていない場合に追跡するには十分に注意が必要です。問題は、元のグラフが変更されているときに、単純で単純なグラフ変換アルゴリズムだけを使用する方法がないように見えることです。

当然のことながら、通常のグラフ変換アルゴリズムを直接使用することはできません。これは、ゼロから開始する以外の方法では変更に応答できないためです。おそらく、アルゴリズムは継続渡しスタイルで記述できます。この場合、アルゴリズムの各ステップは、ビジターのように、元のグラフのノードのタイプごとにメソッドを持つオブジェクトとして表されます。次に、さまざまな単純なビジターを組み合わせてアルゴリズムを組み立てることができます。


別の例:Java Swing、JPanelsとレイアウトマネージャーを使用する場合と同じようにレイアウトされたGUIがあるとします。複雑なレイアウトマネージャーの代わりにネストされたJPanelsを使用すると、そのプロセスを簡略化できます。その結果、レイアウトの目的でのみ存在し、それ以外の場合は無意味なノードを含むさまざまなコンテナーのツリーになります。ここで、GUIの生成に使用されるのと同じツリーが、アプリケーションの別の部分でも使用されているが、ツリーをグラフィカルに配置することは、フォルダーのシステムとして抽象表現ツリーを生成するライブラリーと連携して機能します。このライブラリーを使用するには、レイアウトノードのないバージョンのツリーが必要です。レイアウトノードは親ノードにフラット化する必要がありますが、ツリーの2つのバージョンが単一のデータ構造であるかのように、ツリーが変更されるたびにライブラリに通知する必要があります。


それを見る別の方法:可変ツリーを扱うというまさにその概念は デメテルの法則 に違反しています。構文解析ツリーや構文ツリーが通常どおりの値である場合は、実際には法律違反にはなりませんが、その場合、何も最新の状態に保つ必要がないため問題はありません。それで、この問題はデメテルの法則に違反した直接の結果として存在しますが、ドメインがツリーまたはグラフの操作に関するものであると思われる場合、一般的にどのようにそれを回避しますか?

複合パターン は、グラフを単一のオブジェクトに変換し、デメテルの法則に従うための素晴らしいツールです。複合パターンを使用して、ある種類の木を別の種類の木に効果的に変換することは可能ですか?抽象構文木や制御フローグラフのように機能するように、複合解析ツリーを作成できますか? 単一責任の原則 に違反せずにそれを行う方法はありますか?複合パターンは、クラスが彼らが触れるすべての責任を吸収する傾向がありますが、おそらく Strategyパターン と組み合わせることができます。

8
Geo

あなたのシナリオは Observer Pattern のバリエーションについて議論していると思います。元の各ノード(「件名」)には、(少なくとも)次の2つのメソッドがあります。

  • registerObserver(observer) –依存ノードをオブザーバーのリストに追加します。
  • notifyObservers() –各オブザーバーでx.notify(this)を呼び出します

そして、各従属ノード(“ observer”)にはnotify(original)メソッドがあります。シナリオの比較:

  1. notifyメソッドは、依存するサブツリーをすぐに再構築します。
  2. notifyメソッドはフラグを設定します。再計算が行われるたびに再計算が行われます。
  3. notifyObserversメソッドはスマートで、制約が無効化されているオブザーバーにのみ通知します。これはおそらくVisitor Patternを使用するため、依存ノードはこれを決定するメソッドを提供できます。
  4. (このパターンはブルートフォースの再構築とは関係ありません)

最初の3つのアイデアはオブザーバーパターンのバリエーションにすぎないため、それらのデザインは同様の複雑さを持ちます(実際、それらは実際には複雑さを増すように順序付けられています。№1が実装が最も簡単だと思います)。

機能強化を1つ考えることができます。依存ツリーの構築lazilyです。その場合、各従属ノードには、validまたはinvalidに設定されたブールフラグがあります。各アクセサメソッドはこのフラグをチェックし、必要に応じてサブツリーを再計算します。 №2との違いは、再計算は変更時ではなくアクセス時に行われることです。これにより、計算が最も少なくなる可能性がありますが、ノードのタイプをアクセス時に変更する必要がある場合、重大な問題が発生する可能性があります。


また、複数の依存ツリーの必要性にも挑戦したいと思います。たとえば、パーサーは常にASTをすぐに出力するように構成します。このツリーの構築中にのみ関連する情報は、永続的なデータ構造に格納する必要はありません。同様に、ASTが制御フローグラフとして解釈されるように、オブジェクトを選択することもできます。

実際の例では、Perlインタープリター内のコンパイラー部分がこれを行います:ASTはボトムアップで構築され、その間、一部のノードは定数で折りたたまれます。 2回目の実行では、ノードは実行順に接続され、その間、一部のノードは最適化によってスキップされます。結果は、非常に高速な解析(および割り当てが少ない)ですが、最適化は非常に制限されます。このような設計では、 可能、それはおそらくあなたが努力すべきものではありません:それは 計算されたトレードオフ完全な違反単一の責任の原則の。

実際に複数のツリーが必要な場合は、それらを本当に同時に構築する必要があるかどうかも検討する必要があります。ほとんどの場合、解析ツリーは解析後も一定です。同様に、ASTは、マクロが解決され、ASTレベルの最適化が実行された後も、おそらく一定のままです。

5
amon

2つのグラフの一般的なケースを考えているようです。2番目のグラフは最初のグラフから完全に導出でき、最初の部分が変更されたときに2番目のグラフを効率的に再計算します。

これは、最初のグラフだけで再計算を最小化する問題と概念的には違いはないように見えますが、特定のシステムに実装された場合、各グラフでおそらく異なるタイプであると思います。

グラフ内およびグラフ間の依存関係の追跡がほとんどすべてです。変更されたノードごとに、すべての依存関係を再帰的に更新します。

もちろん、更新を行う前に、依存関係グラフをトポロジ的にソートする必要があります。これにより、循環依存関係があり、無限の更新波が発生する可能性があるかどうかがわかります。また、ノードを更新する前に、そのノードの依存関係をすべて更新できるため、後でやり直す必要のある無意味な計算を回避できます。

特に依存関係を宣言型言語で表現する必要はありませんが、それは完全に独立した問題です。

これは一般的なアルゴリズムであり、特定のケースでは、速度を上げるためにできることが他にもあるかもしれません。 1つの依存関係を更新するために行っている作業の一部は、他の依存関係の更新にも役立つ可能性があり、優れたアルゴリズムはそれを利用します。

グラフ変換アルゴリズムが維持不可能な混乱である限り、解決策は言語固有ですが、オブジェクト指向のアプローチは、依存関係の更新、つまり依存関係の表現、トポロジカルソートの実行、および計算のトリガーを純粋に処理するクラスをいくつか持つことです。 。計算を行うには、作成時に渡されたファーストクラス関数を使用して、実際のクラスにデリゲートします。おそらく、渡されたクラスがインターフェイスを実装する必要があるためです(通常、できない場合は、たとえば、作成しなかった場合は、アダプターを使用できます)。場合によっては、リフレクションを使用してオブジェクト関係のグラフからグラフ情報を収集し、その方法でメソッドを呼び出すことができると思います。設定が簡単で、そのようなメタプログラミングを気にしない場合は。

2
psr

問題の別の表現-いくつかのデータ(グラフ)とさまざまな表現(レイアウトパネル/ツリービューなど)があります。各表現が他の表現と一致していることを確認したい。

それでは、最も基本的な表現を考え出して、お互いの表現を変えて基本的な表現を見てみませんか?その後、基本的なものを変更するだけで十分であり、ビューは依然として一貫しています。


レイアウトの例:最初の表現は、次のようにしましょう:

panelA(
    panelB(
        panelC(
            widget1
            widget2
        )
        panelD(
            widget3
        )
    )
    widget4
)

したがって、次のタプルのリストである「より単純な」表現に変換します。

[
    (panelA, panelB, panelC, widget1),
    (panelA, panelB, panelC, widget2),
    (panelA, panelB, panelD, widget3),
    (panelA, widget4),
]

次に、このグラフをSwingで使用しながら、上記の表現を特殊なツリーに変換するビューを作成します。ツリービューで使用すると、タプルの最後の要素のリストのみを返すビューがあります。

「シンプル」または「ベーシック」とはどういう意味ですか?最も重要なのは、どのビューにも簡単に切り替えられることです(そのため、各ビューの計算が簡単になります)。また、どのビューからでも簡単に変更できる必要があります。

ここで、レイアウトビューを使用してこの構造を変更するとします。 「panelC.parent = panelD」の呼び出しを変換して、「panelDが含まれているリストを検索し、panelCを含むすべてのリストを検索し、それらのリストのすべての要素を置き換え、panelCの前にある最初のリストの一部をpanelDの前に置き換える」 。


他の人々が指摘したように-オブザーバーは役に立つかもしれません。

解析ツリー/ AST /制御フローグラフについて話している場合、グラフを変更したことをビューに通知する必要はありません。これを使用すると、グラフが検査され、検査によって「基本」表現が動的にビュー表現に変わるためです。

Swingで話している場合、1つのビューへの変更は他のビューで通知される必要があるため、ユーザーが見ることができるものは変更されます。

最後に-これは非常にケース固有の質問です。レイアウトと言語分析に使用する場合、完全なソリューションは大きく異なり、完全に一般的なソリューションは、地獄のように醜くて高価になります。


PS。上記の表現は醜く、作成されたアドホックなどです。実際のソリューションではなく、概念を示すことのみを目的としています。

1
Filip Malczak

ツリーがどのように変更されたかを正確に知っていると述べましたが、いつ知っていますか?

HashTreesまたはHashチェーン( Merkle Tree )または一般的に Error Detection の概念を試してみてはいかがでしょうか。ツリーが巨大な場合、たとえば最初のグラフをN/2またはルートNゾーンに分割し、それらのゾーンにハッシュ/チェックサムを割り当てることができます。依存ツリーは、最初のツリーのゾーンに依存する独自のN/2またはルートNゾーンのセットを維持します。最初のツリーで変更が検出されたら、単純なルックアップを使用して従属ツリーの対応するノードを更新します(変更されたものと、そのゾーンのハッシュ/チェックサムがわかっているため)。

1
sunny