What is the most pythonic way to pop a random element from a list?

Say I have a list x with unkown length from which I want to randomly pop one element so that the list does not contain the element afterwards. What is the most pythonic way to do this?

I can do it using a rather unhandy combincation of pop, random.randint, and len, and would like to see shorter or nicer solutions:

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

What I am trying to achieve is consecutively pop random elements from a list. (i.e., randomly pop one element and move it to a dictionary, randomly pop another element and move it to another dictionary, ...)

Note that I am using Python 2.6 and did not find any solutions via the search function.

108529 次浏览

你不会得到比这更好的结果,但这里有一个小小的改进:

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

关于 random.randrange()的文件:

Randrange ([ start ] ,stop [ ,step ])
range(start, stop, step)返回一个随机选择的元素。这等效于 choice(range(start, stop, step)),但是并不实际构建一个范围对象。

一种方法是:

x.remove(random.choice(x))

你看起来正在做的事一开始就不像是 Python 风格的。您不应该从列表的中间删除内容,因为在我所知道的所有 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

一般来说,如果使用功能性更强的样式,而不是变异状态(就像对列表所做的那样) ,通常可以更优雅地表达程序。

这里还有另一种选择: 为什么不对列表 first进行洗牌,然后开始弹出它的元素,直到没有更多的元素保留?像这样:

import random


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


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

如果其余列表元素的顺序不重要,则从列表中随机索引删除一个 单身元素:

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)行为。

虽然没有从列表中弹出,但我在谷歌上遇到了这个问题,当时我正试图从一个没有重复的列表中获取 X 个随机项目。以下是我最终使用的方法:

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

这可能会有点低效,因为您正在重新组合整个列表,但只使用其中的一小部分,但我不是优化专家,所以我可能是错误的。

这个答案来自 @ Niklas-b:

"您可能想使用类似于 < a href = “ http://pypi.python.org/pypi/blist”rel = “ nofollow norefrer”> pypi.python.org/pypi/blist "

引用 PYPI 页面的话:

... 一种类似列表的类型,具有更好的渐近性能和相似性 在小名单上的表现

Blist 是 Python 列表的一个插入式替换,该列表提供 更好的性能时,修改大型列表。 blist 包也 提供 sortedlist,sortedset, 以及 btuple 类型。

我们可以假设随机访问/随机运行结束 的性能降低,因为它是一个“写入时复制”的数据结构。这违反了 Python 列表 所以小心使用它中的许多用例假设。

HOWEVER, if your main use case is to do something weird and unnatural with a list (as in the forced example given by @OP, or my Python 2.6 FIFO queue-with-pass-over issue), then this will fit the bill nicely.

我知道这是一个老问题,但仅仅是为了文件的缘故:

如果你(搜索同一个问题的人)正在做我认为你正在做的事情,那就是从一个列表中随机选择 k 个项目(其中 k < = len (yourlist)) ,但是确保每个项目不会被选择一次以上(= 无替换抽样) ,你可以像@j-f-sebastian 建议的那样使用 随机抽样。但是如果不了解更多关于用例的信息,我不知道这是否是您所需要的。

尽管许多答案建议使用 random.shuffle(x)x.pop()它在大数据非常缓慢。在启用 shuffle 时,10000元素列表所需的时间大约为 6 seconds。当洗牌被禁用时,速度是 0.2s

在测试了以上所有给定的方法之后,最快的方法是@jfs 编写的

import random


L = [1,"2",[3],(4),{5:"6"},'etc'] #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)

这里的时间复杂度图表支持我的观点 enter image description here


如果列表中没有重复,

you can achieve your purpose using sets too. once list made into set duplicates will be removed. remove by value and remove random cost O(1), ie very effecient. this is the cleanest method i could come up with.

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.

与支持 A+B选项的 lists不同,sets还支持 A-B (A minus B)以及 A+B (A union B)A.intersection(B,C,D)。当您想要对数据执行逻辑操作时非常有用。


可以选择

如果你想在列表的头部和尾部执行操作时提高速度,使用 python dequeue (双端队列)来支持我的说法,这里是图片。一个形象就是千言万语。

enter image description here