算法模式,在需要时输出对如何到达解决方案的解释


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的应用程序中轻松显示输出。

什么是在需要时提供解释,而又在不需要解释时又不损害算法实时性能的良好模式?

Answers:


50

您要查找的“模式”称为“日志记录”,只需根据需要使日志记录语句变得冗长。通过使用体面的日志记录框架,您应该能够在运行时将其打开和关闭,提供不同的详细程度或针对不同目的(例如Web与控制台)定制输出。

如果这对性能有显着影响(即使关闭了日志记录),则可能取决于特定情况下所需的语言,框架和日志记录语句的数量。在编译语言中,如果这确实成为问题,则可以提供编译器开关来构建代码的“日志记录变体”和“非日志记录变体”。但是,我强烈建议您不要先进行优化,以防万一。


2
尽管它们不是像日志一样打开和关闭的东西,但是感觉像注释自我记录代码至少应该在有关“自我解释的算法”的问题中得到荣誉奖。
candied_orange

9
@CandiedOrange这个问题专门要求“解释”,其中包含实际的运行时值。在这种情况下,评论无济于事。
2013年

@metacubed哦,加油。我没有说这是日志记录的替代方法。查看问题标题并考虑通过此处的流量。
candied_orange

4
@CandiedOrange:我认为问题标题具有误导性,您是正确的,可以用这种方式解释问题,但这不是OP所要的。但我让我更正,我将编辑标题。
布朗

1
请注意,像treelog这样的东西是专门设计用来产生输出的,它通过产生完整的函数调用记录来解释复杂的计算。
蜘蛛鲍里斯(Boris)

7

观察者是一个很好的模式。https://zh.wikipedia.org/wiki/观察员模式

在算法中,要输出某些内容的每个点都会通知一些观察者。然后,他们决定要做什么,是在控制台上输出您的文本,还是将其发送到HTML引擎/ Apache等。

根据您的编程语言,可能有不同的方法来使其快速。例如,在Java中(为简洁起见,将其作为伪代码进行处理;将其与“ getters”,“ setters”一起设置为“正确”)留给读者:

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. 您可以将完整执行记录为一个日志条目,在算法步骤之间可能存在其他线程记录内容的情况下通常很有用
  2. 在此类的Java版本中(简称为“ Debug”),我没有将字符串添加为日志条目,而是添加了会生成字符串的lambda。仅当实际日志记录发生时才评估这些lambda,即,如果Debug对象发现其日志级别当前处于激活状态。这样,就没有不必要的构造日志字符串的性能开销。

编辑:正如其他人所评论的那样,lambda具有开销,因此您必须进行基准测试以确保此开销小于构造日志字符串所需的代码的不必要评估(日志条目通常不是简单的文字,但是涉及从中获取上下文信息参与对象)。


2
当然,创建lambda的开销
很大

1
塞尔吉奥(Sergio)阐述了但没有完全解释您的逻辑愚蠢。构造日志字符串的性能开销比构造lambda的性能开销低一个数量级。您在这里做出了非常糟糕的折衷
Kyeotic

2
@Tyrsius:您是否有可靠的基准证明?(您链接到的基准是有严重缺陷的,比照stackoverflow.com/questions/504103/...
梅里

1
@Tyrsius取决于具体情况。我也可以给您一个更相关的反例。您会看到String版本比Runnable慢一个数量级。这种情况更为现实,因为在此问题的上下文中,您将始终希望动态构建字符串。这总是需要创建Stringbuilder对象,而对于Lambda,它们仅在需要时(即,在登录时)创建。
jhyot '16

1
Lambda有开销,同意了。但是,在这种情况下发布的基准完全不相关。算法的日志记录通常涉及对其他代码的评估,如果跳过了日志记录(从参与对象中检索上下文信息等),这些代码将无法进行评估。Lambda避免的是此评估。但是您是对的,我在上面的回答确实假设lambda开销小于此开销,这是我没有经过一致测试的。
Cornel Masson

0

我通常会寻找分支,这意味着我会寻找if语句。因为这些表明我评估了一个值,所以它将控制算法的流程。在每种情况下(每种情况),我都可以记录选择的路径以及选择路径的原因。

所以基本上我会记录输入值(初始状态),每个选择的分支(条件)和进入选择的分支时的值(临时状态)。


1
这并不甚至试图解决这个问题问,关于不伤害算法的实时性能的解释时,没有必要
蚊蚋

我认为问题比这更笼统,并在设计水平上回答了这个问题。但是,如果这是一个问题,则可以在条件条件上添加一个标志,以设置是否要打印记录日志。启动时将此标志设置为参数。
理查德·泰瑞格里姆
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.