一个错误的答案显然意味着一个逻辑错误,但啥是错误的想法呢?

Posted

技术标签:

【中文标题】一个错误的答案显然意味着一个逻辑错误,但啥是错误的想法呢?【英文标题】:A wrong answer obviously means a logic error, but what is at fault in what was thought?一个错误的答案显然意味着一个逻辑错误,但什么是错误的想法呢? 【发布时间】:2012-11-30 09:16:48 【问题描述】:

Problem 14 on Project Euler 描述了许多人在这里问过的一个难题。我的问题不是如何解决问题或如何解决其他人的错误。想了想谜底,写了下面的“解法”,但似乎是错误的。有人可以解释我的错误吗?

def main():
    # start has all candidate numbers; found has known sequence numbers
    start, found = set(range(1, 1000000)), set()
    # if are numbers in start, then there are still unfound candidates
    while start:
        # pick a random starting number to test in the sequence generator
        number = start.pop()
        # define the set of numbers that the generator created for study
        result = set(sequence(number, found))
        # remove them from the candidates since another number came first
        start -= result
        # record that these numbers are part of an already found sequence
        found |= result
    # whatever number was used last should yield the longest sequence
    print(number)

def sequence(n, found):
    # generate all numbers in the sequence defined by the problem
    while True:
        # since the first number begins the sequence, yield it back
        yield n
        # since 1 is the last sequence number, stop if we yielded it
        if n == 1:
            break
        # generate the next number in the sequence with binary magic
        n = 3 * n + 1 if n & 1 else n >> 1
        # if the new number was already found, this sequence is done
        if n in found:
            break

if __name__ == '__main__':
    main()

该文档是后来添加的,希望足够清晰,可以解释为什么我认为它会起作用。

【问题讨论】:

为什么不使用递归和记忆? 这个“解决方案”使用了一种与startfound 集一起使用的记忆形式。 我喜欢你的问题标题! 【参考方案1】:
# whatever number was used last should yield the longest sequence
print(number)

由于您是按随机顺序查看start的元素,因此上述评论和结论是错误的。

假设我们正在寻找从18 之间的数字开始的最长序列。由于您的算法的意图是“选择一个随机起始数字进行测试”,让我们按以下顺序选择数字:

    7:这会产生一个长度为 17 的序列,并从 start 中剔除 124586:这会产生一个长度为 9 的序列,并从 start 中剔除 3

start 中没有更多号码了。您的代码得出结论,6 是最佳解决方案,当然,它不是。

更一般地说,假设您碰巧在第一步中选择了最佳起始数字。对于您的工作方法,第一个序列需要包含1999,999 之间的每个数字。除非你能证明这就是发生的事情,否则没有理由认为你的解决方案是正确的。

【讨论】:

这是不可能的,因为start -= result。在生成第一个序列后,1 将从start 中删除,并且永远不会是最后一个尝试的候选人。请参阅我的答案以了解问题的真实解释。 @NoctisSkytower:我坚持我的直觉。然而,我选择的具体例子是有缺陷的。我举了另一个例子。 @NPE:这些数字实际上并没有按随机顺序查看(因为hash(n) 返回n)。从集合中弹出的实际数字序列原来是普通的数字顺序:1、2、3、4、...! @GarethRees:当然,任何确定性算法都没有随机性。但是,该算法的作者非常清楚地表示,他们并不关心选择数字的顺序。我引用:选择一个随机的起始数字... @GarethRees:说实话,我认为整个辩论有点毫无意义。 Python 文档明确指出set.pop()“从集合中删除并返回任意元素”。您的实现似乎以某种方式表现这一事实并不会使我的论点无效。【参考方案2】:

错误的假设在这里:

# whatever number was used last should yield the longest sequence

考虑我们以range(1, 13) 而不是range(1, 1000000) 开头的情况。然后你的算法进行如下:

number result                                  start
-----------------------------------------------------------------------------------
 1     1                                     2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12
 2     2                                     3, 4, 5, 6, 7, 8, 9, 10, 11, 12
 3     3, 4, 5, 8, 10, 16                    4, 5, 6, 7, 8, 9, 10, 11, 12
 6     6                                     7, 9, 11, 12
 7     34, 7, 40, 11, 13, 17, 52, 22, 20, 26 9, 11, 12
 9     9, 28, 14                             12
12     12                                    

