从列表中弹出随机元素的最Python方式是什么?


88

假设我有一个x长度未知的列表,我想从该列表中随机弹出一个元素,以便此后列表中不包含该元素。什么是最pythonic的方式做到这一点?

我可以用一个相当不方便combincation做到这一点poprandom.randintlen,并希望看到更短的或更好的解决方案:

import random
x = [1,2,3,4,5,6]
x.pop(random.randint(0,len(x)-1))

我想要实现的是从列表中连续弹出随机元素。(即,随机弹出一个元素并将其移至字典,随机弹出另一个元素并将其移至另一字典,...)

请注意,我使用的是Python 2.6,但没有通过搜索功能找到任何解决方案。


3
我不是一个Pythonista使用者,但对我来说,这看起来确实不错。
马特·鲍尔

我已经进行了详细的时间复杂度分析,请在以后的地方查看我的答案。SHUFFLE不高效!但是如果您需要以某种方式更改项目顺序,仍然可以使用。如果pop(0)与您有关,请使用我的分析中提到的出队。
nikhil swami

书面答案的时间复杂度为O(2)。将其包装在一个函数中以便快速使用。请注意,除list.pop(-1)之外的任何list.pop(n)都需要O(n)。
nikhil swami

Answers:


94

首先,您似乎想要做的看起来不太像Pythonic。您不应该从列表的中间删除内容,因为列表在我所知道的所有Python实现中都是作为数组实现的,因此这是一项O(n)操作。

如果您确实需要将此功能作为算法的一部分,则应检出blist支持从中间高效删除的数据结构,例如。

在纯Python中,如果不需要访问其余元素,该怎么办就是先将列表随机播放,然后对其进行遍历:

lst = [1,2,3]
random.shuffle(lst)
for x in lst:
  # ...

如果您确实需要其余部分(有点代码味道,恕我直言),至少您现在可以pop()从列表末尾开始(快!):

while lst:
  x = lst.pop()
  # do something with the element      

通常,如果您使用更具功能性的样式而不是改变状态(例如,使用列表),则通常可以更优雅地表达程序。


3
因此,最好使用(更快)的方法random.shuffle(x),然后使用x.pop()?我不明白该怎么做“功能”?
Henrik 2012年

1
@Henrik:如果您有两个集合(例如,字典列表和随机数列表),并且想要同时对其进行迭代,则可以zip使它们获得(字典,数字)对的列表。您说了一些有关多个词典的信息,您希望将每个词典与一个随机数关联。zip非常适合此操作
Niklas B.

2
我应该在不赞成投票时增加一个职位。有时您需要从列表中间删除一个项目...我现在必须这样做。别无选择:我有一个有序列表,我必须在中间删除一个项目。这很糟糕,但是唯一的选择是对一个半罕见操作进行繁重的代码重构。问题是[]的实现之一,对于这样的操作应该有效,但不是。
Mark Gerolimatos '16

5
@NiklasB。OP以随机为例(坦率地说,应该将其保留下来,使问题蒙上了阴影)。“不要那样做”是不够的。更好的答案是建议一个Python数据结构,它在提供足够的访问速度的同时确实支持此类操作(显然不如arra ... er ... list那样好)。在python 2中,我找不到一个。如果我愿意,我会回答。请注意,由于浏览器的故障,我无法将其添加到我的原始评论中,我应该添加一个辅助评论。感谢您让我诚实:)
Mark Gerolimatos

1
@MarkGerolimatos标准库中没有有效的随机访问和插入/删除操作的数据结构。您可能想要使用pypi.python.org/pypi/blist之类的东西,但我仍然会认为在很多使用情况下都可以避免这种情况
Niklas B.

49

您不会得到比这更好的东西,但是这里有一点改进:

x.pop(random.randrange(len(x)))

的文档random.randrange()

random.randrange([start],stop [,step])
从中返回随机选择的元素range(start, stop, step)。这等效于choice(range(start, stop, step)),但实际上并没有建立范围对象。


14

如果其余列表元素的顺序无关紧要,请从列表中的随机索引处删除单个元素:

import random

L = [1,2,3,4,5,6]
i = random.randrange(len(L)) # get random index
L[i], L[-1] = L[-1], L[i]    # swap with the last element
x = L.pop()                  # pop last element O(1)

交换用于避免O(n)从列表中间删除时的行为。


