web-dev-qa-db-ja.com

必要なときにソリューションに到達する方法の説明を出力するアルゴリズムのパターン

次のシナリオが何度か私に起こりました。

特定の問題を解決するアルゴリズムをプログラミングしました。それは正常に動作し、正しい解決策を見つけます。ここで、アルゴリズムに「ソリューションにたどり着いた方法の完全な説明を書く」オプションを付けたいと思います。私の目標は、オンラインデモンストレーション、チュートリアルクラスなどでアルゴリズムを使用できるようにすることです。説明なしで、アルゴリズムをリアルタイムで実行するオプションがまだ欲しいです。使用するのに適したデザインパターンは何ですか?

例: 最大公約数を見つけるためのこのメソッド を実装するとします。現在実装されているメソッドは正しい答えを返しますが、説明はありません。メソッドにそのアクションを説明するためのオプションが欲しい:

Initially, a=6 and b=4. The number of 2-factors, d, is initialized to 0.
a and b are both even, so we divide them by 2 and increment d by 1.
Now, a=3 and b=2.
a is odd but b is even, so we divide b by 2.
Now, a=3 and b=1.
a and b are both odd, so we replace a by (a-b)/2 = 1.
Now, a=1 and b=1.
a=b, so the GCD is a*2^d = 2.

出力は、コンソールとWebベースのアプリケーションの両方で簡単に表示できるように返す必要があります。

説明が必要ないときにアルゴリズムのリアルタイムのパフォーマンスを損なうことなく、必要なときに説明を提供するための良いパターンは何ですか?

14

探している「パターン」は「ロギング」と呼ばれ、必要なだけログステートメントを冗長にします。まともなロギングフレームワークを使用することで、実行時にオンとオフを切り替えたり、さまざまな詳細レベルを提供したり、さまざまな目的(Webとコンソールなど)に合わせて出力を調整したりできます。

これがパフォーマンスに大きな影響を与える場合(ロギングがオフになっている場合でも)は、おそらく言語、フレームワーク、および特定のケースで必要なロギングステートメントの数によって異なります。コンパイルされた言語では、これが本当に問題になる場合、コードの「ロギングバリアント」と「非ロギングバリアント」を構築するコンパイラスイッチを提供できます。ただし、最初に測定せずに、「念のため」を最適化しないことを強くお勧めします。

50
Doc Brown

良いパターンはオブザーバーです。 https://en.wikipedia.org/wiki/Observer_pattern

アルゴリズムでは、何かを出力したい各ポイントで、オブザーバーに通知します。次に、コンソールでテキストを出力するか、HTMLエンジン/ Apacheなどに送信するかを決定します。

プログラミング言語によっては、高速化する方法が異なる場合があります。たとえば、Javaの場合(簡潔にするために、擬似コードとして扱います。ゲッター、セッターを使用して「正しい」ものにすることは、読者に任されています):

interface AlgoLogObserver {
   public void observe(String message);
}

class AlgorithmXyz {   
   AlgoLogObserver observer = null;
   void runCalculation() {   
       if (observer!=null) { oberserver.observe("Hello"); }
       ...
   }   
}

...
algo = new AlgorithmXyz();
algo.observer = new ConsoleLoggingObserver();  // yes, yes make a 
                                               // setter instead, or use Ruby :-)
algo.runCalculation();

これは少し冗長ですが、==nullのチェックはできるだけ速くする必要があります。

(一般的なケースでは、observerはおそらく複数のオブザーバーを許可する代わりにVector observersになることに注意してください。これはもちろん可能であり、オーバーヘッドが増えることはありません。空のVectorを使用する代わりに、observers=nullを設定して最適化を行います。)

もちろん、達成したいことに応じて、さまざまな種類のオブザーバーを実装します。そこにタイミング統計などを入れたり、その他の凝ったことをすることもできます。

7
AnoE

ストレートロギングのわずかな改善として、アルゴリズムの1回の実行をモデル化するある種のオブジェクトを作成します。コードが興味深いことをするたびに、このコンテナオブジェクトに「ステップ」を追加します。アルゴリズムの最後に、コンテナーからの累積ステップをログに記録します。

これにはいくつかの利点があります。

  1. すべての実行をoneログエントリとしてログに記録できます。アルゴステップの間に他のスレッドがログを記録する可能性がある場合に便利です。
  2. 私のJavaこのクラスのバージョン(単に「デバッグ」と呼ばれる)では、ログエントリとしてstringsを追加しませんが、-lambdasを追加しますこれらのラムダは、実際のログ記録が行われる場合にのみ評価されます(つまり、Debugオブジェクトがそのログレベルが現在アクティブ化されていることを検出した場合)。これにより、ログ文字列を不必要に作成することによるパフォーマンスオーバーヘッドが発生しません。

編集:他の人がコメントしたように、ラムダにはオーバーヘッドがあるため、このオーバーヘッドがログ文字列の構築に必要なコードの不要な評価よりも少ないことを確認するためにベンチマークする必要があります(ログエントリは多くの場合単純なリテラルではありませんが、コンテキスト情報を取得する必要があります参加オブジェクト)。

5
Cornel Masson

私は通常、分岐を探します。つまり、ifステートメントを探します。これらは私が値を評価することを示しているため、それがアルゴリズムのフローを制御します。そのような各発生(各条件)で、選択したパスと、それが選択された理由をログに記録できます。

したがって、基本的には、エントリの値(初期状態)、選択したすべてのブランチ(条件付き)、および選択したブランチに入るときの値(一時的な状態)をログに記録します。

0