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

时间:2012-04-06 19:08:56

标签: python list random pop

假设我有一个未知长度的列表x,我想从中随机弹出一个元素,以便列表之后不包含该元素。什么是最蟒蛇的方式呢?

我可以使用poprandom.randintlen的相当不合理的结合来做到这一点,并且希望看到更短或更好的解决方案:

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

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


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

9 个答案:

答案 0 :(得分:72)

你似乎要做的事情首先看起来并不像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      

一般来说,如果你使用更实用的风格,而不是改变状态(就像使用列表一样),你通常可以更优雅地表达你的程序。

答案 1 :(得分:41)

你不会比这更好,但这是一个小小的改进:

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

random.randrange()上的文档:

  

random.randrange([start],stop [,step])
  从range(start, stop, step)返回随机选择的元素。这相当于choice(range(start, stop, step)),但实际上并不构建范围对象。

答案 2 :(得分:8)

这是另一种选择:你为什么不把列表首先洗牌,然后开始弹出它的元素,直到不再有元素为止?像这样:

import random

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

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

答案 3 :(得分:8)

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

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

答案 4 :(得分:2)

一种方法是:

x.remove(random.choice(x))

答案 5 :(得分: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)

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

答案 6 :(得分:2)

尽管有许多答案表明使用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)

这里支持我的主张的是时间复杂度图表from this source enter image description here


如果列表中没有重复项,

您也可以使用集合来达到目的。一旦设为重复的列表将被删除。 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出队(双端队列)来支持我的声明,这里是图像。一个图像就是一千个单词。

enter image description here

答案 7 :(得分:1)

这个答案来自@niklas-b

您可能希望使用类似pypi.python.org/pypi/blist

的内容

引用PYPI page

  

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

     

blist是提供的Python列表的替代品   修改大型列表时性能更佳。 blist包也   提供sortedlist,sortedset,weaksortedlist,weaksortedset,   sorteddict和btuple类型。

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

但是,如果您的主要用例是使用列表做一些奇怪和不自然的事情(如@OP给出的强制示例,或者我的Python 2.6 FIFO队列 - 传递问题),那么这将适合该法案很好。

答案 8 :(得分:1)

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

如果你(搜索相同问题的人)正在做我认为你正在做的事情,那就是从列表中随机选择k个项目(其中k <= len(你的名单)),但要确保每个项目都是从未选择过多次(=没有替换的抽样),你可以像{jf-sebastian建议的那样使用random.sample。但是在不了解用例的情况下,我不知道这是否是你需要的。