调试和二进制搜索

时间:2009-05-09 19:52:09

标签: debugging binary-search programming-pearls

第2列(“AHA!算法”)中的“编程珍珠”讨论了二进制搜索如何帮助各种过程,如排序,树遍历。但它提到二进制搜索可以用于“程序调试”。有人可以解释一下这是怎么做的吗?

7 个答案:

答案 0 :(得分:8)

二进制搜索是efficient way,用于查找已排序列表中的项目。例如,如果您正在寻找书中的特定页面(例如,第147页),您可以在中间附近打开书籍,并确定打开的页面是在您要查找的页面之前还是之后。然后你选择你缩小它的部分并重复这个过程:将它分成两半并确定哪一半包含第147页。更好的是,你可以猜到第147页的距离 - 如果这本书是非常长,接近一本短篇小说的结尾 - 并将该猜测作为第一个分割点。二元搜索的这种变化称为interpolation search

因此,如果您有一个错误和可能隐藏的排序列表,插值搜索通常是压缩它的方法。其他答案解释了在一系列行或源代码提交中隐藏的错误的常见情况。但该技术可以应用于其他情况:

  • 日志搜索

    在一个长期运行的系统上,尤其是处理如此多数据的系统,你必须每天轮换你的日志,所以今天看到几周/几个月/几年前很好的事情并不罕见。使用复杂的互锁系统,可以在不进行任何代码更改的情况下发现错误。找到硬件,网络,操作系统,配置中的变化(虽然应该与代码一起存储),输入,手动程序等可能很困难,因为很多这些事情都会改变很久时间段。对日志(无论是在表格还是在文件中)进行全文搜索通常是不切实际的。

    在这种情况下,几乎没有任何选择,只能在中间某处打开日志,看看问题是否存在。然后剪切你知道bug隐藏的部分并再次查找bug。最终,您应该能够发现您的错误出现的第一时刻,这使得找到罪魁祸首变得更加容易。

  • 输入搜索

    前几天,我注意到obscure "bug" with long text。追踪文本和破坏系统的文本之间的确切边界的最快方法是将文本切成两半,直到找到分界线。 (原来I'm an idiot,但我做得更好counting bananas。)

  • 概念流程步骤

    大多数人甚至不知道他们大部分时间都在使用二进制(或更好的插值)搜索;这是解决问题的一种自然方式。在考虑包含潜在错误的一系列步骤时,首先检查其中一个中间步骤的输出通常是明智的,以避免检查整个代码,只是发现问题是在最后一步。

    < / LI>

答案 1 :(得分:7)

如果您不知道100行程序中的哪一行是错误的,那么您将尝试运行前50行并跳过其余行。如果问题出现,您就会知道第一个段包含错误。接下来你会尝试拆分它并运行前25行并查看问题是否存在等等,直到找到足够短的部分来查看。

二元搜索背后的想法是识别/隔离一个有缺陷的小区域。但是,与所有方法一样,这并不适用于所有情况。例如:递归函数对于这样的工具来说将非常笨拙。当执行路径太多时,将要运行的代码分段可能会变得困难。

答案 2 :(得分:6)

另一种可能性是你有一个错误,并且你知道它在你的二月发行版中没有,但它是在你的四月发行版(或者更确切地说,你的四月发布候选人 - 你永远不会真正向你的用户发送错误,对吗?)。

您可以通过修订控制历史记录进行手动二进制搜索,以便在引入错误时缩小范围。首先检查两个版本之间的代码,构建它,然后查看是否存在错误。保持分区,直到找到它的介绍时间。如果你不知道从哪里开始寻找bug,这可能非常有效,特别是如果你做了相当小的提交。

这适用于Subversion,因为它具有存储库范围的修订号。如果您的二月发行版是533版本,而您的四月版本是rev 701,那么您将更新为版本617,测试它,然后从那里开始。 (实际上,我通常会到600,所以我不需要在脑子里做那么多的数学运算。)一旦我开始缩小范围,我就开始查看提交注释并做出有根据的猜测(“我真的没有认为这个提交会破坏它“),所以我通常不需要做所有log 2 (n)签出。

我从未使用Git,但他们使用内置的“bisect”命令更进了一步。你给它一个起点(什么时候可以工作?)和结束点(你什么时候注意到它被打破了?),它会自动获得二进制搜索中间点的代码。然后在你构建并测试之后,你告诉它这个转速是通过还是失败;然后它获取下一个中间点的代码。您甚至可以告诉它为每个rev运行一个命令,并使用命令的退出代码来确定rev是通过还是失败,此时它可以全自动运行。

答案 3 :(得分:4)

二进制搜索可以通过以下方式帮助调试:

  1. 假设控制必须达到某一点而你怀疑它没有。将print语句放在第一个和最后一个代码行中。假设您看到第一个但不是第二个语句的结果。将打印声明放在中间,然后重试。这样,您可以在代码行的空间上使用二进制搜索来查找错误。
  2. 假设您使用版本控制系统。版本10通过了所有测试。即将发布的第70版未通过一些测试。查看版本40并对其进行测试。如果它工作正常,请尝试版本55.如果版本40失败,请尝试版本25.这样您就可以在程序版本空间上使用二进制搜索,以便在错误进入程序的第一个版本中进行归零。

答案 4 :(得分:1)

假设你有一个错误,但你不知道它在哪里。您可以随机或单步执行代码放置断点,验证每个站点的数据。但是,更好的策略是在您正在查看的代码块中间选择一个位置。如果那里存在问题,那么在开始和当前点之间选择一个点并再次尝试。如果问题不存在,则在当前点和结束点之间选择一个点,然后再试一次。继续这样做,直到您将代码量缩小到足够大的块,以便比停止/重新启动更有效地单步执行。这基本上是对你的代码进行二进制搜索。

答案 5 :(得分:0)

您可以注释掉代码,添加日志记录注释或只是设置断点

非常适用于没有错误但无功能的代码&amp;你充满了自我怀疑

首先在代码中间设置断点,如果一切顺利,那么你知道问题不存在

然后将其设置为代码点的75% - 如果问题出现在这里,那么你知道它在50%和50%之间的代码中。 75%

接下来你将它设置为57%

如果问题出现了,那么你再把它分成两半

基本上你可以在几分钟内找到问题,而不是花费智力时间重新分析你的代码

然后它仍然取决于你解决它。

答案 6 :(得分:0)

完整算法称为 Delta Debugging ,由信息学教授Andreas Zeller和本书作者Why programs fail开发。

但是,这不仅仅是二进制搜索。二进制搜索仅在开始时完成,一旦二进制搜索不再使输入最小化,则采用另一种方法。

完整的算法并不难理解,实际上非常简单。但是,有时很难重现该错误并应用该问题是否已被复制。

除了这本书,还有一个关于Udacity的免费在线课程。如果您更喜欢简短版本,请阅读他的IEEE paper