海象运算符不分配变量?

Walrus Operator Doesn't Assign Variable?

玩海象算子,我实现了归并排序:

def mergesort(array):
    if len(array) == 1:
        output = array
    else:
        pivot = len(array) // 2
        left = mergesort(array[pivot:])
        right = mergesort(array[:pivot])
        output = []
        while (l := len(left)) or (r := len(right)):
            if l and r and left[0] < right[0]:
                output.append(left.pop(0))
            elif r:
                output.append(right.pop(0))
            else:
                output.append(left.pop(0))
        
    return output

mergesort([66, 93, 85, 46, 56, 88, 56, 75, 55, 99, 87])

但是这个returns错误UnboundLocalError: local variable 'r' referenced before assignment:

---------------------------------------------------------------------------
UnboundLocalError                         Traceback (most recent call last)
/tmp/ipykernel_134/1678992391.py in <module>
----> 1 mergesort(array)

/tmp/ipykernel_134/4030760045.py in mergesort(array)
      5     else:
      6         pivot = len(array) // 2
----> 7         left = mergesort(array[pivot:])
      8         right = mergesort(array[:pivot])
      9         

...

/tmp/ipykernel_134/4030760045.py in mergesort(array)
     10         output = []
     11         while (l := len(left)) or (r := len(right)):
---> 12             if l and r and left[0] < right[0]:
     13                 output.append(left.pop(0))
     14             elif r:

UnboundLocalError: local variable 'r' referenced before assignment

为什么 r 不包含在我的 for 循环中,而 l 包含?

  1. Python 获取 len(left) 表达式并将其计算为 True
  2. 这意味着它可以立即进入 while 循环而无需检查第二条语句 len(right),因为 Python 是 lazy
  3. 但是,这意味着 r 没有实例化,因为它没有被计算

不幸的是,在表达式两边加上额外的括号没有任何作用。

例如while ( (l:=len(left)) or (r:=len(right)) ): 无效。


解决此问题的一个选项是使用 + 而不是 or,因为它们具有相同的逻辑含义,但 + 需要计算表达式的右侧.

例如

def mergesort(array):
    if len(array) == 1:
        output = array
    else:
        pivot = len(array) // 2
        left = mergesort(array[pivot:])
        right = mergesort(array[:pivot])
        output = []
        while (l:=len(left)) + (r:=len(right)):
            if l and r and left[0] < right[0]:
                output.append(left.pop(0))
            elif r:
                output.append(right.pop(0))
            else:
                output.append(left.pop(0))
        
    return output

顺便说一句,如果您在 while X and Y 中遇到同样的问题,其中 X=0,您可以使用与上述类似的技巧,但不要将 or 替换为 + 你用 * 替换了 and 因为 0 * n == 0。 IE。 while X * Y

Boolean OR or 是惰性的,所以当 l 结果为真时, (r := len(right)) 甚至不会被执行。

在这种情况下,您可以使用非惰性 bitwise OR |,尽管这有点滥用。

或者只使用列表的 truth value 而不是它们的长度:

while left or right:
    if left and right and left[0] < right[0]:
        output.append(left.pop(0))
    elif right:
        output.append(right.pop(0))
    else:
        output.append(left.pop(0))

顺便说一句,最好使用 <= 而不是 <,这样它是一个稳定的排序,因为合并排序应该是。

附录:享受懒惰的乐趣:

while left or right:
    which = (left or right)[0] <= (right or left)[0] and left or right
    output.append(which.pop(0))

另一个,注意我切换到while ... and ...并在循环后追加剩余的非空:

while left and right:
    which = left if left[0] <= right[0] else right
    output.append(which.pop(0))
output += left or right

或者回到你的风格:

while left and right:
    if left[0] <= right[0]:
        output.append(left.pop(0))
    else:
        output.append(right.pop(0))
output.extend(left or right)