我们有一个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> 57n=16 > 1
,但是从代码中不清楚它为什么起作用。所以我在下面有一些问题


可以用发电机来制作骑士的桌子吗?更清晰?
可以实现一个链表来使代码更清晰而不是取模吗?

我知道代码非常简单,而且解决的问题几乎是微不足道的。但是,由于我是python的初学者/新手,因此几乎没有任何建议。
*是否还有其他常规建议可以改善代码或运行速度?

评论

根据您的代码,我认为您正在使用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中则可以省略括号(但不建议这样做)。

仅供参考,这是具有数百年历史的约瑟夫斯问题

#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)listpopleft()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_numknights_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变量。