我们有一个
n
个骑士的圆桌会议,其中n
是一些正整数。这些未能满足疯狂国王的期望,并被判处死刑。然而,作为一种怜悯之举,他四处走动
杀死每隔一个骑士(从第二个开始)
,直到只剩下一个骑士。
问题是:哪个座位坐您需要选择生存吗?
我写了下面的原始代码来解决这个问题:
while True:
temp_num = input("Enter the number of knights: ")
try:
temp_num = int(temp_num)
if temp_num <= 0: # if not a positive int print message and ask for input again
print("Sorry, the number of knights must be a positive integer, try again")
else:
knights_num = temp_num
break
except ValueError:
print("That's not an integer!")
# else all is good, val is >= 0 and an integer
# Create a table of knights and a temp table
knights_table = [i for i in range(1,knights_num+1)]
knights_temp_table = list(knights_table)
i = 1
while len(knights_temp_table)>1:
# print("Knights =",knights_temp_table , "(",knights_temp_table[i],")")
knights_temp_table.pop(i)
i = (i+1)%len(knights_temp_table)
print( "The knight who survived was no.",knights_temp_table[0] )
代码运行并输出期望值
n=60> 57
和n=16 > 1
,但是从代码中不清楚它为什么起作用。所以我在下面有一些问题
可以用发电机来制作骑士的桌子吗?更清晰?
可以实现一个链表来使代码更清晰而不是取模吗?
我知道代码非常简单,而且解决的问题几乎是微不足道的。但是,由于我是python的初学者/新手,因此几乎没有任何建议。
*是否还有其他常规建议可以改善代码或运行速度?
#1 楼
使用函数您可以将整个输入循环移到一个函数中。称其为合理的东西:
def get_num_knights():
while True:
num = input("Enter the number of knights: ")
try:
num = int(num)
if num <= 0: # if not a positive int print message and ask for input again
print("Sorry, the number of knights must be a positive integer, try again")
else:
return num
except ValueError:
print("That's not an integer!")
很好,其原因有两个:模块化始终很不错,现在您不必担心名称冲突。只需在内部使用
num
,完成后再使用即可。两个列表
此:
knights_table = [i for i in range(1,knights_num+1)]
knights_temp_table = list(knights_table)
使两个
return
s。第一个是产生列表list
的列表理解,第二个只是复制它。没有理由重复。此外,range(1, knights_num + 1)
已经是
list
中的列表。在python2.7
中不是,但是您没有标记版本,但是使用了括号python3
。零索引
Python是零索引的。列表
print
的第一个元素是lst
,而不是lst[0]
。弹出第一个元素时,您需要执行lst[1]
而不是pop(0)
。因此,您的pop(1)
循环全部消失了-您从第二个骑士而不是第一个开始。改进的解决方案
knight_table = list(range(get_num_knights()))
i = 0
while len(knight_table) > 1:
knight_table.pop(i)
i = (i+1) % len(knight_table)
print( "The knight who survived was no.", knights_temp_table[0]+1 )
避免
pop()
现在,对于特别长的骑士来说,
pop()
的时间可能会主导代码运行时。 pop()
毕竟是线性时间-我们必须将元素逐一向下移动。因此,以下代码可能会表现更好:num = get_num_knights()
knight_table = [True] * num
i = 0
while num > 1:
knight_table[i % len(knight_table)] = False
num -= 1
living = 0
while living < 2:
i += 1
living += knight_table[i % len(knight_table)]
print( "The knight who survived was no.", i % len(knight_table))
评论
\ $ \ begingroup \ $
“您从第二个骑士开始,而不是第一个。”我认为这是故意的,如果每隔一个骑士就被弹出。
\ $ \ endgroup \ $
–SuperBiasedMan
2015年9月14日15:57
\ $ \ begingroup \ $
@Barry我不同意。彼此暗示第一个不是另一个
\ $ \ endgroup \ $
– njzk2
2015年9月15日19:16
#2 楼
那些可怜的骑士....他们该怎么做呢?但是,有一种数学的方法可以解决这个问题,需要O(1)的时间和空间。
是什么意思?好吧,这意味着解决一个1个骑士的问题所花的时间(和同样多的内存)与解决10个或1000个或1000000000000000个骑士的时间一样长。这怎么可能?
研究此问题时会出现一种模式。如果有1个骑士,则1个骑士幸存。如果有2个,则骑士1个幸存下来,如果我们将其映射为第一个32个骑士,则得到:
1 -> 1
2 -> 1
3 -> 3
4 -> 1
5 -> 3
6 -> 5
7 -> 7
8 -> 1
9 -> 3
10 -> 5
11 -> 7
12 -> 9
13 -> 11
14 -> 13
15 -> 15
16 -> 1
17 -> 3
18 -> 5
19 -> 7
20 -> 9
21 -> 11
22 -> 13
23 -> 15
24 -> 17
25 -> 19
26 -> 21
27 -> 23
28 -> 25
29 -> 27
30 -> 29
31 -> 31
32 -> 1
请注意,在2的幂处,幸存的骑士跳回到骑士1。此外,在两次幂之间,幸存的骑士会增加2(并且总是奇数)。
模式也很容易枚举-找到小于(或等于)计数的2的最大幂。找到该能力后有多少骑士,将其加倍,再增加一个。
看起来像这样:
import math
def low_power(input):
return 1 << int(math.floor(math.log(input, 2)))
def survivor(count):
return 1 + 2 * (count - low_power(count))
所以,调用
survivor(16)
将返回1。survivor(60)
返回57。但是,您现在也可以执行疯狂的数字,例如survivor(98765432123456789)
是53415676171057707 请参见在ideone上运行此代码:Knights
请注意,您现已确认正在使用Python 3.4。 Python 3.1引入了
bit_length()
方法。这使确定功率变得更加容易(从编程角度看-可以将位长视为log2的实现):def low_power(input):
return 1 << (input.bit_length() - 1)
相同的代码运行bit_length ideone中显示了python 3中的indone(也包含python 3打印语句的正确括号)。
评论
\ $ \ begingroup \ $
@holroy-是的,我很讨厌代码审查,审查基本上是“以其他方式执行”,这是算法审查,而不是代码审查。其他答案也涵盖了许多非算法方面(继续,并对其进行+1)。
\ $ \ endgroup \ $
–rolfl
2015年9月14日在17:31
\ $ \ begingroup \ $
不,确实没有很好的答案。作为数学本科生,我也得出了相同的结论。但是如上所述,我想提高编程方面的生锈技能,而不是数学。^^
\ $ \ endgroup \ $
– N3buchadnezzar
2015年9月14日20:17在
#3 楼
记住,这是一个圆桌会议。因此,如果它是一个圆桌会议……有什么比圆形缓冲区更好的使用呢?from collections import deque
knights_table = deque(range(1, knights_num + 1))
那么您可以更简单地模拟它: />
while len(knights_table) != 1:
knights_table.popleft() # killed
knights_table.rotate(-1) # skipped
print("The knight who survived was no.", knights_table.pop())
一个很大的优势(除了简单性)是,而
pop(i)
是O(i) = O(n)
的list
,popleft()
和rotate(-1)
是O(1)
的deque
。knights_num
就像其他人提到的那样但是,此代码是不完善的:def get_num_knights():
while True:
num = input("Enter the number of knights: ")
try:
num = int(num)
if num <= 0: # if not a positive int print message and ask for input again
print("Sorry, the number of knights must be a positive integer, try again")
else:
return num
except ValueError:
print("That's not an integer!")
您的
try
涵盖的范围远远超出所需范围,这很危险。限制其覆盖范围。我个人也愿意使用if not <wanted condition>
而不是if <problematic condition>
,因为白名单通常比黑名单更简单,更安全。 YMMV。def get_num_knights():
while True:
num = input("Enter the number of knights: ")
try:
num = int(num)
except ValueError:
print("That's not an integer!")
continue
if not num > 0:
print("Sorry, the number of knights must be a positive integer, try again.")
continue
return num
评论
\ $ \ begingroup \ $
最好的Python优雅
\ $ \ endgroup \ $
–雷神召唤师
2015年9月22日下午5:01
\ $ \ begingroup \ $
好的答案。您输入功能的一个小选择。在这里,您将一个字符串分配给一个名为number的变量。仅用作int的输入。我要么给它自己的名字,要么直接将输入移动到try块中:num = int(input(...
\ $ \ endgroup \ $
– magu_
16年7月1日在16:08
\ $ \ begingroup \ $
@magu_将输入移到try中是不好的做法,因为应将输入调用中的错误与int调用中的错误处理方式不同。如果要在引用字符串时给它起一个不同的名称,请放心,但是我自己没有必要这样做。
\ $ \ endgroup \ $
–Veedrac
16年7月1日在23:15
#4 楼
我将回答您的问题,然后提供更多一般性建议。使用生成器的方式无论如何都将涉及列出。的确,因为列表会很小并且不会占用很多空间,所以这不是必须使用生成器表达式的情况。评论。
try except
的方法很杂乱,也不是很清楚,因为当您似乎应该以2s的速度向上跳跃以彼此跳过时,您会加1。链接列表在Python中并不常用,所以我现在在很大程度上也将参考答案2。
现在寻求一般建议。不需要时,应避免使用临时变量。没有真正的理由在这里分开
temp_num
和knights_num
。您可能以前曾经需要,但是如果没有它,则当前代码是密闭的。此外,在
try
块中尽可能少地插入行也是很好的。有时是为了避免错误,而且还可以提高可读性。您可以在您的except子句中使用continue
关键字,然后将其他行向下移动。我也想翻你的if
。如果该数字有效,则可以break
。否则,解释数必须为正。这样,您的print
不必放在else
块中。所以这是我改写此开口的方法:
while True:
knights_num = input("Enter the number of knights: ")
try:
knights_num = int(knights_num)
except ValueError:
print("That's not an integer!")
if knights_num > 0:
break
print("Sorry, the number of knights must be a positive integer, try again")
您会注意到我也删除了注释,如果没有注释,您的if条件非常清楚。您可能还希望阻止用户输入1,这在理论上是有效的,但是与程序的意图不符。
现在,这里有很多冗余:
knights_table = [i for i in range(1,knights_num+1)]
knights_temp_table = list(knights_table)
首先,您可以直接将
range
分配给列表,而无需了解列表。您也不需要复制knights_table
,因为以后只使用这些变量之一。因此,再次剪切temp变量。
评论
根据您的代码,我认为您正在使用Python 2.7。但是可以确认吗?您的代码在不同版本中的运行方式不同。您可能也对这些Java评论感兴趣:codereview.stackexchange.com/questions/56951/…/codereview.stackexchange.com/questions/86023/…
哦,我正在运行3.4这些区别是什么?我以为这主要是使用括号。
@ N3buchadnezzar在Python3范围内将直接返回生成器函数而不是列表,这可能就是为什么您调用list()的原因。在Python 2.x中,range仅直接返回一个列表,从而使该步骤可跳过。除此之外,唯一的不同是print函数需要在3中加上括号,而在2中则可以省略括号(但不建议这样做)。
仅供参考,这是具有数百年历史的约瑟夫斯问题