以下是Project Euler problem 36的解决方案,内容如下:
十进制数,585 = 1001001001 2(二进制),两个基数都是回文。
找出所有数字的总和,少于一百万,在基数10和基数2中是回文。
(请注意,任一基地的回文数字可能不包括前导零。)
我的结果有些不对劲。有人可以帮忙吗?
def findnum(n):
a = 0
for i in range(0, n + 1):
temp = '{0:08b}'.format(i)
if str(i) == str(i)[::-1] and str(temp) == str(temp)[::-1]:
a += i
return a
print findnum(1000000)
我得到的结果是872096,但正确的答案似乎是872187.但这两者之间的差异,91,不是回文。我做错了什么?
答案 0 :(得分:10)
你错过了问题描述中的一些内容:
请注意,任何基数的回文数字可能不包括前导。
然而,您正在使用前导零:
temp = '{0:08b}'.format(i)
从格式中删除08
;这里根本不需要将它填充到宽度:
temp = '{0:b}'.format(i)
通过此更改,您将得到正确答案。
您也可以使用format()
function代替,因为您没有将字符串放入更大的模板中。您不需要将其提供给str()
,因为format()
已生成字符串。我先交换测试来测试二进制回文并避免额外的str()
次调用。 n + 1
来电中range()
不需要;问题描述要求所有这些数字低于 100万:
for i in range(n):
temp = format(i, 'b')
if temp == temp[::-1] and str(i) == str(i)[::-1]:
a += i
或者你可以先测试小数回文,然后格式:
for i in range(n):
decimal = str(i)
if decimal != decimal[::-1]:
continue
binary = format(i, 'b')
if binary == binary[::-1]:
a += i
这削减了整个运行时的大量调用,使最终版本的速度提高了两倍以上:
>>> from timeit import timeit
>>> def findnum_orig(n):
... a = 0
... for i in range(0, n + 1):
... temp = '{0:b}'.format(i)
... if str(i) == str(i)[::-1] and str(temp) == str(temp)[::-1]:
... a += i
... return a
...
>>> def findnum_optimised(n):
... a = 0
... for i in range(n):
... decimal = str(i)
... if decimal != decimal[::-1]:
... continue
... binary = format(i, 'b')
... if binary == binary[::-1]:
... a += i
... return a
...
>>> timeit('fn(1000000)', 'from __main__ import findnum_orig as fn', number=10)
10.886759996414185
>>> timeit('fn(1000000)', 'from __main__ import findnum_optimised as fn', number=10)
3.7782959938049316
这是因为str()
比format()
要快得多:
>>> timeit('for i in range(1000): str(i)', number=1000)
0.17951107025146484
>>> timeit('for i in range(1000): format(i, 'b')', number=1000)
0.2837510108947754
由于只有1999年十进制和2000个二元回文数低于100万:
>>> sum(1 for i in range(1000000) if str(i) == str(i)[::-1])
1999
>>> sum(1 for i in range(1000000) if format(i, 'b') == format(i, 'b')[::-1])
2000
避免慢速操作除了那些1999年的十进制回文外,你可以节省很多时间。
我们可以通过切换将整数转换为二进制的方式来加快速度。 bin()
function也产生二进制数,尽管有0b
前缀。即使必须使用该函数删除该前缀也比使用format()
:
>>> timeit('for i in range(1000): format(i, "b")', number=1000)
0.46987009048461914
>>> timeit('for i in range(1000): bin(i)[2:]', number=1000)
0.24124693870544434
如果您使用的是Python 2.x,则还应使用xrange()
function来避免创建包含1.000.000整数的列表。这将最终时间安排到:
>>> def findnum_bin_xrange(n):
... a = 0
... for i in xrange(n):
... decimal = str(i)
... if decimal != decimal[::-1]:
... continue
... binary = bin(i)[2:]
... if binary == binary[::-1]:
... a += i
... return a
...
>>> findnum_bin_xrange(1000000)
872187
>>> timeit('fn(1000000)', 'from __main__ import findnum_bin_xrange as fn', number=10)
3.5071611404418945
这大约是原始代码时间的1/3。
答案 1 :(得分:0)
我的解决方案大约比Martijn快20倍。
该方法也略有不同,使用的数字必须向后读取相同的事实。我不是检查所有数字以查看它们是否是回文,而是自己生成它们。这引入了一些额外的代码行,因为你必须处理3种不同的情况:
主要的循环范围可以减少到1000.
首先使用上述策略生成所有回文数字(base10)。然后在base2中检查生成的数字。如果它们是回文数,则将它们添加到数组中。 (如果跳过数组并将其加到变量中,则进一步提高速度。)
import time
#calculate digits
def digits(num):
return len(str(num))
#reverse order
def reverse(num):
return int(str(num)[::-1])
#check binary counterpartner
def bin_pal_chk(number):
b = int(bin(number)[2:])
if (b == reverse(b)):
return True
else:
return False
#list with all base 10 palindromes
palin = []
#calculate palindromes
t = time.clock()
for i in range(1,1000):
#single digits
if (i < 10):
if bin_pal_chk(i):
palin.append(i)
#even digits
num = i*10**digits(i) + reverse(i)
if bin_pal_chk(num):
palin.append(num)
#uneven digits
if (i < 100):
for j in range(10):
num = i*10**(digits(i)+1) + j*10**digits(i) + reverse(i)
if bin_pal_chk(num):
palin.append(num)
print time.clock() - t