首先我理解这样的写法并非最佳实践,只是想理解语言差异与背后的原因。
这是一道 LeetCode 题,讨论区里面每种语言都用了相同的解法,但只有 Python 以同样的写法陷入了死循环,问了 AI 都说两种写法等价,所以不应该存在 bug ,但实测显然是不同的,所以想问问原因。
注意看 while
循环里为两个指针连续赋值的地方。
ListNode *partition(ListNode *head, int x) {
ListNode node1(0), node2(0);
ListNode *p1 = &node1, *p2 = &node2;
while (head) {
if (head->val < x)
p1 = p1->next = head;
else
p2 = p2->next = head;
head = head->next;
}
p2->next = NULL;
p1->next = node2.next;
return node1.next;
}
public ListNode partition(ListNode head, int x) {
ListNode smallerHead = new ListNode(0), biggerHead = new ListNode(0);
ListNode smaller = smallerHead, bigger = biggerHead;
while (head != null) {
if (head.val < x) {
smaller = smaller.next = head;
} else {
bigger = bigger.next = head;
}
head = head.next;
}
// no need for extra check because of fake heads
smaller.next = biggerHead.next; // join bigger after smaller
bigger.next = null; // cut off anything after bigger
return smallerHead.next;
}
我自己翻译的 Python 版,然而死循环导致超时:
class Solution:
def partition(self, head: Optional[ListNode], x: int) -> Optional[ListNode]:
lt, ge = ListNode(), ListNode()
ltPtr, gePtr = lt, ge
while head:
if head.val < x:
ltPtr = ltPtr.next = head
else:
gePtr = gePtr.next = head
head = head.next
ltPtr.next, gePtr.next = ge.next, None
return lt.next
翻了一下讨论区,下面的是没问题的 Python 版:
class Solution:
def partition(self, head: ListNode, x: int) -> ListNode:
head1 = tail1 = ListNode()
head2 = tail2 = ListNode()
while head:
if head.val < x:
tail1.next = tail1 = head
else:
tail2.next = tail2 = head
head = head.next
tail2.next = None
tail1.next = head2.next
return head1.next
问题就出在了为两个指针连续赋值的地方,所以为什么在 Python 中,p = p.next = q
与 p.next = p = q
是不等价的呢?分别都是怎样的步骤进行赋值的?
1
gydi 16 小时 34 分钟前
简单用 python 测试了一下,有下面这样的结果。
>>> b = {'next': 'b'} >>> c = {'next': 'c'} >>> b = b['next'] = c >>> print(b,c) {'next': {...}} {'next': {...}} >>> b = {'next': 'b'} >>> c = {'next': 'c'} >>> b['next'] = b = c >>> print(b,c) {'next': 'c'} {'next': 'c'} 于是猜测 python 的执行对于 a=b=c 来说应该是 a=c;b=c 这样。然后搜了一下,发现这个文章 https://chowyi.com/key-points-of-chained-assignment-in-python/ |
2
rrfeng 16 小时 28 分钟前 via Android
我觉得是 c 的遗留
a = ( b = c ) ( b = c )这个赋值表达式是有值的,等于 c |
3
wang93wei 16 小时 25 分钟前
|
5
wang93wei 16 小时 23 分钟前
我之前写 SQL 的时候也犯过同样的问题。在 where 条件内弄了一个三个值的等式,取决于语言特性,导致 where 条件并不成立。
|
6
cmdOptionKana 16 小时 21 分钟前 3
建议不要去搞懂 a=b=c ,同时不管哪种语言都不要使用 a=b=c ,一律拆开来写。
|
8
superhot OP @cmdOptionKana 我知道这种写法不好 只是邯郸学步 不小心踩了坑 想搞明白为啥摔了 真正工程中可不敢这么写
|
9
aijam 16 小时 7 分钟前
a=b=c 在 python 里是 leftmost 所以相当于
|
10
aijam 16 小时 6 分钟前 1
a=b=c 在 python 里是 leftmost 所以相当于
a=c b=c 但在其他 c 系语言里相当于 b=c a=b |
11
aijam 15 小时 56 分钟前
@aijam
所以其他的语言里 p1 = p1->next = head; p1->next 先被指向了 head (此时 p1 还不是 head ),p1 才被指向 head 但在 Python 里,ltPtr = ltPtr.next = head ltPtr 先被指向了 head, ltPtr.next 才被指向 head ,此时 head.next 就被指向了 head 本身,是一个循环链表 |
12
cmdOptionKana 15 小时 53 分钟前
@superhot 懂了就可能忍不住用,还不如不懂,更容易杜绝使用。而且这个也没啥技术因素,纯粹人为规定,属于“偏好”,偏好是不值得学的。
|
13
rrfeng 15 小时 35 分钟前
触发个报错更明显了:
``` >>> b={'n':0} >>> >>> b = b['n'] = 1 Traceback (most recent call last): File "<stdin>", line 1, in <module> TypeError: 'int' object does not support item assignment ``` |
14
NoOneNoBody 14 小时 46 分钟前
python 表达式计算顺序从左至右,且参看手册 Lexical analysis 最后部分
https://docs.python.org/3/reference/lexical_analysis.html#delimiters = 属于分隔符,而多个分隔符两侧优先级相同,则从左至右 最左边变量先得到值,中间变量后得到值 假设 p,q 都含有 next 属性,p.next 为整数,q.next 为 None p.next = p = q p 后赋值,p.next 最终为 None ,但它是两次变化:先是 q 对象,p 赋值为 q 对象时,p.next 也变为 q.next 的值 实际上只写 p=q 就够了,先执行的 p.next=q 被后面 p“覆盖”了 p = p.next = q p.next 后赋值,p.next 为 q 对象,p.next.next 才是 None 这个过程 p.next 两次变化:第一次为 None ,第二次是 q 对象 因为两次变化,实际上最终 p!=q *** 最主要原因是 python 大部分赋值为“引用” 懂的话也不用解释,不懂的话,很难一两句说清,请查阅手册和相关文章 但可以“傻瓜式”理解,就是除了 int/bool, float, complex, str, bytes 这些单值以外,复合的类型全部都要考虑引用问题 最简单例子( b 引用 a ): a=[1,2,3] b=a c=[1,2,3] a[1]=5 print(b,c) # 显示为[1,5,3], [1,2,3] 所以,如果上述 q 为单值的话(应该说没有 next 属性),p=p.next=q 会直接报错,此题是因为所赋值恰好也有 next 属性,所以没有报错而已 |
15
franklinyu 9 小时 52 分钟前 via iPhone
正因為這些奇奇怪怪的歧義,一些新語言直接不支持鏈式賦值,我覺得挺好
|