# 减少导致失败的输入

> 原文： [https://www.fuzzingbook.org/html/Reducer.html](https://www.fuzzingbook.org/html/Reducer.html)

通过构造，模糊器创建的输入可能很难阅读。 当人必须分析故障的确切原因时，这会在*调试*期间引起问题。 在本章中，我们介绍了*自动将故障产生的输入减少并简化为最小*以便于调试的技术。

**前提条件**

*   用于减少的简单“增量调试”技术没有特定的先决条件。
*   由于还原通常与模糊测试一起使用，因此阅读有关基本模糊测试的[一章是一个好主意。](Fuzzer.html)
*   较后的基于语法的技术需要[派生树](GrammarFuzzer.html)和[解析](Parser.html)的知识。

## 内容提要

要使用本章中提供的代码来[，请编写](Importing.html)

```py
>>> from [fuzzingbook.Reducer](Reducer.html) import <identifier>

```

然后利用以下功能。

*减速器*接收引起故障的输入，并将其减小到仍可重现故障的最小值。 本章提供实现此类减速器的`Reducer`类。

这是一个简单的示例：算术表达式在Python解释器中导致错误：

```py
>>> !python -c 'x = 1 + 2 * 3 / 0'
Traceback (most recent call last):
  File "<string>", line 1, in <module>
ZeroDivisionError: division by zero

```

我们可以将输入减少到最小吗？ 要使用`Reducer`，首先必须构建一个`Runner`，如果发生精确错误，其结果为`FAIL`。 因此，我们构建了一个`ZeroDivisionRunner`，如果出现`ZeroDivisionError`，则其`run()`方法将特别返回`FAIL`结果。

```py
>>> from [Fuzzer](Fuzzer.html) import ProgramRunner
>>> class ZeroDivisionRunner(ProgramRunner):
>>>     """Make outcome 'FAIL' if ZeroDivisionError occurs"""
>>>     def run(self, inp=""):
>>>         result, outcome = super().run(inp)
>>>         if result.stderr.find('ZeroDivisionError') >= 0:
>>>             outcome = 'FAIL'
>>>         return result, outcome

```

如果我们将此表达式输入`ZeroDivisionRunner`，它将按设计产生`FAIL`的结果。

```py
>>> python_input = "x = 1 + 2 * 3 / 0"
>>> python_runner = ZeroDivisionRunner("python")
>>> result, outcome = python_runner.run(python_input)
>>> outcome
'FAIL'

```

增量调试是一种简单而强大的归约算法。 我们可以将`DeltaDebuggingReducer`绑定到该运行程序，并让它确定导致`python`程序失败的子字符串：

```py
>>> dd = DeltaDebuggingReducer(python_runner)
>>> dd.reduce(python_input)
'3/0'

```

输入减少到最大：我们得到除以零的本质。

## 为什么要减少？

在这一点上，我们已经看到了许多测试生成技术，它们以某种形式都产生输入以触发故障。 如果它们成功-也就是说程序实际上失败-我们必须找出失败发生的原因以及如何修复它。

这是这种情况的一个例子。 我们有一个带有`run()`方法的类`MysteryRunner`类-在给出其代码的情况下-有时会失败。 但是，这实际上是在什么情况下发生的呢？ 为了使这种情况不明显，我们故意遮盖了确切的条件。

```py
import [fuzzingbook_utils](https://github.com/uds-se/fuzzingbook/tree/master/notebooks/fuzzingbook_utils)

```

```py
from [Fuzzer](Fuzzer.html) import RandomFuzzer, Runner

```

```py
import [re](https://docs.python.org/3/library/re.html)

```

```py
class MysteryRunner(Runner):
    def run(self, inp):
        x = inp.find(chr(0o17 + 0o31))
        y = inp.find(chr(0o27 + 0o22))
        if x >= 0 and y >= 0 and x < y:
            return (inp, Runner.FAIL)
        else:
            return (inp, Runner.PASS)

```

让我们对函数进行模糊处理，直到找到失败的输入。

```py
mystery = MysteryRunner()
random_fuzzer = RandomFuzzer()
while True:
    inp = random_fuzzer.fuzz()
    result, outcome = mystery.run(inp)
    if outcome == mystery.FAIL:
        break

```

```py
failing_input = result
failing_input

```

```py
' 7:,>((/$$-/->.;.=;(.%!:50#7*8=$&&=$9!%6(4=&69\':\'<3+0-3.24#7=!&60)2/+";+<7+1<2!4$>92+$1<(3%&5\'\'>#'

```

此输入中的某些内容导致`MysteryRunner`失败。 那是什么

## 手动减少输入

调试过程中的重要一步是*降低* –也就是说，确定与发生故障相关的故障情况，并*省略*（如果可能） 那不是。 正如Kernighan和Pike [Kernighan *等人*，1999。]所说：

> 对于每种情况下的问题，请检查是否与发生问题有关。 如果不是，请将其从问题报告或相关的测试案例中删除。

专门针对输入，他们建议进行*分治*流程：

> 通过二进制搜索进行。 扔掉一半的输入，看看输出是否仍然错误； 如果不是，则返回先前的状态并丢弃输入的另一半。

这是我们可以轻松尝试的方法。 例如，我们可以看到如果仅在前半部分进纸，是否仍会发生错误：

```py
half_length = len(failing_input) // 2   # // is integer division
first_half = failing_input[:half_length]
mystery.run(first_half)

```

```py
(" 7:,>((/$$-/->.;.=;(.%!:50#7*8=$&&=$9!%6(4=&69':", 'PASS')

```

不，仅上半年是不够的。 也许下半年？

```py
second_half = failing_input[half_length:]
mystery.run(second_half)

```

```py
('\'<3+0-3.24#7=!&60)2/+";+<7+1<2!4$>92+$1<(3%&5\'\'>#', 'PASS')

```

这也不是很好。 我们仍然可以通过删除*个较小的块*来进行操作，例如，一个字符接一个字符。 如果我们的测试是确定性的并且容易重复，那么很显然，此过程最终将减少输入。 但是，这仍然是一个效率很低的过程，特别是对于长输入。 我们需要的是*策略*，该策略可有效地最小化导致故障的输入-该策略可以自动化。

## 增量调试

有效减少故障导致输入的一种策略是*增量调试* [ [Zeller *等人*，2002。](https://doi.org/10.1109/32.988498)。 Delta Debugging实现了上面列出的“二进制搜索”策略，但有一个转折：如果没有一半失败（也如上所述），它将不断从输入中删除越来越小的块，直到消除单个字符为止。 因此，在切除了上半年之后，我们切除了第一季度，第二季度，依此类推。

让我们在我们的示例中对此进行说明，然后看看如果我们削减第一季度会发生什么。

```py
quarter_length = len(failing_input) // 4
input_without_first_quarter = failing_input[quarter_length:]
mystery.run(input_without_first_quarter)

```

```py
('50#7*8=$&&=$9!%6(4=&69\':\'<3+0-3.24#7=!&60)2/+";+<7+1<2!4$>92+$1<(3%&5\'\'>#',
 'FAIL')

```

啊! 这失败了，并将失败的输入减少了25％。 让我们删除另一个季度。

```py
input_without_first_and_second_quarter = failing_input[quarter_length * 2:]
mystery.run(input_without_first_and_second_quarter)

```

```py
('\'<3+0-3.24#7=!&60)2/+";+<7+1<2!4$>92+$1<(3%&5\'\'>#', 'PASS')

```

这并不奇怪，因为我们之前有过这样一个：

```py
second_half

```

```py
'\'<3+0-3.24#7=!&60)2/+";+<7+1<2!4$>92+$1<(3%&5\'\'>#'

```

```py
input_without_first_and_second_quarter

```

```py
'\'<3+0-3.24#7=!&60)2/+";+<7+1<2!4$>92+$1<(3%&5\'\'>#'

```

那去掉第三季度呢？

```py
input_without_first_and_third_quarter = failing_input[quarter_length:
                                                      quarter_length * 2] + failing_input[quarter_length * 3:]
mystery.run(input_without_first_and_third_quarter)

```

```py
("50#7*8=$&&=$9!%6(4=&69':<7+1<2!4$>92+$1<(3%&5''>#", 'PASS')

```

好。 让我们删除第四季度。

```py
input_without_first_and_fourth_quarter = failing_input[quarter_length:quarter_length * 3]
mystery.run(input_without_first_and_fourth_quarter)

```

```py
('50#7*8=$&&=$9!%6(4=&69\':\'<3+0-3.24#7=!&60)2/+";+', 'FAIL')

```

是! 这已经成功了。 现在，我们的输入减少了50％。

现在，我们已尝试删除组成原始失败字符串的$ \ frac {1} {2} $和$ \ frac {1} {4} $的片段。 在下一次迭代中，我们将删除甚至更小的碎片– $ \ frac {1} {8} $，$ \ frac {1} {16} $等。 我们一直继续到$ \ frac {1} {97} $ –即单个字符为止。

但是，这是我们高兴地让计算机为我们做的事情。 我们首先介绍一个`Reducer`类，作为所有异径管的抽象超类。 `test()`方法运行一个测试（如果需要，可以进行日志记录）； `reduce()`方法最终将输入减少到最小。

```py
class Reducer(object):
    def __init__(self, runner, log_test=False):
        """Attach reducer to the given `runner`"""
        self.runner = runner
        self.log_test = log_test
        self.reset()

    def reset(self):
        self.tests = 0

    def test(self, inp):
        result, outcome = self.runner.run(inp)
        self.tests += 1
        if self.log_test:
            print("Test #%d" % self.tests, repr(inp), repr(len(inp)), outcome)
        return outcome

    def reduce(self, inp):
        self.reset()
        # Default: Don't reduce
        return inp

```

`CachingReducer`变体保存测试结果，这样我们就不必一次又一次地运行相同的测试：

```py
class CachingReducer(Reducer):
    def reset(self):
        super().reset()
        self.cache = {}

    def test(self, inp):
        if inp in self.cache:
            return self.cache[inp]

        outcome = super().test(inp)
        self.cache[inp] = outcome
        return outcome

```

这是*增量调试*减速器。 Delta Debugging实施上述策略：首先删除大小为\\ frac {1} {2} $的较大块； 如果这没有失败，那么我们将继续处理大小为$ \ frac {1} {4} $，然后是$ \ frac {1} {8} $的块，依此类推。

我们的实现使用与[ [Zeller *等人*等，2002。](https://doi.org/10.1109/32.988498)]中的Zeller几乎相同的Python代码。 唯一的不同是，它已被修改为可以在Python 3和我们的`Runner`框架上运行。 变量`n`（最初为2）表示粒度–在每个步骤中，将切掉大小为\\ frac {1} {n} $的块。 如果所有测试均未通过（`some_complement_is_failing`为False），则将`n`加倍-直到达到输入长度为止。

```py
class DeltaDebuggingReducer(CachingReducer):
    def reduce(self, inp):
        self.reset()
        assert self.test(inp) != Runner.PASS

        n = 2     # Initial granularity
        while len(inp) >= 2:
            start = 0
            subset_length = len(inp) / n
            some_complement_is_failing = False

            while start < len(inp):
                complement = inp[:int(start)] + \
                    inp[int(start + subset_length):]

                if self.test(complement) == Runner.FAIL:
                    inp = complement
                    n = max(n - 1, 2)
                    some_complement_is_failing = True
                    break

                start += subset_length

            if not some_complement_is_failing:
                if n == len(inp):
                    break
                n = min(n * 2, len(inp))

        return inp

```

要查看`DeltaDebuggingReducer`的工作方式，让我们在失败的输入上运行它。 在每一步中，我们看到剩余的输入如何变得越来越小，直到只剩下三个字符为止：

```py
dd_reducer = DeltaDebuggingReducer(mystery, log_test=True)
dd_reducer.reduce(failing_input)

```

```py
Test #1 ' 7:,>((/$$-/->.;.=;(.%!:50#7*8=$&&=$9!%6(4=&69\':\'<3+0-3.24#7=!&60)2/+";+<7+1<2!4$>92+$1<(3%&5\'\'>#' 97 FAIL
Test #2 '\'<3+0-3.24#7=!&60)2/+";+<7+1<2!4$>92+$1<(3%&5\'\'>#' 49 PASS
Test #3 " 7:,>((/$$-/->.;.=;(.%!:50#7*8=$&&=$9!%6(4=&69':" 48 PASS
Test #4 '50#7*8=$&&=$9!%6(4=&69\':\'<3+0-3.24#7=!&60)2/+";+<7+1<2!4$>92+$1<(3%&5\'\'>#' 73 FAIL
Test #5 "50#7*8=$&&=$9!%6(4=&69':<7+1<2!4$>92+$1<(3%&5''>#" 49 PASS
Test #6 '50#7*8=$&&=$9!%6(4=&69\':\'<3+0-3.24#7=!&60)2/+";+' 48 FAIL
Test #7 '\'<3+0-3.24#7=!&60)2/+";+' 24 PASS
Test #8 "50#7*8=$&&=$9!%6(4=&69':" 24 PASS
Test #9 '9!%6(4=&69\':\'<3+0-3.24#7=!&60)2/+";+' 36 FAIL
Test #10 '9!%6(4=&69\':=!&60)2/+";+' 24 FAIL
Test #11 '=!&60)2/+";+' 12 PASS
Test #12 "9!%6(4=&69':" 12 PASS
Test #13 '=&69\':=!&60)2/+";+' 18 PASS
Test #14 '9!%6(4=!&60)2/+";+' 18 FAIL
Test #15 '9!%6(42/+";+' 12 PASS
Test #16 '9!%6(4=!&60)' 12 FAIL
Test #17 '=!&60)' 6 PASS
Test #18 '9!%6(4' 6 PASS
Test #19 '6(4=!&60)' 9 FAIL
Test #20 '6(460)' 6 FAIL
Test #21 '60)' 3 PASS
Test #22 '6(4' 3 PASS
Test #23 '(460)' 5 FAIL
Test #24 '460)' 4 PASS
Test #25 '(0)' 3 FAIL
Test #26 '0)' 2 PASS
Test #27 '(' 1 PASS
Test #28 '()' 2 FAIL
Test #29 ')' 1 PASS

```

```py
'()'

```

现在我们知道`MysteryRunner`失败的原因-只要输入包含两个匹配的括号并在它们之间有一个数字就足够了。 增量调试确定这是29个步骤。 其结果是 *1个最小值*，这意味着需要使用每个字符来产生错误； 删除任何内容（如上述测试`#27`和`#29`所示）不再会使测试失败。 此属性由增量调试算法保证，该算法在最后阶段始终尝试逐个删除字符。

简化的测试案例（例如上述案例）有很多优点：

*   减少的测试用例**减少了程序员**的*认知负荷*。 测试用例更短，更集中，因此不会给程序员带来不相关的细节。 减少的输入通常会导致执行时间缩短和程序状态变小，这两者都会在了解bug时减少搜索空间。 在我们的案例中，我们消除了许多不相关的输入-只有减少的输入中包含的两个字符才是相关的。

*   简化的测试案例**更易于传达**。 这里所有需要的是摘要：`MysteryRunner fails on "()"`，它比`MysteryRunner fails on a 4100-character input (attached)`好得多。

*   简化的测试案例有助于**识别重复项**。 如果已经报告了类似的错误，并且已将所有错误归结为相同的原因（即，输入中包含匹配的括号），那么很明显，所有这些错误都是同一根本原因的不同症状，并且都是 一次代码修复即可解决。

增量调试的效果如何？ 在最佳情况下（当左半部分或右半部分发生故障时），测试次数与输入长度$ n $成对数关系（即$ O（\ log_2 n）$）； 这与二进制搜索的复杂度相同。 但是，在最坏的情况下，增量调试可能需要大量与$ n ^ 2 $（即$ O（n ^ 2）$）成比例的测试-这种情况发生在我们无法达到字符粒度的情况下，并且 必须反复尝试删除所有字符，才发现删除最后一个字符会导致失败[ [Zeller *等人*，2002。](https://doi.org/10.1109/32.988498)]。 （不过，这是一个非常病理的情况。）

通常，增量调试是一种健壮的算法，易于实现，易于部署和易于使用-前提是基础测试用例具有确定性，并且运行速度足够快，可以进行大量实验。 由于这些条件都可以有效地使模糊测试变得有效，因此增量调试是模糊测试的绝佳伴侣。

## 基于语法的输入减少

如果输入语言在语法上很复杂，则增量调试可能会进行几次尝试以减少输入，并且可能根本无法减少输入。 因此，在本章的后半部分，我们介绍一种名为*的基于语法的归约*（简称GRABR）的算法，该算法利用*语法*来减少语法复杂的输入。

### 词汇还原与句法规则

尽管它具有一般的鲁棒性，但在某些情况下，增量调试可能效率低下或完全失败。 例如，考虑一些*表达式输入*，例如`1 + (2 * 3)`。 Delta调试需要大量测试以简化导致故障的输入，但最终会返回最少的输入

```py
expr_input = "1 + (2 * 3)"
dd_reducer = DeltaDebuggingReducer(mystery, log_test=True)
dd_reducer.reduce(expr_input)

```

```py
Test #1 '1 + (2 * 3)' 11 FAIL
Test #2 '2 * 3)' 6 PASS
Test #3 '1 + (' 5 PASS
Test #4 '+ (2 * 3)' 9 FAIL
Test #5 '+ ( 3)' 6 FAIL
Test #6 ' 3)' 3 PASS
Test #7 '+ (' 3 PASS
Test #8 ' ( 3)' 5 FAIL
Test #9 '( 3)' 4 FAIL
Test #10 '3)' 2 PASS
Test #11 '( ' 2 PASS
Test #12 '(3)' 3 FAIL
Test #13 '()' 2 FAIL
Test #14 ')' 1 PASS
Test #15 '(' 1 PASS

```

```py
'()'

```

但是，从上面的测试来看，实际上只有很少几个表示语法上有效的算术表达式。 在实际设置中，我们可能想测试一个程序，该程序实际上*解析*这样的表达式，并且该程序将*拒绝*所有无效输入。 我们定义了一个类`EvalMysteryRunner`，该类首先*解析*给定的输入（根据我们的表达语法规则），并且只有*如果合适，它将*传递给我们的原始`MysteryRunner` ]。 这将模拟一个设置，在该设置中我们测试一个表达式解释器，并且其中只有有效的输入才可以触发该错误。

```py
from [Grammars](Grammars.html) import EXPR_GRAMMAR

```

```py
from [Parser](Parser.html) import EarleyParser  # minor dependency

```

```py
class EvalMysteryRunner(MysteryRunner):
    def __init__(self):
        self.parser = EarleyParser(EXPR_GRAMMAR)

    def run(self, inp):
        try:
            tree, *_ = self.parser.parse(inp)
        except SyntaxError as exc:
            return (inp, Runner.UNRESOLVED)

        return super().run(inp)

```

```py
eval_mystery = EvalMysteryRunner()

```

在这种情况下，事实证明增量调试完全失败。 它所应用的任何缩减都不会产生语法上有效的输入，因此，整个输入仍然像以前一样复杂。

```py
dd_reducer = DeltaDebuggingReducer(eval_mystery, log_test=True)
dd_reducer.reduce(expr_input)

```

```py
Test #1 '1 + (2 * 3)' 11 FAIL
Test #2 '2 * 3)' 6 UNRESOLVED
Test #3 '1 + (' 5 UNRESOLVED
Test #4 '+ (2 * 3)' 9 UNRESOLVED
Test #5 '1 2 * 3)' 8 UNRESOLVED
Test #6 '1 + ( 3)' 8 UNRESOLVED
Test #7 '1 + (2 *' 8 UNRESOLVED
Test #8 ' + (2 * 3)' 10 UNRESOLVED
Test #9 '1+ (2 * 3)' 10 UNRESOLVED
Test #10 '1 (2 * 3)' 9 UNRESOLVED
Test #11 '1 + 2 * 3)' 10 UNRESOLVED
Test #12 '1 + ( * 3)' 10 UNRESOLVED
Test #13 '1 + (2 3)' 9 UNRESOLVED
Test #14 '1 + (2 *3)' 10 UNRESOLVED
Test #15 '1 + (2 * ' 9 UNRESOLVED
Test #16 '1  (2 * 3)' 10 UNRESOLVED
Test #17 '1 +(2 * 3)' 10 UNRESOLVED
Test #18 '1 + (2* 3)' 10 UNRESOLVED
Test #19 '1 + (2  3)' 10 UNRESOLVED
Test #20 '1 + (2 * )' 10 UNRESOLVED
Test #21 '1 + (2 * 3' 10 UNRESOLVED

```

```py
'1 + (2 * 3)'

```

如果被测程序对输入有效性有多个约束，则可能会出现这种情况。 Delta调试不了解这些约束（通常也不了解输入结构），因此它可能一次又一次违反这些约束。

### 基于语法的归约方法

为了减少具有高语法复杂性的输入，我们使用了另一种方法：我们减少了表示其结构的*树*而不是减少输入字符串。 总体思路是从解析输入的*派生树*开始，然后*用相同类型*的较小子树替换子树。 这些备用子树可以

1.  从树本身，或者
2.  通过使用树中的元素应用替代的语法扩展。

让我们通过一个示例展示这两种策略。 我们从算术表达式的派生树开始：

```py
from [GrammarFuzzer](GrammarFuzzer.html) import all_terminals, expansion_to_children, display_tree

```

```py
derivation_tree, *_ = EarleyParser(EXPR_GRAMMAR).parse(expr_input)
display_tree(derivation_tree)

```

<svg height="584pt" viewBox="0.00 0.00 203.00 584.00" width="203pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 580)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="74.5" y="-564.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="74.5" y="-513.8"><expr></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-462.8"><term></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="74.5" y="-462.8">+</text></g> <g class="edge" id="edge7"><title>1->7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-462.8"><expr></text></g> <g class="edge" id="edge8"><title>1->8</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-411.8"><factor></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-360.8"><integer></text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-309.8"><digit></text></g> <g class="edge" id="edge5"><title>4->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-258.8">1</text></g> <g class="edge" id="edge6"><title>5->6</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-411.8"><term></text></g> <g class="edge" id="edge9"><title>8->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-360.8"><factor></text></g> <g class="edge" id="edge10"><title>9->10</title></g> <g class="node" id="node12"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="79.5" y="-309.8">(</text></g> <g class="edge" id="edge11"><title>10->11</title></g> <g class="node" id="node13"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-309.8"><expr></text></g> <g class="edge" id="edge12"><title>10->12</title></g> <g class="node" id="node25"><title>24</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="161.5" y="-309.8">)</text></g> <g class="edge" id="edge24"><title>10->24</title></g> <g class="node" id="node14"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-258.8"><term></text></g> <g class="edge" id="edge13"><title>12->13</title></g> <g class="node" id="node15"><title>14</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-207.8"><factor></text></g> <g class="edge" id="edge14"><title>13->14</title></g> <g class="node" id="node19"><title>18</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-207.8">*</text></g> <g class="edge" id="edge18"><title>13->18</title></g> <g class="node" id="node20"><title>19</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="167.5" y="-207.8"><term></text></g> <g class="edge" id="edge19"><title>13->19</title></g> <g class="node" id="node16"><title>15</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-156.8"><integer></text></g> <g class="edge" id="edge15"><title>14->15</title></g> <g class="node" id="node17"><title>16</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-105.8"><digit></text></g> <g class="edge" id="edge16"><title>15->16</title></g> <g class="node" id="node18"><title>17</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-54.8">2</text></g> <g class="edge" id="edge17"><title>16->17</title></g> <g class="node" id="node21"><title>20</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="167.5" y="-156.8"><factor></text></g> <g class="edge" id="edge20"><title>19->20</title></g> <g class="node" id="node22"><title>21</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="167.5" y="-105.8"><integer></text></g> <g class="edge" id="edge21"><title>20->21</title></g> <g class="node" id="node23"><title>22</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="167.5" y="-54.8"><digit></text></g> <g class="edge" id="edge22"><title>21->22</title></g> <g class="node" id="node24"><title>23</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="167.5" y="-3.8">3</text></g> <g class="edge" id="edge23"><title>22->23</title></g></g></svg>

### 通过替换子树来简化

为了简化此树，我们可以用树中的某些`<expr>`子树替换树中的任何`<expr>`符号。 例如，我们可以用最右边的`<expr>`子树替换最上面的`<expr>`，得到字符串`(2 + 3)`：

```py
import [copy](https://docs.python.org/3/library/copy.html)

```

```py
new_derivation_tree = copy.deepcopy(derivation_tree)
# We really should have some query language
sub_expr_tree = new_derivation_tree[1][0][1][2]
display_tree(sub_expr_tree)

```

<svg height="482pt" viewBox="0.00 0.00 160.00 482.00" width="160pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 478)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-462.8"><expr></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-411.8"><term></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-360.8"><factor></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="36.5" y="-309.8">(</text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-309.8"><expr></text></g> <g class="edge" id="edge4"><title>2->4</title></g> <g class="node" id="node17"><title>16</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="118.5" y="-309.8">)</text></g> <g class="edge" id="edge16"><title>2->16</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-258.8"><term></text></g> <g class="edge" id="edge5"><title>4->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-207.8"><factor></text></g> <g class="edge" id="edge6"><title>5->6</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-207.8">*</text></g> <g class="edge" id="edge10"><title>5->10</title></g> <g class="node" id="node12"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-207.8"><term></text></g> <g class="edge" id="edge11"><title>5->11</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-156.8"><integer></text></g> <g class="edge" id="edge7"><title>6->7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-105.8"><digit></text></g> <g class="edge" id="edge8"><title>7->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-54.8">2</text></g> <g class="edge" id="edge9"><title>8->9</title></g> <g class="node" id="node13"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-156.8"><factor></text></g> <g class="edge" id="edge12"><title>11->12</title></g> <g class="node" id="node14"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-105.8"><integer></text></g> <g class="edge" id="edge13"><title>12->13</title></g> <g class="node" id="node15"><title>14</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-54.8"><digit></text></g> <g class="edge" id="edge14"><title>13->14</title></g> <g class="node" id="node16"><title>15</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-3.8">3</text></g> <g class="edge" id="edge15"><title>14->15</title></g></g></svg>

```py
new_derivation_tree[1][0] = sub_expr_tree
display_tree(new_derivation_tree)

```

<svg height="533pt" viewBox="0.00 0.00 160.00 533.00" width="160pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 529)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-513.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-462.8"><expr></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-411.8"><term></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-360.8"><factor></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="36.5" y="-309.8">(</text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-309.8"><expr></text></g> <g class="edge" id="edge5"><title>3->5</title></g> <g class="node" id="node18"><title>17</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="118.5" y="-309.8">)</text></g> <g class="edge" id="edge17"><title>3->17</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-258.8"><term></text></g> <g class="edge" id="edge6"><title>5->6</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-207.8"><factor></text></g> <g class="edge" id="edge7"><title>6->7</title></g> <g class="node" id="node12"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-207.8">*</text></g> <g class="edge" id="edge11"><title>6->11</title></g> <g class="node" id="node13"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-207.8"><term></text></g> <g class="edge" id="edge12"><title>6->12</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-156.8"><integer></text></g> <g class="edge" id="edge8"><title>7->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-105.8"><digit></text></g> <g class="edge" id="edge9"><title>8->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-54.8">2</text></g> <g class="edge" id="edge10"><title>9->10</title></g> <g class="node" id="node14"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-156.8"><factor></text></g> <g class="edge" id="edge13"><title>12->13</title></g> <g class="node" id="node15"><title>14</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-105.8"><integer></text></g> <g class="edge" id="edge14"><title>13->14</title></g> <g class="node" id="node16"><title>15</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-54.8"><digit></text></g> <g class="edge" id="edge15"><title>14->15</title></g> <g class="node" id="node17"><title>16</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-3.8">3</text></g> <g class="edge" id="edge16"><title>15->16</title></g></g></svg>

```py
all_terminals(new_derivation_tree)

```

```py
'(2 * 3)'

```

只要一个子树（例如`<expr>`）在我们的树中多次出现，就可以用另一个子树替换。 在上述简化的`new_derivation_tree`中，我们只能再替换一次`<expr>`树。

### 通过替代扩展进行简化

简化此树的第二种方法是应用*替代扩展*。 也就是说，对于一个符号，我们检查是否存在子项数量较少的替代扩展。 然后，用替代扩展替换符号，从树中填充所需的符号。

例如，考虑上面的`new_derivation_tree`。 `<term>`的应用扩展为

```py
<term> ::= <term> * <factor> 
```

麻烦我们用替代扩展替换它：

```py
<term> ::= <factor>
```

```py
term_tree = new_derivation_tree[1][0][1][0][1][0][1][1][1][0]
display_tree(term_tree)

```

<svg height="278pt" viewBox="0.00 0.00 160.00 278.00" width="160pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 274)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-258.8"><term></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-207.8"><factor></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="77.5" y="-207.8">*</text></g> <g class="edge" id="edge5"><title>0->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-207.8"><term></text></g> <g class="edge" id="edge6"><title>0->6</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-156.8"><integer></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-105.8"><digit></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-54.8">2</text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-156.8"><factor></text></g> <g class="edge" id="edge7"><title>6->7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-105.8"><integer></text></g> <g class="edge" id="edge8"><title>7->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-54.8"><digit></text></g> <g class="edge" id="edge9"><title>8->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="124.5" y="-3.8">3</text></g> <g class="edge" id="edge10"><title>9->10</title></g></g></svg>

```py
shorter_term_tree = term_tree[1][2]
display_tree(shorter_term_tree)

```

<svg height="227pt" viewBox="0.00 0.00 63.00 227.00" width="63pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 223)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-207.8"><term></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-156.8"><factor></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-105.8"><integer></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-54.8"><digit></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-3.8">3</text></g> <g class="edge" id="edge4"><title>3->4</title></g></g></svg>

```py
new_derivation_tree[1][0][1][0][1][0][1][1][1][0] = shorter_term_tree
display_tree(new_derivation_tree)

```

<svg height="482pt" viewBox="0.00 0.00 95.00 482.00" width="95pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 478)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="43.5" y="-462.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="43.5" y="-411.8"><expr></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="43.5" y="-360.8"><term></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="43.5" y="-309.8"><factor></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="2.5" y="-258.8">(</text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="43.5" y="-258.8"><expr></text></g> <g class="edge" id="edge5"><title>3->5</title></g> <g class="node" id="node12"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="84.5" y="-258.8">)</text></g> <g class="edge" id="edge11"><title>3->11</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="43.5" y="-207.8"><term></text></g> <g class="edge" id="edge6"><title>5->6</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="43.5" y="-156.8"><factor></text></g> <g class="edge" id="edge7"><title>6->7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="43.5" y="-105.8"><integer></text></g> <g class="edge" id="edge8"><title>7->8</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="43.5" y="-54.8"><digit></text></g> <g class="edge" id="edge9"><title>8->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="43.5" y="-3.8">3</text></g> <g class="edge" id="edge10"><title>9->10</title></g></g></svg>

```py
all_terminals(new_derivation_tree)

```

```py
'(3)'

```

如果用（较小的）子树替换派生子树，并且如果搜索可再次产生较小的子树的替代扩展，则可以系统地简化输入。 这可能比增量调试快得多，因为我们的输入在语法上始终是有效的。 但是，我们需要一种何时应用哪种简化规则的策略。 这是我们在本节其余部分中要开发的内容。

### 用语法进行归约的一类

我们介绍`GrammarReducer`类，它也是`Reducer`。 请注意，我们从`CachingReducer`派生，因为该策略将产生多个重复项。

```py
class GrammarReducer(CachingReducer):
    def __init__(self, runner, parser, log_test=False, log_reduce=False):
        super().__init__(runner, log_test=log_test)
        self.parser = parser
        self.grammar = parser.grammar()
        self.start_symbol = parser.start_symbol()
        self.log_reduce = log_reduce
        self.try_all_combinations = False

```

### 少数帮助者

我们定义了许多助手功能，这些对于我们的策略将是需要的。 `tree_list_to_string()`顾名思义，从派生树列表中创建一个字符串：

```py
def tree_list_to_string(q):
    return "[" + ", ".join([all_terminals(tree) for tree in q]) + "]"

```

```py
tree_list_to_string([derivation_tree, derivation_tree])

```

```py
'[1 + (2 * 3), 1 + (2 * 3)]'

```

函数`possible_combinations()`获取列表$ [[x_1，x_2]，[y_1，y_2]，\ dots $$的列表，并创建组合列表$ [[x_1，y_1]，[x_1，y_2]，[x_2 ，y_1]，[x_2，y_2]，\ dots $。

```py
def possible_combinations(list_of_lists):
    if len(list_of_lists) == 0:
        return []

    ret = []
    for e in list_of_lists[0]:
        if len(list_of_lists) == 1:
            ret.append([e])
        else:
            for c in possible_combinations(list_of_lists[1:]):
                new_combo = [e] + c
                ret.append(new_combo)
    return ret

```

```py
possible_combinations([[1, 2], ['a', 'b']])

```

```py
[[1, 'a'], [1, 'b'], [2, 'a'], [2, 'b']]

```

函数`number_of_nodes()`和`max_height()`分别返回给定树的节点数和最大高度。

```py
def number_of_nodes(tree):
    (symbol, children) = tree
    return 1 + sum([number_of_nodes(c) for c in children])

```

```py
number_of_nodes(derivation_tree)

```

```py
25

```

```py
def max_height(tree):
    (symbol, children) = tree
    if len(children) == 0:
        return 1
    return 1 + max([max_height(c) for c in children])

```

```py
max_height(derivation_tree)

```

```py
12

```

### 简化策略

现在让我们实现我们的两种简化策略-替换子树和备用扩展。

#### 查找子树

方法`subtrees_with_symbol()`返回给定树中其根等于给定符号的所有子树。 如果设置了`ignore_root`（默认值），则不与`tree`的根节点进行比较。 （`depth`参数将在下面讨论。）

```py
class GrammarReducer(GrammarReducer):
    def subtrees_with_symbol(self, tree, symbol, depth=-1, ignore_root=True):
        # Find all subtrees in TREE whose root is SYMBOL.
        # If IGNORE_ROOT is true, ignore the root note of TREE.

        ret = []
        (child_symbol, children) = tree
        if depth <= 0 and not ignore_root and child_symbol == symbol:
            ret.append(tree)

        # Search across all children
        if depth != 0 and children is not None:
            for c in children:
                ret += self.subtrees_with_symbol(c,
                                                 symbol,
                                                 depth=depth - 1,
                                                 ignore_root=False)

        return ret

```

举个例子：这些都是我们的派生树`derivation_tree`中带有`<term>`的子树。

```py
grammar_reducer = GrammarReducer(
    mystery,
    EarleyParser(EXPR_GRAMMAR),
    log_reduce=True)

```

```py
all_terminals(derivation_tree)

```

```py
'1 + (2 * 3)'

```

```py
[all_terminals(t) for t in grammar_reducer.subtrees_with_symbol(
    derivation_tree, "<term>")]

```

```py
['1', '(2 * 3)', '2 * 3', '3']

```

如果我们要替换`<term>`子树以简化树，则可以使用这些子树来替换它们。

#### 替代扩展

我们的第二种策略是通过交替扩展来简化，但要复​​杂一些。 我们首先获取给定符号的可能扩展名（从子项最少的子类开始）。 对于每个扩展，我们为子树中的符号填充值（使用上面的`subtrees_with_symbols()`）。 然后，我们选择第一个可能的组合（如果设置了`try_all_combinations`属性，则选择*所有*组合）。

```py
class GrammarReducer(GrammarReducer):
    def alternate_reductions(self, tree, symbol, depth=-1):
        reductions = []

        expansions = self.grammar.get(symbol, [])
        expansions.sort(
            key=lambda expansion: len(
                expansion_to_children(expansion)))

        for expansion in expansions:
            expansion_children = expansion_to_children(expansion)

            match = True
            new_children_reductions = []
            for (alt_symbol, _) in expansion_children:
                child_reductions = self.subtrees_with_symbol(
                    tree, alt_symbol, depth=depth)
                if len(child_reductions) == 0:
                    match = False   # Child not found; cannot apply rule
                    break

                new_children_reductions.append(child_reductions)

            if not match:
                continue  # Try next alternative

            # Use the first suitable combination
            for new_children in possible_combinations(new_children_reductions):
                new_tree = (symbol, new_children)
                if number_of_nodes(new_tree) < number_of_nodes(tree):
                    reductions.append(new_tree)
                    if not self.try_all_combinations:
                        break

        # Sort by number of nodes
        reductions.sort(key=number_of_nodes)

        return reductions

```

```py
grammar_reducer = GrammarReducer(
    mystery,
    EarleyParser(EXPR_GRAMMAR),
    log_reduce=True)

```

```py
all_terminals(derivation_tree)

```

```py
'1 + (2 * 3)'

```

这是`<term>`的所有*组合：*

```py
grammar_reducer.try_all_combinations = True
print([all_terminals(t)
       for t in grammar_reducer.alternate_reductions(derivation_tree, "<term>")])

```

```py
['1', '2', '3', '1 * 1', '1 * 3', '2 * 1', '2 * 3', '3 * 1', '3 * 3', '(2 * 3)', '1 * 2 * 3', '2 * 2 * 3', '3 * 2 * 3', '1 * (2 * 3)', '(2 * 3) * 1', '(2 * 3) * 3', '2 * (2 * 3)', '3 * (2 * 3)']

```

但是，默认设置只是返回以下第一个：

```py
grammar_reducer.try_all_combinations = False
[all_terminals(t) for t in grammar_reducer.alternate_reductions(
    derivation_tree, "<term>")]

```

```py
['1', '1 * 1']

```

#### 两种策略

现在让我们合并这两种策略。 为了用给定的符号替换子树，我们首先搜索已经存在的子树（使用`subtrees_with_symbol()`）； 然后我们进行替代扩展（使用`alternate_expansions()`）。

```py
class GrammarReducer(GrammarReducer):
    def symbol_reductions(self, tree, symbol, depth=-1):
        """Find all expansion alternatives for the given symbol"""
        reductions = (self.subtrees_with_symbol(tree, symbol, depth=depth)
                      + self.alternate_reductions(tree, symbol, depth=depth))

        # Filter duplicates
        unique_reductions = []
        for r in reductions:
            if r not in unique_reductions:
                unique_reductions.append(r)

        return unique_reductions

```

```py
grammar_reducer = GrammarReducer(
    mystery,
    EarleyParser(EXPR_GRAMMAR),
    log_reduce=True)

```

```py
all_terminals(derivation_tree)

```

```py
'1 + (2 * 3)'

```

这些是`<expr>`节点的可能减少量。 请注意，在进行`<expr>`（`1`）的替代扩展之前，我们如何首先返回子树（`1 + (2 * 3)`，`(2 * 3)`和`2 * 3`）。

```py
reductions = grammar_reducer.symbol_reductions(derivation_tree, "<expr>")
tree_list_to_string([r for r in reductions])

```

```py
'[1 + (2 * 3), (2 * 3), 2 * 3, 1]'

```

这些是`<term>`节点的可能减少量。 再次，我们首先具有派生树的子树，然后是备用扩展`1 * 1`。

```py
reductions = grammar_reducer.symbol_reductions(derivation_tree, "<term>")
tree_list_to_string([r for r in reductions])

```

```py
'[1, (2 * 3), 2 * 3, 3, 1 * 1]'

```

### 减少策略

现在，我们可以为树中的每个符号返回许多替代项。 这就是我们在减少策略`reduce_subtree()`的核心功能中应用的内容。 从`subtree`开始，我们发现每个孩子的减少量都有可能。 对于每个归约，我们用归约替换子对象，并测试生成的（完整）树。 如果失败，则说明还原成功。 否则，我们将孩子放回原位，然后尝试下一个减少方法。 最终，我们将`reduce_subtree()`应用于所有儿童，并减少了这些儿童。

```py
class GrammarReducer(GrammarReducer):
    def reduce_subtree(self, tree, subtree, depth=-1):
        symbol, children = subtree
        if len(children) == 0:
            return False

        if self.log_reduce:
            print("Reducing", all_terminals(subtree), "with depth", depth)

        reduced = False
        while True:
            reduced_child = False
            for i, child in enumerate(children):
                (child_symbol, _) = child
                for reduction in self.symbol_reductions(
                        child, child_symbol, depth):
                    if number_of_nodes(reduction) >= number_of_nodes(child):
                        continue

                    # Try this reduction
                    if self.log_reduce:
                        print(
                            "Replacing",
                            all_terminals(
                                children[i]),
                            "by",
                            all_terminals(reduction))
                    children[i] = reduction
                    if self.test(all_terminals(tree)) == Runner.FAIL:
                        # Success
                        if self.log_reduce:
                            print("New tree:", all_terminals(tree))
                        reduced = reduced_child = True
                        break
                    else:
                        # Didn't work out - restore
                        children[i] = child

            if not reduced_child:
                if self.log_reduce:
                    print("Tried all alternatives for", all_terminals(subtree))
                break

        # Run recursively
        for c in children:
            if self.reduce_subtree(tree, c, depth):
                reduced = True

        return reduced

```

我们现在需要的只是一些驱动程序。 方法`reduce_tree()`是`reduce_subtree()`的主要入口点：

```py
class GrammarReducer(GrammarReducer):
    def reduce_tree(self, tree):
        return self.reduce_subtree(tree, tree)

```

定制方法`parse()`将给定的输入转换为派生树：

```py
class GrammarReducer(GrammarReducer):
    def parse(self, inp):
        tree, *_ = self.parser.parse(inp)
        if self.log_reduce:
            print(all_terminals(tree))
        return tree

```

方法`reduce()`是一个单一的入口点，先解析输入然后进行缩减。

```py
class GrammarReducer(GrammarReducer):
    def reduce(self, inp):
        tree = self.parse(inp)
        self.reduce_tree(tree)
        return all_terminals(tree)

```

让我们在输入`expr_input`和`mystery()`函数上实践一下。 我们多快减少一次？

```py
expr_input

```

```py
'1 + (2 * 3)'

```

```py
grammar_reducer = GrammarReducer(
    eval_mystery,
    EarleyParser(EXPR_GRAMMAR),
    log_test=True)
grammar_reducer.reduce(expr_input)

```

```py
Test #1 '(2 * 3)' 7 FAIL
Test #2 '2 * 3' 5 PASS
Test #3 '3' 1 PASS
Test #4 '2' 1 PASS
Test #5 '(3)' 3 FAIL

```

```py
'(3)'

```

成功！ 仅需五步，我们的`GrammarReducer`就将输入降至导致故障的最小值。 请注意，所有测试在构造上如何在语法上有效，避免了导致增量调试停止的`UNRESOLVED`结果。

### 深度策略

即使已经做好了五个步骤，我们仍然可以做得更好。 如果我们看一下上面的日志，可以看到在测试`#2`后，其中输入（树）被减少为`2 * 3`，我们的`GrammarReducer`首先尝试用`2`和`3`替换树， 这是备用`<term>`子树。 当然，这可能有效； 但是如果有很多可能的子树，我们的策略将花费相当多的时间来尝试一个接一个的子树。

如上所述，Delta调试遵循的想法是尝试将输入削减大约一半，从而迅速实现最小输入。 通过用小得多的子树替换树，我们*可能会*显着减少树，但可能需要进行几次尝试。 更好的策略是只考虑*大型*子树-既用于子树替换又用于替代扩展。 为了找到这样的*大*子树，我们限制了*深度*，通过它我们可以在子树中搜索可能的替换-首先，通过查看直接后代，然后在较低的后代中。

这是`subtrees_with_symbol()`中使用的`depth`参数的作用，并通过调用函数传递。 如果设置，则仅返回给定深度的*符号。 这是一个示例，再次从我们的派生树`derivation_tree`开始：*

```py
grammar_reducer = GrammarReducer(
    mystery,
    EarleyParser(EXPR_GRAMMAR),
    log_reduce=True)

```

```py
all_terminals(derivation_tree)

```

```py
'1 + (2 * 3)'

```

```py
display_tree(derivation_tree)

```

<svg height="584pt" viewBox="0.00 0.00 203.00 584.00" width="203pt" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink"><g class="graph" id="graph0" transform="scale(1 1) rotate(0) translate(4 580)"><title>%3</title> <g class="node" id="node1"><title>0</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="74.5" y="-564.8"><start></text></g> <g class="node" id="node2"><title>1</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="74.5" y="-513.8"><expr></text></g> <g class="edge" id="edge1"><title>0->1</title></g> <g class="node" id="node3"><title>2</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-462.8"><term></text></g> <g class="edge" id="edge2"><title>1->2</title></g> <g class="node" id="node8"><title>7</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="74.5" y="-462.8">+</text></g> <g class="edge" id="edge7"><title>1->7</title></g> <g class="node" id="node9"><title>8</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-462.8"><expr></text></g> <g class="edge" id="edge8"><title>1->8</title></g> <g class="node" id="node4"><title>3</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-411.8"><factor></text></g> <g class="edge" id="edge3"><title>2->3</title></g> <g class="node" id="node5"><title>4</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-360.8"><integer></text></g> <g class="edge" id="edge4"><title>3->4</title></g> <g class="node" id="node6"><title>5</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-309.8"><digit></text></g> <g class="edge" id="edge5"><title>4->5</title></g> <g class="node" id="node7"><title>6</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="27.5" y="-258.8">1</text></g> <g class="edge" id="edge6"><title>5->6</title></g> <g class="node" id="node10"><title>9</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-411.8"><term></text></g> <g class="edge" id="edge9"><title>8->9</title></g> <g class="node" id="node11"><title>10</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-360.8"><factor></text></g> <g class="edge" id="edge10"><title>9->10</title></g> <g class="node" id="node12"><title>11</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="79.5" y="-309.8">(</text></g> <g class="edge" id="edge11"><title>10->11</title></g> <g class="node" id="node13"><title>12</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-309.8"><expr></text></g> <g class="edge" id="edge12"><title>10->12</title></g> <g class="node" id="node25"><title>24</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="161.5" y="-309.8">)</text></g> <g class="edge" id="edge24"><title>10->24</title></g> <g class="node" id="node14"><title>13</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-258.8"><term></text></g> <g class="edge" id="edge13"><title>12->13</title></g> <g class="node" id="node15"><title>14</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-207.8"><factor></text></g> <g class="edge" id="edge14"><title>13->14</title></g> <g class="node" id="node19"><title>18</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="120.5" y="-207.8">*</text></g> <g class="edge" id="edge18"><title>13->18</title></g> <g class="node" id="node20"><title>19</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="167.5" y="-207.8"><term></text></g> <g class="edge" id="edge19"><title>13->19</title></g> <g class="node" id="node16"><title>15</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-156.8"><integer></text></g> <g class="edge" id="edge15"><title>14->15</title></g> <g class="node" id="node17"><title>16</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-105.8"><digit></text></g> <g class="edge" id="edge16"><title>15->16</title></g> <g class="node" id="node18"><title>17</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="70.5" y="-54.8">2</text></g> <g class="edge" id="edge17"><title>16->17</title></g> <g class="node" id="node21"><title>20</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="167.5" y="-156.8"><factor></text></g> <g class="edge" id="edge20"><title>19->20</title></g> <g class="node" id="node22"><title>21</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="167.5" y="-105.8"><integer></text></g> <g class="edge" id="edge21"><title>20->21</title></g> <g class="node" id="node23"><title>22</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="167.5" y="-54.8"><digit></text></g> <g class="edge" id="edge22"><title>21->22</title></g> <g class="node" id="node24"><title>23</title> <text fill="#000000" font-family="Times,serif" font-size="14.00" text-anchor="middle" x="167.5" y="-3.8">3</text></g> <g class="edge" id="edge23"><title>22->23</title></g></g></svg>

深度为1时，没有`<term>`符号：

```py
[all_terminals(t) for t in grammar_reducer.subtrees_with_symbol(
    derivation_tree, "<term>", depth=1)]

```

```py
[]

```

在深度2处，左侧有`<term>`子树：

```py
[all_terminals(t) for t in grammar_reducer.subtrees_with_symbol(
    derivation_tree, "<term>", depth=2)]

```

```py
['1']

```

深度为3时，右侧有`<term>`子树：

```py
[all_terminals(t) for t in grammar_reducer.subtrees_with_symbol(
    derivation_tree, "<term>", depth=3)]

```

```py
['(2 * 3)']

```

现在的想法是从深度0开始，然后随着我们的进行逐渐增加：

```py
class GrammarReducer(GrammarReducer):
    def reduce_tree(self, tree):
        depth = 0
        while depth < max_height(tree):
            reduced = self.reduce_subtree(tree, tree, depth)
            if reduced:
                depth = 0    # Start with new tree
            else:
                depth += 1   # Extend search for subtrees
        return tree        

```

```py
grammar_reducer = GrammarReducer(
    mystery,
    EarleyParser(EXPR_GRAMMAR),
    log_test=True)
grammar_reducer.reduce(expr_input)

```

```py
Test #1 '(2 * 3)' 7 FAIL
Test #2 '(3)' 3 FAIL
Test #3 '3' 1 PASS

```

```py
'(3)'

```

我们看到，面向深度的策略在我们的设置中需要更少的步骤。

### 比较策略

最后，我们将演示基于文本的增量调试与基于语法的简化之间的区别。 我们建立一个很长的表达：

```py
from [GrammarFuzzer](GrammarFuzzer.html) import GrammarFuzzer

```

```py
long_expr_input = GrammarFuzzer(EXPR_GRAMMAR, min_nonterminals=100).fuzz()
long_expr_input

```

```py
'++---((-2 / 3 / 3 - -+1 / 5 - 2) * ++6 / +8 * 4 / 9 / 2 * 8 + ++(5) * 3 / 8 * 0 + 3 * 3 + 4 / 0 / 6 + 9) * ++++(+--9 * -3 * 7 / 4 + --(4) / 3 - 0 / 3 + 5 + 0) * (1 * 6 - 1 / 9 * 5 - 9 / 0 + 7) * ++(8 - 1) * +1 * 7 * 0 + ((1 + 4) / 4 * 8 * 9 * 4 + 4 / (4) * 1 - (4) * 8 * 5 + 1 + 4) / (+(2 - 1 - 9) * 5 + 3 + 6 - 2) * +3 * (3 - 7 + 8) / 4 - -(9 * 4 - 1 * 0 + 5) / (5 / 9 * 5 + 2) * 7 + ((7 - 5 + 3) / 1 * 8 - 8 - 9) * --+1 * 4 / 4 - 4 / 7 * 4 - 3 / 6 * 1 - 2 - 7 - 8'

```

使用语法，我们只需要进行少量测试即可找到导致失败的输入：

```py
from [Timer](Timer.html) import Timer

```

```py
grammar_reducer = GrammarReducer(eval_mystery, EarleyParser(EXPR_GRAMMAR))
with Timer() as grammar_time:
    print(grammar_reducer.reduce(long_expr_input))

```

```py
(9)

```

```py
grammar_reducer.tests

```

```py
10

```

```py
grammar_time.elapsed_time()

```

```py
0.20119122300002346

```

相比之下，Delta调试需要更多数量级的测试（因此需要更多时间）。 同样，这种归约没有像基于语法的归约器那样完美。

```py
dd_reducer = DeltaDebuggingReducer(eval_mystery)
with Timer() as dd_time:
    print(dd_reducer.reduce(long_expr_input))

```

```py
((2 - 1 - 2) * 8 + (5) - (4)) / ((2) * 3) * (9) / 3 / 1 - 8

```

```py
dd_reducer.tests

```

```py
900

```

```py
dd_time.elapsed_time()

```

```py
5.701468969999951

```

我们看到，如果输入在语法上很复杂，那么使用语法来减少输入是最好的方法。

## 经验教训

*   将导致故障的输入减少到最低程度有助于测试和调试。
*   *Delta调试*是一种简单且健壮的算法，可轻松减少测试用例。
*   对于语法复杂的输入，基于语法的*归约*更快，并且产生更好的结果。

## 后续步骤

我们的下一章重点介绍 [Web GUI Fuzzing](WebFuzzer.html) ，这是生成和减少测试用例的另一个领域。

## 背景

这里讨论的增量调试算法源自[ [Zeller *等人*，2002。](https://doi.org/10.1109/32.988498)]； 实际上，这是Zeller在2002年使用的确切的Python实现。系统地减少输入的想法已经被发现了很多次，尽管不像增量调试那样自动和通用。 [ [Slutz *等人*，1998。](https://www.microsoft.com/en-us/research/publication/massive-stochastic-testing-of-sql/)]，例如，讨论了SQL数据库的SQL语句的系统化简化。 [Kernighan *等人*，1999。]很好地描述了作为手工工作的一般过程。

关于语法复杂输入的增量调试缺陷首先在*编译器测试*中进行了讨论，然后迅速发现*减少树输入*而不是字符串输入是替代方法。 *分层Delta调试*（ *HDD* ）[ [Misherghi *等人*，2006\.](https://doi.org/10.1145/1134285.1134307) ]在分析树的子树上应用增量调试，系统地减少了调试次数 解析树最小。 *广义树减少* [ [Herfert *等人*，2017年。](http://dl.acm.org/citation.cfm?id=3155562.3155669)概括了此想法，以应用任意*模式*，例如用兼容替换术语 子树中的术语，如`subtrees_with_symbol()`。

但是，现有方法均未使用语法来应用扩展替代项（如`alternate_reductions()`那样）； 他们也没有努力首先替换较大的子树（如`reduce_tree()`一样）。 因此，本章做出了原创性贡献。

尽管`GrammarReducer`是可以使用任意语法进行参数化的通用方法，但是*语言特定的*方法可以为手头的语言做得更好。 *C-Reduce* [ [Regehr *等人*，2012。](https://doi.org/10.1145/2254064.2254104)]是专门针对编程语言还原的还原器。 除了减少增量调试或树形转换的样式之外，C-Reduce还提供了30多个源到源转换，这些转换用标量替换聚合，删除定义和所有调用位置的函数参数，更改函数以返回`void` 并删除所有`return`语句，等等。 尽管这些原理是为C语言专门实例化的（并用于测试C编译器），但这些原理扩展到遵循类似ALGOL语法的任意编程语言。 测试编译器时，可以使用C-Reduce。

David McIver的[博客文章](https://www.drmaciver.com/2019/01/notes-on-test-case-reduction/)包含了许多有关如何在实践中应用约简的见解，尤其是具有不同抽象级别的多次运行。

## 练习

如何最好地减少投入仍然是一个欠发达的研究领域，有很多机会。

### 练习1：基于变异的基于模糊的模糊

当对种群进行模糊测试时，偶尔地*减小*每个元素的长度可能会很有用，这样将来的后代也会更短，这通常会加快测试速度。

考虑[中的`MutationFuzzer`类，这是基于突变的模糊](MutationFuzzer.html)的一章。 扩展它，以便每当将新输入添加到总体中时，首先使用增量调试来减少它。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Reducer.ipynb#Exercises) to work on the exercises and see solutions.

### 练习2：通过生产减少

如上所述，基于语法的输入减少可能是一个很好的算法，但绝不是唯一的选择。 一个有趣的问题是“还原”应仅限于已经存在的元素，还是应该允许人们也创建*新的*元素。 这些将不会出现在原始输入中，但仍然允许产生更小的输入，而这些输入仍会重现原始故障。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Reducer.ipynb#Exercises) to work on the exercises and see solutions.

例如，请考虑以下语法：

```py
<number> ::= <float> | <integer> | <not-a-number>
<float> ::= <digits>.<digits>
<integer> ::= <digits>
<not-a-number> ::= NaN
<digits> ::= [0-9]+
```

假设输入`100.99`失败。 我们也许可以将其降低到`1.9`的最小值。 但是，我们不能将其简化为`<integer>`或`<not-a-number>`，因为这些符号不会出现在原始输入中。 通过允许*为这些符号创建*替代项，我们还可以测试诸如`1`或`NaN`之类的输入，并进一步归纳程序失败的输入类别。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Reducer.ipynb#Exercises) to work on the exercises and see solutions.

创建一个类`GenerativeGrammarReducer`作为`GrammarReducer`的子类； 相应地扩展方法`reduce_subtree()`。

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Reducer.ipynb#Exercises) to work on the exercises and see solutions.

### 练习3：大幅度减少枪战

为先前已经定义的语法创建*基准*，该语法包括：

1.  一组*输入*，这些输入是使用`GrammarFuzzer`及其派生词从这些语法中产生的；
2.  一组*测试*，这些代码检查单个符号以及这些符号的对和三对的出现：
    *   如果输入在语法上无效，则测试应为*未解析的*；
    *   如果出现符号（或其对或三元组），则测试*不通过*；
    *   在所有其他情况下，测试应*通过*。

在基准上比较增量调试和基于语法的调试。 实施HDD [ [Misherghi *等*，2006\.](https://doi.org/10.1145/1134285.1134307) ]和*广义减树* [ [Herfert *等*等，2017。](http://dl.acm.org/citation.cfm?id=3155562.3155669)]并将它们添加到您的比较中。 哪种方法效果最佳，在什么情况下？

[Use the notebook](https://mybinder.org/v2/gh/uds-se/fuzzingbook/master?filepath=docs/notebooks/Reducer.ipynb#Exercises) to work on the exercises and see solutions.