python a,b = b,一个实现?它与C ++交换功能有何不同?

时间:2018-08-21 13:46:36

标签: python algorithm

当我想尝试以下版本的python版本时遇到了这个问题: https://leetcode.com/problems/first-missing-positive/discuss/17071/My-short-c++-solution-O(1)-space-and-O(n)-time

我不确定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的实现,语法类似于:

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方式编写这种交换函数?

5 个答案:

答案 0 :(得分:7)

这种行为确实与Python评估类型表达式的方式有关

a,b=b,a

实际上,Python要做的是首先通过创建元组(b,a)“准备”右侧的值。然后,将该元组解压缩并以相反的顺序分配给变量。

请注意,临时元组是使用变量的 values (值的实际副本)创建的,而不是对变量的引用(您可以在此处阅读)大约the difference between passing by value and by reference)。

要使用您使用的引用类型(列表)细分示例,请执行以下操作:

a = [2,1,0]    
a[0], a[a[0]] = a[a[0]], a[0]
  1. a[a[0]]从列表a[0]的{​​{1}}元素(等于2)中获取值(值a)。
  2. 0a[0],因此创建的元组是2
  3. 打开元组(0,2)的包装,并用(0,2)替换列表中的0(第0个元素)。
  4. 现在,2可以理解为:取列表a[a[0]]的第0个元素(当前为a),然后将该位置的列表中的值替换为{{1 }}从元组解包中(现在02取代了-这使该操作看起来对列表没有任何作用)。

根据the answer from von Oak中的建议,更改顺序会有所帮助,因为从上述第4点开始的步骤不会再次替换该值。

答案 1 :(得分:5)

要了解这一点,您需要使用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

您可以看到前两项相同的输出字节码。因此,它不会交换

答案 2 :(得分:1)

Python和C ++是具有不同规则的不同语言。这很大程度上是为什么表面相似的结构在这些语言中表现不同的原因。

您无法在Python中编写与swap之类的输入兼容的通用a[0], a[a[0]]。这不是问题。您不应该尝试以任何语言交换此类内容,以免造成混乱并增加将来成为程序员的机会。

如果您绝对需要交换由同一数组的元素索引的数组元素,则可以在Python中这样做:

p, q, a[p], a[q] = index0, index1, a[q], a[p]

其中index0index1可以是涉及a[i]a[a[i]]a[a[a[i]]]或任何类似内容的任何表达式。例如

p, q,  a[p], a[q] = a[0], a[a[0]],  a[q], a[p]

有效。

答案 3 :(得分:0)

仅在纸上(例如在工作面试中)就很容易想到它,而无需调试或将代码反汇编为字节码即可理解。

我还认为这与C ++中swap函数的实现没有任何关系。这些是无关的东西。

您唯一需要知道的是首先对右侧进行完全求值,然后将表达式右侧的值按从左到右的顺序分配给左侧的值。 Sophros answered it right way我只会更详细地扩展这个想法。

想象第一种情况。我们有:

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]

现在我们执行分配的最后一部分,即为a[a[0]]分配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]]分配来自元组的第一项,即2a[0]2,因此在归约后,我们将值a[2]分配给2。现在我们有

a[a[0]], a[0] = (2, 0)   # a[a[0]] == 2, a == [2, 1, 2]

现在我们执行分配的最后一部分,即为a[0]分配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]]使用此更改的值,这会导致不良行为。

最后,无论使用哪种编程语言,最好在要交换它们的值时都不要使用因变量(另一个数组索引的数组索引等)。

答案 4 :(得分:0)

所以,这实际上是 python 的一个特性,称为多重赋值,如果您来自其他语言,有时很难掌握。这就是它的工作原理。

例如。 from git import Repo print('cloning ....') repo = Repo.clone_from( 'git@gitlab.com:gitlab-org/gitlab-docs.git', './Projects/', filter=['tree:0','blob:none'], sparse=True ) print(repo)

这段代码实际上会交换元素。我先给出一个简单直观的解释,然后是更技术性的解释。

  1. 首先评估 RHS,然后将值分别分配给 LHS 上的变量(标签)。
  2. 所以你实际上可以在 python 中定义一个元组 (a,b) 为 a,b,括号只是为了更好的可读性。所以你的 RHS 是一个解包的元组,每个元素分别分配给 LHS 上的每个标签。因此,以下所有代码片段都是等效的。
a, b = b, a

注意:python 中变量的概念与其他语言非常不同,其他语言是某种类型的容器来存储该类型的值。在 python 中,labels 是一个比 variables 更正确的术语,因为你只是用这个名字标记一个对象,而这个对象可以是任何数据类型。因此,要理解此代码,您实际上并不是在执行 a, b = b, a a, b = (b, a) a, b = [b, a] 交换值,而是交换标签
因此,python 首先在 RHS 上查找标签 a,b = b,ab 指向的值,将这些值放在那里,然后只是为这些值提供新标签。