求教 Python 问题, GPT o1 跟 Gemini 2.0 都解决不了

26 天前
 superhot

首先我理解这样的写法并非最佳实践,只是想理解语言差异与背后的原因。

这是一道 LeetCode 题,讨论区里面每种语言都用了相同的解法,但只有 Python 以同样的写法陷入了死循环,问了 AI 都说两种写法等价,所以不应该存在 bug ,但实测显然是不同的,所以想问问原因。

注意看 while 循环里为两个指针连续赋值的地方。

C++ 版

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;
}

Java 版

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 = qp.next = p = q 是不等价的呢?分别都是怎样的步骤进行赋值的?

2324 次点击
所在节点    Python
15 条回复
gydi
26 天前
简单用 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/
rrfeng
26 天前
我觉得是 c 的遗留

a = ( b = c )

( b = c )这个赋值表达式是有值的,等于 c
wang93wei
26 天前
zhouyin
26 天前
@rrfeng
java 和 c++也是 c 系
照理说 a=b=c a 也等于 c 啊 不是吗?
wang93wei
26 天前
我之前写 SQL 的时候也犯过同样的问题。在 where 条件内弄了一个三个值的等式,取决于语言特性,导致 where 条件并不成立。
cmdOptionKana
26 天前
建议不要去搞懂 a=b=c ,同时不管哪种语言都不要使用 a=b=c ,一律拆开来写。
superhot
26 天前
@wang93wei 这个回答非常清晰…看来我跟 AI 对话时的描述与问题不够好
superhot
26 天前
@cmdOptionKana 我知道这种写法不好 只是邯郸学步 不小心踩了坑 想搞明白为啥摔了 真正工程中可不敢这么写
aijam
26 天前
a=b=c 在 python 里是 leftmost 所以相当于
aijam
26 天前
a=b=c 在 python 里是 leftmost 所以相当于
a=c
b=c

但在其他 c 系语言里相当于
b=c
a=b
aijam
26 天前
@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 本身,是一个循环链表
cmdOptionKana
26 天前
@superhot 懂了就可能忍不住用,还不如不懂,更容易杜绝使用。而且这个也没啥技术因素,纯粹人为规定,属于“偏好”,偏好是不值得学的。
rrfeng
26 天前
触发个报错更明显了:

```
>>> 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
```
NoOneNoBody
26 天前
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 属性,所以没有报错而已
franklinyu
26 天前
正因為這些奇奇怪怪的歧義,一些新語言直接不支持鏈式賦值,我覺得挺好

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://tanronggui.xyz/t/1100672

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX