python有一个列表构造函数吗?

时间:2016-02-10 04:01:59

标签: python list

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

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

为了澄清,我正在寻找的是Python found 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

4 个答案:

答案 0 :(得分:11)

要回答你的问题,没有直接相当于你通常会在所谓的“功能”语言(lisp,OCaml,Haskell等)中找到的缺点。

那是因为有两种竞争模型来表示编程语言中的元素列表。

链接列表

您似乎熟悉的那个称为链表。

链表由缺陷单元组成,每个单元包含两个引用:

  • 第一个朝向列表元素并称为 head
  • 另一个朝向列表中的下一个cons-cell并且是 tail

因为列表很少是无限的,所以最后一个const单元通常指向一个特殊值,即空列表,有时称为 nil

如果您想将列表保存在变量中以供将来参考,您可以保留对第一个cons-cell的引用。

Here's a visual representation from Wikipedia

在这个模型中,每个列表都必须通过创建一个新的cons-cell向前面添加元素来构造,指向新元素作为其头部,并指向先前构建的子列表作为其尾部。这就是为什么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单元格相同。
  • 您现在可以将此单元格的尾部更改为指向新创建的单元格。

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

在这种情况下,这是明显优越的链表。

答案 1 :(得分:6)

Python列表是可变的,与OCaml相比,它们具有不可变的值语义,但如果a是一个项目而b是一个列表,则可以获得所需的结果[a] + b将返回包含a的新列表,后跟b中的项目,而不会修改ab。但是,建立列表的一种更常见的模式是appendextend就地。

答案 2 :(得分:6)

有些人建议你这样做:

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

如果b的类型为list,则该工作正常。但是,通常您会发现自己使用的是列表或元组或(在此处插入您喜欢的可迭代对象)的对象。在这种情况下,你可能更值得做:

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

这使整个事物与b的类型无关(这使得你的代码更加“迷人”,这可能有点好)。 。

当然,还有其他选择。 。 。例如,您可以选择覆盖itertools

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

我们再次受益于不关心什么类型的可迭代b我们选择了懒惰迭代的附带好处 - 我们不会制作新列表或元组。我们只是创建一个对象,当你迭代它时会产生同样的东西。这可以节省内存(有时可以节省CPU的周期),但如果b也是一种类似于生成器的对象,同时在其他地方进行迭代,也可能产生意想不到的后果(不会经常偶然发生。)

答案 3 :(得分:1)

您可以使用列表连接

head = 1
tail = [2,3,4]
lst = [head] + tail # [1,2,3,4]