类似于TDD的算法问题解决方法


10

我在Codility的一次算法测试中失败了,因为我试图找到一种更好的解决方案,最后我一无所有。

因此,这让我开始思考是否可以使用类似于TDD的方法?即我是否通常可以以类似的方式逐步开发解决方案?

如果我正在编写排序算法,则可以从标准Bubblesort转换为2-way Bubbleort,但是诸如Quicksort之类的更高级的东西将是“量子飞跃”,但至少我拥有可以轻松验证的测试数据。

有关此类测试的其他提示?下次我要做的一件事是使用比内部循环更多的方法/函数。例如,在排序中,您通常需要交换。如果是一种方法,我只需要修改调用代码。我什至可以拥有更高级的解决方案作为派生类。

对于“算法”与“常规”问题,我指的是时间复杂性很重要的问题。因此,您不必像在TDD中那样通过更多的测试,而应使其“表现得更好”。

“类似于TDD”是指:

  1. 编写相对自动的测试以节省手动测试pr增量的时间。
  2. 增量发展。
  3. 回归测试,能够检测代码是否中断或至少在两次增量之间功能是否发生了变化。

如果您比较一下,我认为这应该很容易理解

  1. 直接编写shell排序
  2. 从Bubblesort跳到quicksort(完全重写)
  3. 从单向冒泡排序逐步转换为外壳排序(如果可能)。

“类似于TDD”是什么意思?您显然可以尝试使用TDD来开发排序功能,然后使用单元测试来验证以更高效的排序算法代替排序算法时该功能仍然有效,但这听起来像是您在想一个其他问题?
布朗

“渐进地” :-)-参见最后一句添加的“ So相反...”
Olav

2
当然,您可以尝试使用一个可行的(但不是非常有效的)解决方案来解决许多问题,然后对其进行改进。这绝不限于算法或编程问题,并且与TDD没有太多共同之处。这回答了你的问题了吗?
布朗

@DocBrown否-请参见Bubblesort / Quicksort示例。TDD之所以“行之有效”,是因为采用增量方法可以解决许多类型的问题。算法问题可能有所不同。
奥拉夫

因此,您的意思是“有可能以增量方式解决算法设计问题”(就像TDD是增量方式一样),而不是“按TDD”,对吗?请说清楚。
布朗

Answers:


9

另请参见罗恩·杰弗里斯(Ron Jeffries)尝试使用TDD创建Sudoku求解器的尝试,不幸的是这没有用。

算法需要对算法设计原理有深入的了解。有了这些原则,确实有可能像Peter Norvig一样逐步制定计划。

实际上,对于需要轻而易举的设计工作的算法,几乎总是在本质上是渐进的。但是,每个“增量” 在算法设计者看来都是微不足道的,对于那些没有专门知识或知识的人来说,这似乎是一个巨大的飞跃(借用您的话)。

这就是为什么CS理论的基础知识与大量算法编程实践相结合同样重要的原因。知道存在一种特定的“技术”(算法的小组成部分),是实现这些渐进式量子飞跃的漫长道路。


但是,算法的增量进度和TDD之间存在一些重要的区别。

JeffO提到了一种差异:验证输出数据正确性的测试与断言相同算法的不同实现(或争夺相同解决方案的不同算法)之间的性能的测试是分开的。

在TDD中,以测试的形式添加了新的要求,并且该测试最初不应通过(红色)。然后满足要求(绿色)。最后,代码被重构。

在算法开发中,要求通常不会改变。结果正确性验证测试要么首先编写,要么在算法的草拟(高度自信但缓慢)实现完成后不久编写。数据正确性测试很少更改;TDD仪式的一部分不会将其更改为失败(红色)。

但是,在这方面,数据分析与算法开发有明显的不同,因为在人们的理解中,数据分析要求(输入集和预期结果)仅是宽松定义的。因此,需求在技术水平上经常变化。这种快速的变化使数据分析介于算法开发和通用软件应用程序开发之间,尽管仍然占用大量算法资源,但对于任何程序员而言,需求也“太快了”。

如果需求发生变化,通常会要求使用其他算法。

在算法开发中,将性能比较测试更改(加强)为失败(红色)是很愚蠢的-它不能使您对可以改善性能的算法潜在更改有任何了解。

因此,在算法开发中,正确性测试和性能测试都不是TDD测试。相反,两者都是回归测试。具体而言,正确性回归测试可防止您更改会破坏其正确性的算法。性能测试可防止您对算法进行更改,使其运行速度变慢。

您仍然可以将TDD合并为个人工作方式,除了“红色-绿色-重构”仪式不是严格必要的,也不是对算法开发的思想过程特别有益。

我认为,算法的改进实际上是由于对当前算法的数据流程图进行了随机(不一定是正确的)排列,或者是在先前已知的实现之间进行了混合和匹配。


当有多个需求可以逐步添加到测试集中时,将使用TDD 。

另外,如果您的算法是数据驱动的,则可以逐条添加每个测试数据/测试用例。TDD也将是有用的。因此,“添加新的测试数据-改进代码以使其正确处理此数据-重构”的类似于“ TDD”的方法也适用于开放式数据分析工作,其中算法的目标在人类中进行了描述。以人类为中心的词语也可以判断以中枢为中心的单词及其成功程度。

