一行中的头和尾

117
有没有一种Pythonic的方式可以在单个命令中解包一个列表的第一个元素和“尾部”?
例如:
>> head, tail = **some_magic applied to** [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
>> head
1
>>> tail
[1, 2, 3, 5, 8, 13, 21, 34, 55]

10
请记住,在Python中,列表不是以单向链表的形式实现的,因此这个操作是昂贵的(也就是说,整个列表需要被复制)。根据您想要实现的目标而定,这可能是一个问题或不是问题。我提到这一点只是因为这种类型的列表解构经常出现在函数式语言中,那里它实际上是一种非常便宜的操作。 - Niklas B.
5个回答

242

在 Python 3.x 版本中,你可以优雅地实现这个:

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

在3.x中的一个新特性是可以使用*操作符进行解包,在语义上表示任意多余的值。具体描述可参考 PEP 3132 - 扩展迭代解包。这种方法还有一个优点,即不仅适用于序列,而且适用于任何可迭代对象。

此外,这种方法真的非常容易理解。

根据PEP的说明,在2.x中要实现等效的功能(而又不会产生临时列表),需要执行以下操作:

it = iter(iterable)
head, tail = next(it), list(it)

如评论所述,这也提供了一个机会来获得head的默认值而不是抛出异常。如果您想要这种行为,则next()会带有一个可选的第二个参数,默认值为None,因此next(it, None)将在没有头元素的情况下返回None

当然,如果您正在处理一个列表,则最简单的方法是使用不带3.x语法的以下方式:

head, tail = seq[0], seq[1:]

1
抱歉,我不正确地使用了“tail”这个术语。我的意思是在示例中所说的,即列表中去掉第一个元素。 - Giacomo d'Antonio
1
@NikolayFominyh 他们两个是一样的 - 它们都取头元素并构造一个包含尾元素的新列表。复杂度没有区别。另一个类可以实现__getitem__/__setitem__来懒惰地执行尾操作,但内置列表不会这样做。 - Gareth Latty
2
在一个包含800元素的列表上,执行100万次操作,用 head, *tail = seq 方案需要2.8秒,而用 head, tail = seq[0], seq[1:] 方案只需要1.8秒。对于列表,切片仍然更快。 - Cabu
2
@CMCDragonkai 不,Python的主要列表类是一个数组列表。这将是O(n),因为它涉及将尾部复制到新列表(其中一个O(1)获取头部)。 - Gareth Latty
1
这种简洁的语法是迁移到 Python 3.x 的另一个原因。 - daparic
显示剩余6条评论

40
>>> mylist = [1, 1, 2, 3, 5, 8, 13, 21, 34, 55]
>>> head, tail = mylist[0], mylist[1:]
>>> head
1
>>> tail
[1, 2, 3, 5, 8, 13, 21, 34, 55]

13
deque可以实现O(1)时间复杂度的head,tail操作。请使用以下方法:
from collections import deque
l = deque([1,2,3,4,5,6,7,8,9])
head, tail = l.popleft(), l

当您必须迭代列表的所有元素时,它会非常有用。例如,在归并排序中天真地合并2个分区时。


1
似乎deque(list_instance)的时间复杂度为O(N),我错了吗? - Никита Конин
1
@НикитаКонин,你对deque的构建是正确的。然而,如果你想要多次访问第一个元素,那么head, tail = l.popleft(), l的时间复杂度是O(1)。而head, tail = seq[0], seq[1:]的时间复杂度是O(n)。 - Nikolay Fominyh
看起来你只需要执行 head = l.popleft(),而 tail 只是 l 的别名。如果 l 改变了,tail 也会改变。 - kon psych

3

使用lambda的Python 2

>>> head, tail = (lambda lst: (lst[0], lst[1:]))([1, 1, 2, 3, 5, 8, 13, 21, 34, 55])
>>> head
1
>>> tail
[1, 2, 3, 5, 8, 13, 21, 34, 55]

2
为什么不直接使用 head, tail = lst[0], lst[1:],而要这样做呢?如果 OP 想要使用字面量,那么他可以手动拆分头和尾部,例如 head, tail = 1, [1, 2, 3, 5, 8, 13, 21, 34, 55] - Filipe Pina
3
(1)Op的问题是是否可能在一行中完成此操作(因此在前一行中没有lst = ...)。 (2)执行head,tail = lst [0],lst [1:]会导致代码受到副作用的影响(考虑head,tail = get_list()[0],get_list()[1:]),并且与Op的形式不同head,tail = **应用于** [1,1,2,3,5,8,13,21,34,55]的某些魔法。 - BobIsNotMyName
话虽如此,我承认这是一种糟糕的混淆方式来获取头/尾。但我认为这是针对Op特定问题的Python 2的最佳答案。 - BobIsNotMyName

2

GarethLatty的Python 2解决方案的基础上,以下是在Python 2中获得单行等效的方法,而不需要使用中间变量。

t=iter([1, 1, 2, 3, 5, 8, 13, 21, 34, 55]);h,t = [(h,list(t)) for h in t][0]

如果你需要让它具备异常防护能力(即支持空列表),那么请添加:
t=iter([]);h,t = ([(h,list(t)) for h in t]+[(None,[])])[0]

如果您想不使用分号来完成此操作,请使用以下方法:
h,t = ([(h,list(t)) for t in [iter([1,2,3,4])] for h in t]+[(None,[])])[0]

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接