所以最后使用的数字是 12。但是以这个范围内的数字开头的最长序列是 9 → 28 → 14 → 7 → 22 → 11 → 34 → 17 → 52 → 26 → 13 → 40 → 20 → 10 → 5 → 16 → 8 → 4 → 2 → 1(长度20);序列 12 → 6 → 3 → 10 → 5 → 16 → 8 → 4 → 2 → 1 的长度只有 10。

只有当你得到正确答案(最长序列的起始数字)时,你的方法才有效,范围内所有更高的数字要么已经找到,要么在生成序列的过程中找到从正确答案开始。

但是在这个例子中,当我们到 9 时,数字 12 还没有在任何序列中找到,也没有在从 9 开始的序列扩展过程中找到。

【讨论】:

在这种情况下,您认为这是一个错误的假设是正确的。但是,我回答中的示例程序表明该假设在一个封闭的数字范围内似乎是正确的。 @Noctis:您的示例有效的原因不是因为序列没有超出范围。这是因为最长的序列访问范围内的每个数字,因此符合我在“您的方法只有在以下情况下才有效”的段落中给出的条件。在 3n+1 问题中,最长序列没有这个性质。 实际上,我们说的是同一件事。由于3n+1,没有办法将序列限制在一个范围内。如果为问题定义了任何数字范围,则序列将通过乘法超出该范围。不可能定义序列不离开的大量候选对象。无论如何,感谢您试图帮助我理解问题。我发现了错误,只需要设计一个更好的解决方案。 @Noctis:我们说的不是同一件事!我是说你的“封闭范围”理论是错误的。构建序列保持在范围内的示例很容易,但是您的方法失败了。例如,试试yield from self.__sequence[self.__sequence.index(start)::2] 是的,你只是澄清了我的假设。它是算法工作的整个封闭范围内的序列。请原谅我以不精确的方式沟通导致误解。【参考方案3】:

在向同事解释了建议的解决方案后,我得到了答案:这个解决方案没有考虑在被测试的数字范围之外生成的序列长度。因此,需要设计一种考虑完整序列长度的新解决方案。

为了测试算法,编写了以下程序。该方法适用于整个封闭范围内的序列。这在 Collat​​z 问题中是完全不可能完成的,所以代码失败了。

import random
import time

class Sequencer:

    def __init__(self, limit, seed):
        random.seed(seed)
        self.__sequence = tuple(random.sample(range(limit), limit))

    def __call__(self, start):
        yield from self.__sequence[self.__sequence.index(start):]

    @property
    def longest(self):
        return self.__sequence[0]

def main(limit):
    while True:
        sequence = Sequencer(limit, str(time.time()))
        longest = find_longest(limit, sequence)
        print('Found longest ' +
              ('' if longest == sequence.longest else 'in') +
              'correctly.')

def find_longest(limit, sequence):
    start, found = set(range(limit)), set()
    while start:
        number = start.pop()
        result = set(get_sequence(sequence(number), found))
        start -= result
        found |= result
    return number

def get_sequence(sequence, found):
    for number in sequence:
        if number in found:
            break
        yield number

if __name__ == '__main__':
    main(1000000)

代码的更正版本在其设计中遵循类似的模式,但会跟踪原始范围之外的值。已发现执行时间与解决该难题的其他 Python 解决方案相似。

def main():
    start, found = set(range(2, 1000000)), 1: 1
    while start:
        scope = reversed(tuple(sequence(start.pop(), found)))
        value = dict(map(reversed, enumerate(scope, found[next(scope)] + 1)))
        start -= frozenset(value)
        found.update(value)
    lengths = dict(map(reversed, found.items()))
    print(lengths[max(lengths)])

def sequence(n, found):
    while True:
        yield n
        if n in found:
            break
        n = 3 * n + 1 if n & 1 else n >> 1

if __name__ == '__main__':
    main()

【讨论】:

我不相信这个观察是准确的。还有其他人有进一步的见解吗? 我认为刚刚删除的关于订单任意的答案是正确的。没有理由假设 start 中的最后一个值导致最长序列。

以上是关于一个错误的答案显然意味着一个逻辑错误,但啥是错误的想法呢?的主要内容,如果未能解决你的问题,请参考以下文章

感想1

参考:啥是变量范围,哪些变量可以从哪里访问,啥是“未定义变量”错误?

参考:啥是变量范围,哪些变量可以从哪里访问,啥是“未定义变量”错误?

强类型视图中的错误消息 - 啥是“字典”?

Metal 命令缓冲区内部错误:啥是内部错误(IOAF 代码 2067)?

错误:需要左值作为赋值的左操作数,我收到此错误啥是左值和右值以及如何删除此错误