python有列表构造函数吗?

2024-05-12 23:43:24 发布

您现在位置:Python中文网/ 问答频道 /正文

python是否有像OCaml(::)中的cons(或lisp)那样的列表构造函数,它接受head元素和tail列表,并返回一个新的列表head::tail

我搜索了python列表构造函数,最后找到了关于__init__的其他内容。参见例如Creating a list in Python- something sneaky going on?

为了澄清,我要寻找的是Pythonfound in this question中以下列表分解的逆解:

head, *tail = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]

这就提供了:

>>>head
1
>>> tail
[1, 2, 3, 5, 8, 13, 21, 34, 55]

我正在寻找一个列表构造函数,例如cons::,以便

head :: tail  =>   original list

Tags: increating元素内容列表initheadsomething
3条回答

有人建议你这样做:

a = 1
b = [2, 3, 4, 5]
c = [a] + b

只要blist类型,它就可以工作。但是,通常您会发现自己使用的对象是列表或元组,或者(在此处插入您最喜欢的iterable对象)。在这种情况下,可能更值得你花点时间:

a = 1
b = (2, 3, 4, 5)
c = [a]
c.extend(b)

这使得整个事情对于b的类型是不可知的(这允许您的代码更加“ducky”,这可能是一种不错的方式)。


当然,还有其他的选择。例如,您可以选择访问itertools

import itertools
a = 1
b = [2, 3, 4, 5]
lazy_c = itertools.chain([a], b)

同样,我们有一个好处,就是不关心什么类型的iterableb我们有一个附带的好处,那就是惰性地迭代——我们不会生成一个新的列表或元组。我们只需要创建一个对象,当您迭代它时,它会产生相同的结果。这可以节省内存(有时还可以为您的CPU节省周期),但如果b也是一种类似生成器的对象,同时在其他地方迭代(这不是偶然发生的),也可能会产生不预期的后果。

Python列表是可变的,而OCaml具有不可变的值语义,但是如果a是一个项,b是一个列表,则可以使用[a] + b获得所需的结果,该列表将返回包含ab中的项的新列表,并且不修改ab。不过,构建列表的一个更常见的模式是appendextend将其放置到位。

要回答您的问题,没有直接的等价物,您通常会发现在所谓的“函数式”语言(lisp、OCaml、Haskell等)中的缺点

这是因为在编程语言中有两个相互竞争的模型来表示元素列表。

链接列表

你似乎很熟悉的那个叫做链表。

链接列表由cons单元格组成,每个单元格包含两个引用:

  • 第一个指向列表元素,称为head
  • 另一个指向列表中的下一个cons单元格,是尾部

由于列表很少是无限的,最后一个常量单元格通常指向一个特殊的值,即空列表,有时称为nil

如果您想将列表保存在变量中以备将来参考,您可以保留对第一个cons单元格的参考。

Here's a visual representation from Wikipedia

在这个模型中,每个列表都必须通过创建一个新的cons单元来将元素添加到前面来构造,该单元指向新元素作为其头部,指向先前构造的子列表作为其尾部。这就是cons运算符有时被称为列表构造函数的原因。

数组

这是Python等命令式语言通常首选的模型。在这个模型中,列表只是对内存中某个范围的引用。

假设您创建了一个这样的列表:

l = [1, 2, 3]

每当您创建一个列表时,Python都会给它分配一个小范围的内存来存储元素,并为您以后添加元素提供一点额外的空间。要存储它,只需存储对第一个元素和内存范围大小的引用,如下所示:

l  <-- your variable
|     ___ ___ ___ ___ ___ ___ ___ ___ ___
|->  |   |   |   |   |   |   |   |   |   |
     | 1 | 2 | 3 |   |   |   |   |   |   |
     |___|___|___|___|___|___|___|___|___|

如果决定在列表末尾添加元素,可以使用append

l.append(4)

结果如下:

 ___ ___ ___ ___ ___ ___ ___ ___ ___
|   |   |   |   |   |   |   |   |   |
| 1 | 2 | 3 | 4 |   |   |   |   |   |
|___|___|___|___|___|___|___|___|___|

现在,假设您忘记了首字母0,现在希望将其添加到前面。您可以很好地使用insert方法(插入位置为0):

l.insert(0, 0)

但是列表的开头没有空格!Python别无选择,只能获取每个元素,并在直接右边的位置一次复制它们:

 ___ ___ ___ ___ ___ ___ ___ ___ ___
|   |   |   |   |   |   |   |   |   |
| 1 | 2 | 3 | 4 |   |   |   |   |   |
|___|___|___|___|___|___|___|___|___|
  |   |   |__ |___
  |   |___   |    |  First, Python has to copy the four elements
  |___    |  |    |  one space to the right 
 ___ _\/ _\/ \/_ _\/ ___ ___ ___ ___
|   |   |   |   |   |   |   |   |   |
|   | 1 | 2 | 3 | 4 |   |   |   |   |
|___|___|___|___|___|___|___|___|___|

Only then can it insert the 0 at the beginning

 ___ ___ ___ ___ ___ ___ ___ ___ ___
|   |   |   |   |   |   |   |   |   |
| 0 | 1 | 2 | 3 |   |   |   |   |   |
|___|___|___|___|___|___|___|___|___|

对于这样一个小的数组来说,这似乎不算什么,但是想象一下,您的数组要大得多,并且您多次重复此操作:您将花费大量时间构建列表!

这就是为什么在使用数组(如Python)的语言中找不到列表构造函数。

进一步跳水:为什么两种不同的模式?

您现在可能想知道,为什么不同的语言更喜欢不同的列表模型,以及这两种模型中是否有一种更优秀。

这是因为这两种数据结构在不同的上下文中有不同的性能。两个例子:

访问中间的元素

假设您想要得到列表的第五个元素。

在链接列表中,您需要获得:

  • 第一个囚室
  • 然后这个细胞的尾部得到第二个元素
  • 然后这个细胞的尾部得到第三个元素
  • 然后这个细胞的尾部得到第四个元素
  • 最后这个细胞的尾部得到第五个元素

因此,你必须通过5个参考!

对于数组,这要简单得多:您知道第一个元素的引用。您只需要访问右边的引用4个点,因为元素都在一个连续的内存范围内!

如果需要多次访问非常大的列表的随机元素,那么数组就更好了。

在中间插入元素

假设你现在想在中间插入一个元素。

使用链接列表:

  • 你找到了cons小区转到插入点之前的最后一个元素。
  • 创建一个新的cons单元格,头部指向要添加的元素,尾部与刚刚定位的cons单元格相同。
  • 现在可以将此单元格的尾部更改为指向新创建的单元格。

对于数组,就像在中间添加一个元素一样,您需要将每个元素复制到插入点右侧,从右侧复制一个空格!

在这种情况下,链接列表显然更优越。

相关问题 更多 >