One way ("missing" the initial number, but I don't think that's important for the purpose):
print(f'{n}:')
print([n := 3*n+1 if n%2 else n//2
for _ in iter(lambda: n, 1)])
Output for n = 92
:
92:
[46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1]
And one (ab)using a list comp, based on kolypto's:
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])
Output for n = 92
:
[92, 46, 23, 70, 35, 106, 53, 160, 80, 40, 20, 10, 5, 16, 8, 4, 2, 1]
I still think the while
loop is the appropriate way, but it turns out it's not a lot faster. Times for solving all n from 1 to 5000:
78 ms while_loop_ewz93
87 ms list_comp_Kelly
126 ms list_comp_kolypto
82 ms list_comp_kolypto_Kellied
My iter(lambda: n, 1)
simulates while n != 1:
. More generally, while condition:
can be simulated with iter(lambda: bool(condition), False)
. (The explicit bool
isn't necessary if the condition already is a bool
, for example iter(lambda: mystring.startswith('x'), False)
.)
Benchmark code (Try it online!) with ewz93's and kolypto's modified to also not include the start number (for fairer comparison):
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()