[英]python a,b = b,a implementation? How is it different from C++ swap function?
当我想尝试 python 版本时遇到了这个问题: https : //leetcode.com/problems/first-missing-positive/discuss/17071/My-short-c++-solution-O( 1)-space- and -准时
我不知道为什么a[0], a[a[0]] = a[a[0]], a[0]
这个不做交换?
>>> nums
[2, 1, 0]
>>> a = [2,1,0]
>>> a[0], a[a[0]] = a[a[0]], a[0]
>>> a
[2, 1, 0]
>>> a[0]
2
>>> a[0],a[2] = a[2], a[0]
>>> a
[0, 1, 2]
我的猜测是 a, b = b, a 语法的实现是这样的:
tmp = a[0] (tmp = 2)
a[0] = a[a[0]] (a[0] = a[2] = 0)
a[a[0]] = tmp (a[a[0]] = a[0] = tmp = 2)
然后我检查了C++中swap函数的实现。 我对 C++ 一无所知,但看起来想法是一样的: http : //www.cplusplus.com/reference/algorithm/swap/
The behavior of these function templates is equivalent to:
template <class T> void swap (T& a, T& b)
{
T c(std::move(a)); a=std::move(b); b=std::move(c);
}
template <class T, size_t N> void swap (T (&a)[N], T (&b)[N])
{
for (size_t i = 0; i<N; ++i) swap (a[i],b[i]);
}
我们有 c = a,然后 a = b 和 b = a 那么为什么 C++ 交换函数没有这个问题呢? 以及如何以 Pythonic 的方式编写这种交换函数?
这种行为确实与Python对类型表达式求值的方式有关
a,b=b,a
实际上,Python 所做的首先是通过创建元组(b,a)
来“准备”右侧的值。 然后这个元组被解包并以相反的顺序分配给变量。
需要注意的是,尽管 Python 使用对对象的引用,但如果变量名称引用的对象引用不可变类型的值,则它们可能会更改。 可变类型并非如此( 在 Python FAQ 中以示例说明)。
要使用您使用的可变类型(列表)分解示例:
a = [2,1,0]
a[0], a[a[0]] = a[a[0]], a[0]
a[a[0]]
从列表a
(值0
)的a[0]
元素(等于2
)中获取值。a[0]
是2
因此创建的元组是(0,2)
(0,2)
被解包, 0
替换列表中的2
(第 0 个元素)。a[a[0]]
可以理解为:以列表的第0个元素a
(这是目前0
),然后在该位置与替换该列表中的值2
从元组拆包(现在0
被替换为2
-这使操作看起来对列表没有任何作用)。 正如von Oak
的回答中所建议的那样,更改顺序会有所帮助,因为上面第 4 点的步骤不会再次替换该值。
我建议您参考通过赋值的答案来理解函数和参数传递。
要理解这一点,您需要使用dis
深入实现。
首先让我们考虑一个简单的交换函数:
from dis import dis
def swap(i, j):
i, j = j, i
dis(swap)
输出字节码:
4 0 LOAD_FAST 1 (j)
2 LOAD_FAST 0 (i)
4 ROT_TWO
6 STORE_FAST 0 (i)
8 STORE_FAST 1 (j)
10 LOAD_CONST 0 (None)
12 RETURN_VALUE
你可以看到有ROT_TWO这意味着
交换最顶部的两个堆栈项。
这个ROT_TWO
主要负责交换。
现在来回答你的问题:
让我们以正在运行的示例为例:
from dis import dis
def swap():
a = [2, 1]
a[0], a[1] = a[1], a[0]
dis(swap)
输出字节码:
4 0 LOAD_CONST 1 (2)
2 LOAD_CONST 2 (1)
4 BUILD_LIST 2
6 STORE_FAST 0 (a)
5 8 LOAD_FAST 0 (a)
10 LOAD_CONST 2 (1)
12 BINARY_SUBSCR
14 LOAD_FAST 0 (a)
16 LOAD_CONST 3 (0)
18 BINARY_SUBSCR
20 ROT_TWO
22 LOAD_FAST 0 (a)
24 LOAD_CONST 3 (0)
26 STORE_SUBSCR
28 LOAD_FAST 0 (a)
30 LOAD_CONST 2 (1)
32 STORE_SUBSCR
34 LOAD_CONST 0 (None)
36 RETURN_VALUE
输出字节码类似于我们在它是一个简单的交换函数时所拥有的。
但是当代码改变时:
from dis import dis
def swap():
a = [1, 0]
a[0], a[a[0]] = a[a[0]], a[0]
dis(swap)
swap()
输出是
4 0 LOAD_CONST 1 (1)
2 LOAD_CONST 2 (0)
4 BUILD_LIST 2
6 STORE_FAST 0 (a)
5 8 LOAD_FAST 0 (a)
10 LOAD_FAST 0 (a)
12 LOAD_CONST 2 (0)
14 BINARY_SUBSCR
16 BINARY_SUBSCR
18 LOAD_FAST 0 (a)
20 LOAD_CONST 2 (0)
22 BINARY_SUBSCR
24 ROT_TWO
26 LOAD_FAST 0 (a)
28 LOAD_CONST 2 (0)
30 STORE_SUBSCR
32 LOAD_FAST 0 (a)
34 LOAD_FAST 0 (a)
36 LOAD_CONST 2 (0)
38 BINARY_SUBSCR
40 STORE_SUBSCR
42 LOAD_CONST 0 (None)
44 RETURN_VALUE
您可以看到顶部两项相同的输出字节码。 因此它不会交换
也很容易在纸上(例如在求职面试中)考虑它,并且您不需要将代码调试或反汇编为字节码以进行理解。
我也认为它与 C++ 中交换函数的实现没有任何关系。 这些都是不相关的事情。
您只需要知道,首先对右侧进行完全评估,然后将表达式右侧的值按从左到右的顺序分配给左侧的值。 Sophros 以正确的方式回答了这个问题,我只是进一步更详细地扩展了这个想法。
想象第一种情况。 我们有:
a = [2,1,0]
a[0], a[a[0]] = a[a[0]], a[0]
当我们开始执行这段代码时,右边先计算,所以我们会有
a[0], a[a[0]] = a[a[0]], a[0] # a[a[0]] == 0, a[0] == 2, a == [2, 1, 0]
在右侧,我们有元组(0, 2)
并且a
仍然是[2, 1, 0]
接下来,我们开始从左侧分配给表达式的左侧,因此我们将元组中的第一项分配给a[0]
,即0
。 现在我们有
a[0], a[a[0]] = (0, 2) # a[0] == 0, a == [0, 1, 0]
现在我们执行赋值的最后一部分,即 to a[a[0]]
assign 2
。 但是a[0]
现在是0
,所以在减少之后我们分配给a[0]
值2
。 因此最后一次赋值之后的值是
a[0], a[a[0]] = (0, 2) # a[a[0]] == 2, a == [2, 1, 0]
看起来,什么都没有改变,值也没有交换,但从上面可以看出a
是[2,1,0]
,然后是[0,1,0]
,最后又是[2,1,0]
。 所以看起来,没有任何改变,交换不起作用。
现在是第二种情况,我们只更改表达式中变量的顺序:
a = [2,1,0]
a[a[0]], a[0] = a[0], a[a[0]]
当我们开始执行这段代码时,右边先计算,所以我们会有
a[a[0]], a[0] = a[0], a[a[0]] # a[0] == 2, a[a[0]] == 0, a == [2, 1, 0]
在右侧,我们有元组(2, 0)
并且a
仍然是[2, 1, 0]
接下来,我们从左侧开始分配给表达式的左侧,因此我们将元组中的第一项分配给a[a[0]]
,即2
。 a[0]
是2
,所以归约后,我们分配给a[2]
值2
。 现在我们有
a[a[0]], a[0] = (2, 0) # a[a[0]] == 2, a == [2, 1, 2]
现在我们执行赋值的最后一部分,即 to a[0]
assign 0
。 因此最后一次赋值之后的值是
a[a[0]], a[0] = (2, 0) # a[0] == 0, a == [0, 1, 2]
现在这按预期工作。
因此,当您的交换表达式中有因变量时,还需要考虑顺序。 作为因变量,我的意思是在第一种情况下,我们在左侧有a[0], a[a[0]]
,这意味着a[0]
改变它的值,而a[a[0]]
使用这个改变后的值,这会导致不必要的行为。
最后,无论编程语言如何,当您想要交换它们的值时,最好不要使用因变量(另一个数组索引的数组索引等)。
Python 和 C++ 是不同的语言,具有不同的规则。 这就是为什么表面上相似的结构在这些语言中表现不同的主要原因。
您不能在 Python 中编写通用swap
来处理诸如a[0], a[a[0]]
。 这不是问题。 你不应该试图用任何语言来交换这些东西,以避免混淆并提高你未来作为程序员就业的机会。
如果您绝对需要交换由同一数组的元素索引的数组元素,则可以在 Python 中这样做:
p, q, a[p], a[q] = index0, index1, a[q], a[p]
其中index0
和index1
可以是涉及a[i]
、 a[a[i]]
、 a[a[a[i]]]
或任何类似内容的任何表达式。 例如
p, q, a[p], a[q] = a[0], a[a[0]], a[q], a[p]
作品。
所以,这实际上是 python 的一个特性,称为多重赋值,如果你来自其他语言,有时很难掌握。 这就是它的工作原理。
例如。 a, b = b, a
这段代码实际上会交换元素。 我将给出一个简单直观的解释,然后是一个更具技术性的解释。
a, b = b, a
a, b = (b, a)
a, b = [b, a]
注意:python 中变量的概念与其他语言非常不同,在其他语言中,它们是某种类型的容器来存储该类型的值。 在 python 中,标签是比变量更正确的术语,因为您只是用这个名称标记一个对象,并且该对象可以是任何数据类型。 所以为了理解这段代码,当你做a,b = b,a
,你实际上并不是在交换值,而是在交换标签。
因此,python 首先在 RHS 上查找标记b
和a
指向的值,将这些值放在那里,然后它只是为这些值提供新标签。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.