9

这是另一种选择:为什么不随机排列列表,然后开始弹出列表的元素,直到没有更多的元素了?像这样:

import random

x = [1,2,3,4,5,6]
random.shuffle(x)

while x:
    p = x.pop()
    # do your stuff with p

3
@NiklasB。因为我们要从列表中删除元素。如果它不是绝对必要删除元素,是的,我同意你的看法:[for p in x]
奥斯卡·洛佩斯

因为它会更改列表,并且如果您现在只想选择一半的元素,然后再选择另一半的元素,那么稍后将剩下其余的元素。
亨里克(Henrik)2012年

@Henrik:好的,这就是为什么我问你是否需要剩余的清单。你没有回答。
Niklas B.

2

一种方法是:

x.remove(random.choice(x))

7
如果元素多次出现,则可能会出现问题。
Niklas B.

2
当有重复项时,这将删除最左边的元素,从而导致结果不完全随机。
FogleBird 2012年

使用,pop您可以将名称指向已删除的元素,而使用则不能。
2012年

公平地说,我同意当元素出现不止一次时这不是很随机。
Simeon Visser'4

1
除了歪曲您的分布的问题之外,还remove需要对列表进行线性扫描。与查找索引相比,效率极低。
aaronasterling'4

2

虽然没有从列表中弹出,但我在Google上尝试从列表中获取X个随机项而没有重复项时遇到了这个问题。这是我最终使用的:

items = [1, 2, 3, 4, 5]
items_needed = 2
from random import shuffle
shuffle(items)
for item in items[:items_needed]:
    print(item)

这可能会有点效率低下,因为您要整理整个列表,但只使用其中的一小部分,但是我不是优化专家,所以我可能是错的。


3
random.sample(items, items_needed)
jfs

2

我知道这是一个老问题,但仅出于文档目的:

如果您(正在搜索同一问题的人)正在做我认为您正在做的事情,即从列表(其中k <= len(您的列表))中随机选择k个项目,但要确保不再选择每个项目一次以上(=采样而不替换),您可以使用random.sample,如@ jf-sebastian建议。但是在不了解用例的情况下,我不知道这是否是您所需要的。


1

这个答案来自@ niklas-b

您可能想使用pypi.python.org/pypi/blist之类的东西

引用PYPI页面

...具有更好渐近性能且在小列表上具有相似性能的类似列表的类型

blist是Python列表的直接替代,可在修改大列表时提供更好的性能。blist包还提供了sortedlist,sortedset,weaksortedlist,weaksortedset,sorteddict和btuple类型。

人们可能会认为随机访问/随机运行端的性能会降低,因为它是“写时复制”数据结构。这违反了Python列表上的许多用例假设,因此请谨慎使用

但是,如果您的主要用例是对列表做一些奇怪且不自然的事情(例如@OP给出的强制示例,或者我的Python 2.6 FIFO传递不通过问题),那么这将非常适合。


1

尽管有很多答案表明使用它,random.shuffle(x)并且x.pop()在大数据上使用它非常慢。启用随机播放后,10000元素列表所需的时间和所需的时间6 seconds。禁用随机播放时,速度为0.2s

测试上述所有给定方法后最快的方法竟然是由@jfs编写的

import random

L = ['1',2,3,'4'...1000] #you can take mixed or pure list
i = random.randrange(len(L)) # get random index
L[i], L[-1] = L[-1], L[i]    # swap with the last element
x = L.pop()                  # pop last element O(1)

为了支持我的主张,这里是此来源的时间复杂度图表 在此处输入图片说明


如果列表中没有重复项,

您也可以使用集合来达到目的。一旦列表成为重复项,将被删除。remove by valueremove random成本O(1),即非常有效。这是我能想到的最干净的方法。

L=set([1,2,3,4,5,6...]) #directly input the list to inbuilt function set()
while 1:
    r=L.pop()
    #do something with r , r is random element of initial list L.

lists哪个支持A+B选项不同,sets它还A-B (A minus B)A+B (A union B)和一起支持A.intersection(B,C,D)。当您要对数据执行逻辑操作时,超级有用。


可选的

如果要在列表的头尾执行操作时要提高速度,请使用python出队(双端队列)来支持我的声明,这里是图像。一个图像就是一千个单词。

在此处输入图片说明

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.