有没有更符合Python风格的方法来编写多个比较?

5
我正在编写一个简单的纸牌游戏(类似Snap)。我已经让它正常运行了,但我感觉应该有更加优雅的解决方案。 给出一组赢得条件:
Y赢R
R赢B
B赢Y
等等

我想比较两个玩家的卡牌并将两张卡牌分配给赢家。 注意:我是在中学教学(没有OOP),希望能与学生讨论生成的代码。

我将最终条件留作elif,因为我想回头向选项列表添加更多卡牌。

if-elif链没有问题;我想知道是否有更加优美的解决方案。

    #I have code that randomly selects from a list, but this is the basic 
    #idea:
    p1=input("enter r,y or b")  
    p2=input("enter r,y or b")  

    stack1=[]  
    stack2=[]  

    if   p1=="r" and p2=="b":  
        stack1.extend([p1,p2])  
    elif p1=="y" and p2=="r":  
        stack1.extend([p1,p2])  
    elif p1 =="b" and p2 =="y":  
        stack1.extend([p1,p2])  
    elif p2 =="r" and p1 =="b":  
        stack2.extend([p1,p2])  
    elif p2 =="y" and p1 =="r":  
        stack2.extend([p1,p2])             
    elif p2 =="b" and p1 =="y":  
        stack2.extend([p1,p2])  

    print(stack1)  
    print(stack2)  

我从剩余代码中摘录了这段代码 - 所有卡片都是随机生成的,因此实际上不需要用户输入。


3
最后三个elif可以简写为else: stack2.extend(....) - Ocaso Protal
1
如果 if p1 + p2 in {'rb', 'yr', 'by'}: stack.extend([p1, p2]) 等等呢? - Chris_Rands
谢谢大家的帮助。我现在有足够的资源来进行一系列的课程了。@OcasoProtal - 我简直不敢相信我错过了那个。Chris_Rands - 我已经将这个想法用于扩展任务中了。RocketLL - 那很好,而且与我们之前做的石头剪刀布任务相吻合。Goyo - 我同意,并且我会和他们讨论一些问题。这一切都是因为我发现一个学生有超过1200行代码,因为他正在将每张牌与牌堆中的每张牌进行比较。 - MisterPhil
这是我想出来的最简洁版本:stack1.extend([p1, p2]) if (p1 + p2) in ['rb', 'yr', 'by'] else stack2.extend([p1,p2]) - MisterPhil
5个回答

5
创建一个新字典,使得Y、R和B分别映射到0、1、2。
win_map = {"Y": 0, "R": 1, "B": 2}

我们可以看到这里存在一个循环关系。0打败1,1打败2,2打败0。前两种情况可以简单使用>来确定,但是需要考虑第三种情况。通过巧妙的方法,我们可以看到我们可以通过添加1和使用模运算符%来实现"包裹"。 (0+1) % 3 == 1(1+1) % 3 == 2(2+1) % 3 == 0 ,这3种情况是唯一确定胜者的情况。
if (win_map[p1] + 1) % 3 == win_map[p2]: ...  # p1 wins
else if (win_map[p2] + 1) % 3 == win_map[p1]: ... # p2 wins

我不确定这个方法是否能够清晰地传授给学生,但这是一个更简洁的解决方案。

注意:当使用更多的卡片时,这种循环关系将会被打破,此方法将无法使用。


1
加1分的优雅,这绝对是减少重复性的重要步骤,但请注意:“添加额外卡片”的要求将会被破坏(因为每张卡片只能击败/输给一张卡片,所以例如5张卡片会产生无法确定胜者的对手)。 - Adam.Er8
@Adam.Er8 真的,使用卡牌计数变量进行了编辑。 - RocketLL
1
它还有更多的内容 - 每张卡牌只赢/输给一个选项(因为%只给出一个数字)。每一对可能的组合都应该有一个结果,就像剪刀石头布蜥蜴斯波克游戏一样。 - Adam.Er8
1
@Adam.Er8 哦,没错。已编辑。 - RocketLL

3
因此,你的胜利条件看起来像是一组(获胜者,失败者)对,并将你的输入(p1,p2)与它们进行比较似乎是最简单的事情。
win_conditions = {
    ('y', 'r'),
    ('r', 'b'),
    ('b', 'y')
}

p1=input("enter r,y or b")
p2=input("enter r,y or b")

stack1=[]
stack2=[]

if (p1, p2) in win_conditions:
    stack1.extend([p1,p2])
elif (p2, p1) in win_conditions:
    stack2.extend([p1,p2])
else:
    raise ValueError('{} and {} cannot beat each other.'.format(p1, p2))

请注意,如果您假设获胜条件是穷尽的,则可以简化代码。
如果您将低级操作封装在具有适当名称的函数中,那么意图更加明显,这样可以提高可读性,我认为您会对学生们有所帮助。
def beats(p1, p2):
    return (p1, p2) in win_conditions

if beats(p1, p2):
    stack1.extend([p1,p2])
elif beats(p2, p1):
    stack2.extend([p1,p2])
else:
    raise ValueError('"{}" and "{}" cannot beat each other.'.format(p1, p2))

也许您可以为通过扩展列表实现的任何目标寻找更好的名称。

1
“标准”的解决方案,适用于像您这样的小规模问题,是将所有可能性放入地图中:
result_map = { ('r', 'b'): 1, ('b', 'y'): 1, ('y', 'r'): 1, 
('b', 'r'): 2, ('y', 'b'): 2, ('r', 'y'): 2 }
v = result_map.get((p1, p2), None)
if v == 1:
    stack1.extend([p1, p2])
elif v == 2:
    stack2.extend([p1, p2])

为什么这样做?因为它为您提供了一种更轻松的方式来更改胜利/失败条件(只需更改一个字典),胜利/失败规则可以完全任意,并且很容易遵循代码(想象一下,如果您有一些奇怪的if-else条件,有人来查看此代码并想知道正在发生什么以及为什么)。

0

你重复了太多次同样的东西,有两个 extend 调用被重复了 3 次。

你可以通过使用 or 关键字来大大简化你的代码:

# extend to "stack1"
if (p1 == "r" and p2 == "b") or (p1 == "y" and p2 == "r") or (p1 == "b" and p2 == "y"):
  stack1.extend([p1, p2])

# extend to "stack2"
elif (p2 == "r" and p1 == "b") or (p2 == "y" and p1 == "r") or (p2 == "b" and p1 == "y"):
  stack2.extend([p1, p2])

祝你好运。


我认为你也重复了太多次同样的事情:只是交换了两个变量的相同长链比较。将其封装在一个函数中不仅可以提高可读性和澄清意图,还可以消除重复并将低级细节(如何找出获胜者?)从代码的其余部分抽象出来。 - Stop harming Monica

0

只是另一种比较的方式,也许可以在for循环或字典映射中使用,以帮助重构您的代码,如果必要且性能不重要。

from operator import and_ 
from functools import partial

def compare(a, b, c, d):
    return and_(a == c, b == d) 

p1 = 'r'
p2 = 'b'
cmp_p1_p2 = partial(compare, p1, p2)
cmp_p2_p1 = partial(compare, p2, p1)

cmp_p1_p2('r', 'b')
# True
cmp_p2_p1('b', 'r')
# True

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接