它宣称教的方式,使其少压倒性不是试图以满足在单一的尝试要求所有(几十或几百个)。换句话说,当您在实施解决方案的某些早期草案时可以指示某些需求或延展目标可以暂时忽略时,将启用TDD 。

TDD不能替代计算机科学。它是一种心理拐杖,可以帮助程序员克服必须立即满足许多要求的烦恼。

但是,如果您已经有一个实现正确的结果的实现,则TDD会认为其目标已实现,并且代码已经准备好交付(重构或交给另一个程序员用户)。从某种意义上说,它鼓励您不要过早地优化代码,方法是客观地向您发出代码“足够好”(通过所有正确性测试)的信号。


在TDD中,也着重于“微观需求”(或隐藏的品质)。例如,参数验证,断言,异常抛出和处理等。TDD帮助确保在软件执行的正常过程中很少执行的执行路径的正确性。

某些类型的算法代码也包含这些内容。这些都适合TDD。但是由于算法的一般工作流程不是TDD,因此此类测试(关于参数验证,断言以及异常引发和处理的测试)倾向于在(至少部分)编写实现代码之后编写。


您帖子中前两个引用的单词(“鲍勃叔叔”)是什么意思?
罗伯特·哈维

@RobertHarvey根据Bob叔叔的说法,TDD可用于算法发现。根据另一位专家的说法,它不起作用。我认为应该同时提及这两个方面(即,只要有人提到一个例子,一个人也必须提到另一个例子),这样人们就可以获得关于正面和负面例子的平衡信息。
rwong

好。但是你明白我的困惑吗?您的第一段似乎是引用某人说出“鲍勃叔叔”的字样。谁在说?
罗伯特·哈维

已执行@RobertHarvey订单。
rwong

2

对于您的问题,您将进行两个测试:

  1. 进行测试以确保算法仍然正确。例如,排序正确吗?
  2. 性能比较测试-性能有所提高。这可能会很棘手,因此有助于在同一台机器,相同的数据上运行测试并限制任何其他资源的使用。专用机器会有所帮助。

测试什么或全面测试范围尚待商,,但我认为需要微调(更改很多)的应用程序复杂部分是自动化测试的理想选择。应用程序的这些部分通常很早就被确定。在这一部分中使用TDD方法是有意义的。

不愿意尝试各种方法不会妨碍能够解决复杂的问题。进行自动化测试应该会在这方面有所帮助。至少您会知道自己并没有变得更糟。


1

因此,您不必像在TDD中那样通过更多的测试,而应使其“表现得更好”。

有点。

您可以测试运行时间和复杂性。运行时测试需要稍微宽容,以允许对系统资源的争用。但是,在许多语言中,您也可以覆盖或注入方法并计算实际的函数调用。而且,如果您确信当前的算法不是最佳选择,则可以引入一种测试,该测试只要求sort()调用compare()比幼稚的实现更少的时间。

或者,您可以比较sort()两组,并确保它们compare()根据目标复杂性(或大约,如果您期望有些不一致的话)在呼叫中扩展。

而且,如果你能在理论上证明了一组大小N应该严格要求不超过N*log(N)比较,它可能是合理的限制你已经努力sort()N*log(N)的调用compare()...

但是...

满足性能或复杂性要求并不能确保基础实现为{AlgorithmX}。而且,我认为这通常是可以的。只要使用的算法满足您的要求(包括任何重要的复杂性,性能或资源要求),使用哪种算法都无关紧要。

但是,如果您要确保使用特定的算法,则需要更加乏味并且深入得多。像..

  • 确保准确拨打compare()swap()(或执行其他任何操作)的预期次数
  • 包装所有主要功能/方法,并使用可预测的数据集确保调用按预期的顺序进行
  • 在N个步骤中的每个步骤之后检查工作状态,以确保其完全按照预期进行更改

但同样,如果你想确保{} AlgorithmX在特定的使用,有可能是功能的{} AlgorithmX您关心的是测试是否比更重要{} AlgorithmX在年底实际使用...


还请记住,TDD不会否定软件开发中的研究,思考或计划的必要性。即使您无法轻松地在自动化测试套件中使用 Google搜索和白板功能,它也不会否定头脑风暴和实验的需要


关于确定操作数界限的可能性的极好点。我会说这是(模拟,存根和间谍)的力量,可以在TDD中使用,也可以在其他类型的测试中使用。
rwong

在测试场景中,在代码之前编写测试没有多大意义。满足功能标准后,通常是最后一个标准。有时您可能会先做随机数,然后再做最坏的情况,但是与一些聪明的打印输出相比,编写测试仍会花费很长时间。(通过一些统计信息,您可能可以编写一些通用代码,但不能在测试过程中编写代码)在现实世界中,我认为您想知道在某些情况下性能是否突然下降。
奥拉夫

如果您查看codility.com/programmers/task/stone_wall,您会知道您是否具有大于N的复杂度,除了特殊情况下(例如,您必须在很长的跨度内工作)。
奥拉夫

@Olav“与一些聪明的打印输出相比,写作测试将花费很长时间”……要做一次 ……呃.. 也许,但也值得商de。每次构建都要重复吗?... 当然不。
svidgen

@Olav“在现实世界中,我想您想知道在某些情况下性能是否突然下降。” 在实时服务中,您将使用诸如New Relic之类的工具来监视整体性能-而不仅仅是某些方法。理想情况下,您的测试将告诉您性能关键的模块和方法何时部署未能达到预期。
svidgen
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.