当前位置 : 主页 > 编程语言 > python >

Python 的二维数组,你写对了吗?

来源:互联网 收集:自由互联 发布时间:2022-06-18
这是「​刷题躲坑​」系列的第一篇文章,这个系列帮助大家躲掉刷题/代码中常见的坑。 怪事 在 Python 中,定义长度为 ​​3​​​,数值全为 ​​0​​ 的一维​列表​(也就是​数


这是「​刷题躲坑​」系列的第一篇文章,这个系列帮助大家躲掉刷题/代码中常见的坑。

怪事

在 Python 中,定义长度为 ​​3​​​,数值全为 ​​0​​ 的一维​列表​(也就是​数组​)的方式有:

a = [0, 0, 0]
a = [0 for _ in range(3)]
a = [0] * 3

在刷算法题是,我们常常遇到的一个场景,就是需要定义一个和输入等长的一维列表。所以,我基本都是用上述的第 3 种方式。

如:

a = [0] * len(nums)

假设 ​​nums​​​ 的长度是 5,那么运行完上述代码以后,​​a​​​ 的值为 ​​[0, 0, 0, 0, 0]​​。

我们修改一下 ​​a​​ 列表:

In [1]: a[0] = 666

In [2]: a
Out[2]: [666, 0, 0, 0, 0]

可以看到,修改 ​​a​​​ 列表的第 ​​0​​ 个元素​并不会​影响到其他位置的元素。

这是理所当然的事情。

可是,​当数组变成二维的时候,怪事就发生了​。

按照上述定义一维数组的思路,定义一个 ​​3​​​ 行 ​​2​​​ 列的二维数组 ​​b​​:

In [1]: b = [[0] * 2] * 3

In [2]: b
Out[2]: [[0, 0], [0, 0], [0, 0]]

看起来 ​​b​​ 是符合要求的,没问题吧。

但是,当我们修改 ​​b​​ 中的元素时:

In [1]: b[0][0] = 2333

In [2]: b
Out[2]: [[2333, 0], [2333, 0], [2333, 0]]

看到了吗?​我们只修改了 ​​b[0][0]​​,但是 ​​b​​ 列表中的第 ​​0​​ 列的所有元素,全部都被修改了!!​

是不是很奇怪??

分析

遇到这种奇怪的问题时,可以用我之前分享的【代码执行可视化工具】进行分析。

这个工具的地址是:​​https://pythontutor.com/​​

先看 Python 一维列表,在内存中的可视化运行情况。

Python 的二维数组,你写对了吗?_力扣

从上面的动图可以看到,修改一维数组中的某个元素,并不会影响到其他元素。

再看按照之前方式定义的 Python 二维列表,在内存中的运行情况。

​我们本以为的正确的二维列表应该是下面这样​。即数组有 ​​3​​ 行,每行都是一个​独立的​长度为 ​​2​​ 的列表。

Python 的二维数组,你写对了吗?_Python_02

​可实际上呢?​

当我们定义 ​​b = [[0] * 2] * 3​​​ 时,虽然看起来结果是一个 ​​3 x 2​​ 的二维列表,但是在内存中,数组中​每行都是指向同一个长度为 ​​2​​ 的列表​。

Python 的二维数组,你写对了吗?_力扣_03

因此,当我们修改 ​​b[0][0]​​​ 时,虽然只是修改了内存中的一个元素,但是由于 ​​3​​​ 行指向的是同一个地址,因此看起来是把 ​​3​​ 行中的元素都修改了。

Python 的二维数组,你写对了吗?_力扣_04

​这就是怪事发生的原因。​

我们在 Python 终端中看一下 ​​b​​​ 的 ​​3​​​ 行的内存地址(可以使用​​id()​​函数获取内存地址),进行验证。

In [1]: b = [[0] * 2] * 3
In [2]: for i in range(3):
...: print(id(b[i]))
...:
140178338622720
140178338622720
140178338622720

看到 ​​b​​​列表的 ​​3​​ 行内存地址确实一样的,和我们上面的可视化结果一致。

正确写法

上面是 Python 中常见的坑,负雪在刷题的时候,也被坑过。。

我们可以这么理解:

  • ​​[0] * 2​​ 返回的是一个内存中的地址。
  • 定义​​[[0] * 2] * 3​​​时 ,​​[0] * 2​​​ 只运行了一次,返回了一个地址​​x​​​;二维列表中存放了​​3​​​ 个​​x​​​,即​​[x, x, x]​​。

那 Python 二维数组的正确写法是什么呢?

​第一种写法​,你可以把所有的元素都显式的写出来,这样肯定没问题:

a = [[0, 0], [0, 0], [0, 0]]

​第二种写法​,我们使用 ​​for​​ 来定义第二个维度。是推荐的写法。

In [1]: a = [[0] * 2 for _ in range(3)]

In [2]: a
Out[2]: [[0, 0], [0, 0], [0, 0]]

In [3]: a[0][0] = 666

In [4]: a
Out[4]: [[666, 0], [0, 0], [0, 0]]

为什么使用 ​​for​​ 的写法可以呢?

因为这种情况下 ​​[0] * 2​​​ 运行了 ​​3​​​ 次,所以返回的是 ​​3​​​ 个不同的地址​​x,y,z​​​。二维列表中的存放的是 ​​[x, y, z]​​。

可视化结果如下:

Python 的二维数组,你写对了吗?_Python_05

可以看到 ​​3​​ 行指向不同的地址,这样的结果就是符合预期的了。

总结

今天分享了 Python 刷题中常见的一个坑:​二维列表的定义​。

我们通过代码运行可视化工具,分析了为什么使用 ​​*​​ 定义二维数组会出问题。

最后也给出了使用 ​​for​​ 来定义二维列表的正确写法。

了解内存知识,在编程中非常有用。

推荐新手在遇到这种问题时,使用可视化工具进行排查,非常有助于理解。

以上就是「​刷题躲坑​」系列的第一篇文章啦!

欢迎关注我的公众号「​负雪明烛​」,在编程学习路上,我们一起踩坑、躲坑。




网友评论