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


14

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

特定の問題を解決するアルゴリズムをプログラムしました。正常に機能し、正しい解決策を見つけます。今、私はアルゴリズムに「あなたが解決策に到達した方法の完全な説明を書く」ことを伝えるオプションが欲しいです。私の目標は、オンラインデモ、チュートリアルクラスなどでアルゴリズムを使用できるようにすることです。説明なしでリアルタイムにアルゴリズムを実行するオプションが引き続き必要です。使用する良いデザインパターンは何ですか?

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

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ベースのアプリケーションの両方で簡単に表示できるように返される必要があります。

説明が不要なときにアルゴリズムのリアルタイムパフォーマンスを損なわずに、必要なときに説明を提供するのに適したパターンは何ですか?

回答:


50

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

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


2
ロギングのようにオン/オフを切り替えるものではありませんが、コメント自己文書化コードは、少なくとも「それ自体を説明するアルゴリズム」についての質問で名誉ある言及を受けるべきだと感じています。
candied_orange

9
@CandiedOrangeは、実際の実行時の値が含まれる「説明」を明確に求める質問です。その場合、コメントはあまり役に立ちません。
16年

@metacubedおっと。ロギングに代わるものとは言いませんでした。質問のタイトルを見て、ここを通過するトラフィックについて考えてください。
candied_orange

4
@CandiedOrange:質問のタイトルは誤解を招くと思います、あなたはそれがそのように解釈されるかもしれないことは正しいですが、それはOPが求めているものではありません。しかし、私はそれを修正させて、タイトルを編集します。
ドックブラウン

1
treelogのようなものは、関数呼び出しの完全なレコードを生成することにより、複雑な計算を説明する出力を生成するように特別に設計されていることに注意してください。
クモのボリス

7

良いパターンはオブザーバーです。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代わりに、複数の観測を可能にするために、これはうまくとしてはもちろん可能であり、より多くのオーバーヘッドにつながることはありません。あなたはまだあなたが設定することを最適化して置くことができobservers=nullたのではなく、空Vectorです。)

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


5

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

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

  1. 完全な実行を1つのログエントリとして記録できます。これは、他のスレッドがアルゴリズムのステップの間に何かを記録する可能性がある場合に役立ちます。
  2. このクラスのJavaバージョン(単に「デバッグ」と呼ばれる)では、ログエントリとして文字列を追加するのではなく、文字列を生成するラムダを追加します。これらのラムダは、実際のロギングが行われる場合、つまり、デバッグオブジェクトがそのログレベルが現在アクティブになっている場合にのみ評価されます。このように、不必要にログ文字列を構築することによるパフォーマンスのオーバーヘッドはありません。

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


2
作成のオーバーヘッドが、当然のことながら、あるラムダは ...
セルジオTulentsev

1
セルジオは、あなたの論理の愚かさを軽視しますが、完全には説明しません。ログ文字列を構築するパフォーマンスオーバーヘッドは、ラムダを構築するパフォーマンスオーバーヘッドよりも桁違いに低くなっています。あなたはここで非常に貧弱なトレードオフをしました
-Kyeotic

2
@Tyrsius:それを証明する信頼できるベンチマークはありますか?(あなたが深く欠陥があるためにリンクベンチマーク、CF stackoverflow.com/questions/504103/...
メリオン

1
@Tyrsiusそれはすべて特定の状況に依存します。また、ほぼ間違いなく、より関連性の高い反例を示します。StringバージョンはRunnableよりも桁違いに遅いことがわかります。この質問の文脈では、常に動的に文字列を構築する必要があるため、このケースはより現実的です。これは常に Stringbuilderオブジェクトの作成を必要としますが、Lambdaでは必要なとき(つまり、ログがオンのとき)にのみ作成されます。
-jhyot

1
ラムダにはオーバーヘッドがあります。ただし、このコンテキストでは、掲載されているベンチマークは完全に無関係です。アルゴリズムのロギングには、ロギングがスキップされた場合には評価されなかった他のコードの評価が含まれることがよくあります(参加オブジェクトからコンテキスト情報を取得するなど)。ラムダが回避するのはこの評価です。しかし、あなたは正しいです、上記の私の答えは、ラムダのオーバーヘッドがこのオーバーヘッドよりも小さいことを前提としています。私は一貫してテストしていません。
コーネルマッソン

0

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

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


1
求めていたこのさえについての説明がされているアルゴリズムのリアルタイム性能傷つけない、問題に対処しようとしませんではない必要
ブヨ

それよりも一般的な質問を取り上げ、設計レベルで答えました。しかし、それが懸念される場合は、ログに印刷するかどうかを設定するフラグを条件に追加します。起動時にこのフラグをパラメーターとして設定します。
リチャードティレグリム
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.