【问题标题】:The recursive implementation with python for a mathematical puzzle of hailstone sequence or Collatz conjecture?用python递归实现冰雹序列或Collat​​z猜想的数学难题?
【发布时间】:2016-09-15 09:22:01
【问题描述】:

有一道数学题:

  1. 选择一个正整数 n 作为开始。
  2. 如果 n 是偶数,则将其除以 2。
  3. 如果 n 是奇数,则乘以 3,然后加 1。
  4. 继续此过程,直到 n 为 1。

我想写一个递归函数,它以n为参数,然后返回一个元组,其中包含进程中的n的轨道序列,以及序列的长度。但是失败了。

我的代码有什么问题?如何完成任务?

def hailstone(n):
    """the implementation of recursive one,
    return a tuple that includes the hailstone sequence
    starting at n, and the length of sequence.

    >>> a = hailstone(1)
    ([1, 4, 2, 1], 4)
    """
    if n % 2 == 0:
        n = n//2
    else:
        n = n*3 + 1

    if n == 1:
        return [n]
    else:
        """
        for some magic code here,
        it will return a tuple that includes the list track of n,
        and the length of sequence,
        like ([1, 4, 2, 1], 4)
        """
        return ([n for some_magic in hailstone(n)], lenght_of_seq)

【问题讨论】:

  • “失败”有任何进展吗?
  • 我不知道如何在递归调用中使用列表跟踪不断变化的 n?@jonrsharpe
  • 请将问题editminimal reproducible example解释实际问题,然后。
  • 你没有在两次返回中返回相同的对象!!另外,你试过len([n for some_magic in hailstone(n)]) 吗?它给出了数组的 len。这不是 C,这是一种进化的语言。所以你可以在返回值上使用len来获取长度。
  • 仅供参考,这是Collatz conjecture

标签: python algorithm recursion


【解决方案1】:

首先,您需要决定是使用迭代过程还是递归过程 - 在 Python 中这并不重要(因为 Python 不使用尾调用优化),但在语言中它可以。有关迭代/递归过程的更深入解释,请参阅this question

在任何一种情况下,通常最好从结束条件开始,然后从那里开始工作。在本例中为n == 1

递归过程

让我们从递归过程开始。在这种情况下,状态保持在调用链中,一旦我们到达最里面的调用(并且我们正在返回调用链),就会计算结果。

def hailstone(n):
    if n == 1:
        return [n], 1

好的,这就是结束条件 - 现在我们已经确保函数将在 n 为 1 时返回。让我们继续并添加其余条件:

def hailstone_rec(n):
    if n == 1:
        return (n,), 1

    if n % 2 == 0: # Even
        rest = hailstone_rec(n//2)
    else: # Odd
        rest = hailstone_rec(n*3+1)

    return (n,) + rest[0], rest[1] + 1

n 不是 1 时,这里发生的情况是,我们首先递归计算序列的其余部分,然后将当前调用的值添加到该结果中。因此,如果n 为 2,这意味着我们将计算 hailstone_rec(2//2),它会返回 ((1,), 1),然后我们将当前值相加并返回结果 (((2, 1), 2))。

迭代过程

在迭代过程中,结果是在沿着调用链进行时计算的,并且在递归时将当前状态传递给下一个函数调用。这意味着结果不依赖于调用链中更高层的调用,这意味着当到达终点时,最里面的函数可以返回结果。这在具有尾调用优化的语言中很重要,因为外部调用的状态可能会被丢弃。

当使用这个过程实现一个函数时,使用带有额外参数的内部辅助函数来传递状态通常很有帮助,所以让我们定义一个面向用户的函数和一个内部辅助函数:

# user facing function
def hailstone_it(n):
    # Call the helper function with initial values set
    return hailstone_it_helper(n, (), 0)

# internal helper function
def hailstone_it_helper(n, seq, length):
    pass

可以看出,面向用户的函数只是调用内部函数并将状态参数设置为初始值。让我们继续实现实际的逻辑,所有这些都将驻留在帮助程序中。和之前的解决方案一样,让我们​​从结束条件(n == 1)开始:

def hailstone_it_helper(n, seq, length):
    if n == 1:
        return seq + (n,), length + 1

这一次,我们从之前的调用中获取部分结果,添加当前值,然后返回。现在,处理剩下的情况:

def hailstone_it_helper(n, seq, length):
    if n == 1:
        return seq + (n,), length + 1

    if n % 2 == 0: # Even
        return hailstone_it_helper(n//2, seq + (n,), length + 1)
    else:
        return hailstone_it_helper(n*3+1, seq + (n,), length + 1)

def hailstone_it(n):
    return hailstone_it_helper(n, (), 0)

这里递归时,我们传入序列中的下一个 n(取决于当前 n 是偶数还是奇数),以及到目前为止的结果。

更 Python 的解决方案

最后,由于您通常不会在 Python 中编写这样的代码,因此让我们以一个更 Python 的解决方案作为奖励(即根本不使用递归的解决方案):

def hailstone_pythonic(n):
    seq = []
    while n != 1:
        seq.append(n)
        if n % 2 == 0:
            n //= 2
        else:
            n = n * 3 + 1
    seq.append(n)
    return tuple(seq), len(seq)

【讨论】:

  • 您的解释非常清晰,令人惊叹的答案,谢谢!
  • 您似乎删除了第一段中的链接。我发现您使用“迭代”来描述完成传递的尾部调用有点令人困惑。由于 python 不优化尾调用并且具有严格的递归限制,因此(实际)迭代解决方案绝对是首选。
  • @rici 这个术语在 CS 中很常见。重要的是,该解决方案不是迭代的,它使用迭代过程(我从未将其称为“迭代”)。这些概念通常在基于(或使用)SICP 的课程中​​教授。许多这样的课程已经过渡到使用 Python(而不是 Scheme 或 Common Lisp),并且在这样做的过程中,将他们的作业直接翻译成 Python,而不是调整它们以符合 Python 的方式。这在某种程度上是有道理的,因为目标是教授 CS 概念,而不是“好的 Python”。
【解决方案2】:

你可以使用累加器:

def hailstone(n):
    """
    return a tuple that includes the hailstone sequence
    starting at n, and the length of sequence.
    1) Pick a positive integer n as start.
    2) If n is even, then divide it by 2.
    3) If n is odd, multiply it by 3, and add 1.
    4) continue this process until n is 1.
    """

    def hailstone_acc(n, acc):
        acc.append(n)
        if n == 1:
            return acc, len(acc)
        elif n % 2 == 0:
            return hailstone_acc(n//2, acc)
        else:
            return hailstone_acc(3*n + 1, acc)

    if n == 1:
        return hailstone_acc(4,[1])
    else:
        return hailstone_acc(n, [])

现在,行动起来:

(trusty)juan@localhost:~/workspace/testing/hailstone$ python -i hailstone.py 
>>> hailstone(1)
([1, 4, 2, 1], 4)
>>> hailstone(4)
([4, 2, 1], 3)
>>> hailstone(23)
([23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1], 16)
>>> 

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2019-07-08
    • 2014-07-07
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多