【问题标题】:Using List comprehensions to solve Collatz conjecture使用列表推导解决 Collat​​z 猜想
【发布时间】:2022-06-15 14:27:01
【问题描述】:

有没有一种方法可以使用列表推导来验证 Collat​​z 猜想,而无需使用 while 语句或其他方法将 n 值附加到 ls 而无需在每个语句后添加 ls?

from random import choice
from time import sleep

n = choice([x for x in range(2, 99*99) if all(x%y != 0 for y in range(2, x))])
ls = []
ls.append(n)
while True:
    if n % 2 == 0:
        n = n // 2
        ls.append(n)
    elif n % 2 != 0:
        n = (3 * n) + 1
        ls.append(n)
    if n == 1:
        break
print(ls)

【问题讨论】:

  • while 循环有什么问题?
  • while 循环没有什么问题,但是对于较大的值,它似乎需要很长时间,而且对我个人而言,似乎有一种方法可以使这个循环更紧凑
  • 列表推导不能替代迭代逻辑,您需要提前确定需要执行多少次迭代
  • This 可能包含一些有用的讨论。
  • 没有办法清除破坏理解的方法(您可能可以定义一些自定义迭代器,但它会很丑陋和令人困惑)

标签: python list-comprehension


【解决方案1】:

一种方式(“缺少”初始数字,但我认为这对目的而言并不重要):

print(f'{n}:')
print([n := 3*n+1 if n%2 else n//2
       for _ in iter(lambda: n, 1)])

n = 92 的输出:

92:
[46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1]

还有一个 (ab) 使用基于 kolypto 的列表组合:

print([memo
       for memo in [[n]]
       for n in memo
       if n == 1 or memo.append(n//2 if n%2==0 else n*3+1)
      ][0])

n = 92 的输出:

[92, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1]

我仍然认为while 循环是合适的方式,但事实证明它并没有快很多。求解从 1 到 5000 的所有 n 的次数:

 78 ms  while_loop_ewz93
 87 ms  list_comp_Kelly
126 ms  list_comp_kolypto
 82 ms  list_comp_kolypto_Kellied

带有 ewz93 和 kolypto 的基准代码 (Try it online!) 已修改为不包括起始编号(为了更公平的比较):

from timeit import repeat

def while_loop_ewz93(n):
    ls = []
    while n != 1:
        n = n // 2 if n % 2 == 0 else (3 * n) + 1
        ls.append(n)
    return ls

def list_comp_Kelly(n):
    return [n := 3*n+1 if n%2 else n//2
            for x in iter(lambda: n, 1)]

def list_comp_kolypto(n):
    return [
        *(lambda memo: [
            memo.append(n // 2 if n%2==0 else n*3+1) or memo[-1]
            for n in memo
            if memo[-1] != 1
        ])([n])
    ]

def list_comp_kolypto_Kellied(n):
    return [
        memo
        for memo in [[n]]
        for n in memo
        if n == 1 or memo.append(n//2 if n%2==0 else n*3+1)
    ][0]

funcs = [
    while_loop_ewz93,
    list_comp_Kelly,
    list_comp_kolypto,
    list_comp_kolypto_Kellied,
]

for _ in range(3):
    for func in funcs:
        t = min(repeat(lambda: list(map(func, range(1, 5001))), number=1))
        print('%3d ms ' % (t * 1e3), func.__name__)
    print()

【讨论】:

  • 这实际上很酷,但是当我使用固定种子尝试此操作时,我会从 OP 或我的解决方案中得到不同的结果。似乎第一项总是丢失,并且 n%2 对我造成了无限循环,但是当我更改这两个部分时,它对我有用并给出完全相同的结果。
  • @ewz93 是的,我知道“丢失”的初始数字,我认为这并不重要,但我现在提到了。对于 n%2 无限循环,我猜您的意思是您从解决方案中删除了 == 0 ?这否定了真值,所以你要么需要做not n%2,要么切换两种可能的结果。
【解决方案2】:

好吧,while 是您在不知道需要多少步时使用的工具,因为这已经融入了寻找值猜想的逻辑中,因此没有真正的解决方法。我个人认为使用while 循环没有什么不好。

您仍然可以使代码更加紧凑和可读,同时保持while 循环,例如像这样:

from random import choice

n = choice([x for x in range(2, 99 * 99) if all(x % y != 0 for y in range(2, x))])

ls = [n]
while n != 1:
    n = n // 2 if n % 2 == 0 else (3 * n) + 1
    ls.append(n)
print(ls)

编辑:

@Kelly Bundys 答案的略微修改版本对我来说可以让它更加紧凑(但我们先不提可读性):

from random import choice

n = choice([x for x in range(2, 99 * 99) if all(x % y != 0 for y in range(2, x))])

ls = [n] + [n := n // 2 if n % 2 == 0 else (3 * n) + 1 for _ in iter(lambda: n, 1)]
print(ls)

【讨论】:

  • 我不知道你可以在同一行添加 if 和 else 值
  • 这些被称为三元运算符或条件表达式。它们(有时)是缩短代码并使其更具可读性的好方法(但你应该只在 if 条件是基本的东西时才使用它们,否则很难阅读)。查看此文档:book.pythontips.com/en/latest/…
  • @ewz93 那个页面说“他们在 2.4 版中成为 Python 的一部分”让我想知道该页面有多旧和最新:-P
【解决方案3】:
    seq = [
        *(lambda memo: [n] + [
            memo.append(n // 2 if n%2==0 else n*3+1) or memo[-1]
            for n in memo
            if memo[-1] != 1
        ])([n])
    ]

这不是纯粹的理解,而是某种意义上的。

核心思想是引入一个命名列表memo,我可以将其称为变量并将值推送到

【讨论】:

  • 现在在我的基准测试中包含了这个和一个更高效的版本(只构建一个完整的列表)。
  • "kolypto_kellied" :D :D :D
  • 顺便说一句,我喜欢迭代具有一个元素开头的备忘录列表的想法。我不确定我以前做过或见过。在这种情况下,它仍然是对列表理解的滥用,因为我们要丢弃一个或另一个列表。但是如果我们想要一个 multiple 序列的 list,对于 multiple 起始值,那么至少我的版本变成了一个相当合适的列表理解用法。 Demo.
【解决方案4】:
n=1024
ls=[n]
[ls.append(n//2 if n%2==0 else 3*n+1) for n in ls if n!=1]
print (ls)

【讨论】:

    【解决方案5】:

    这是一个更通用的替代方案,它允许以表达式的形式出现循环条件。

    import itertools
    
    n = 15
    h = [
        n := 3*n + 1 if n % 2 else n // 2
        for _ in itertools.takewhile(lambda _: n > 1, itertools.repeat(0))
        ]
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2011-06-23
      • 2019-03-09
      • 2012-06-08
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多