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

Posted

技术标签:

【中文标题】从列表中弹出随机元素的最pythonic方法是啥?【英文标题】:What is the most pythonic way to pop a random element from a list?从列表中弹出随机元素的最pythonic方法是什么? 【发布时间】:2012-04-20 08:12:40 【问题描述】:

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

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

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

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

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

【问题讨论】:

我不是 Python 达人,但这对我来说确实不错。 我已经进行了详细的时间复杂度分析,请稍后查看我的答案。随机播放效率不高!但是如果您需要以某种方式更改项目的顺序,您仍然可以使用。如果 pop(0) 与您有关,请使用我的分析中提到的 dequeue。 我写的答案的时间复杂度为 O(2)。将其包装在一个函数中以便快速使用。请注意,除了 list.pop(-1) 之外的任何 list.pop(n) 都需要 O(n)。 【参考方案1】:

你不会比这更好,但这里有一点改进:

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

random.randrange() 上的文档:

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

【讨论】:

【参考方案2】:

一种方法是:

x.remove(random.choice(x))

【讨论】:

如果元素多次出现,这可能会出现问题。 这会在有重复的时候移除最左边的元素,导致一个不完全随机的结果。 使用pop,您可以将名称指向已删除的元素,但您不能这样做。 很公平,我同意当元素多次出现时这不是很随机。 除了倾斜分布的问题,remove 需要对列表进行线性扫描。与查找索引相比,这效率非常低。【参考方案3】:

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

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

【讨论】:

所以更好(更快)的想法是使用random.shuffle(x),然后使用x.pop()?我不明白如何做到这一点“功能”? @Henrik:如果你有两个集合(例如一个字典列表和一个随机数列表)并且你想同时迭代它们,你可以zip它们来获取(dict, number) 对的列表。您谈到了多个字典,您希望将每个字典与一个随机数相关联。 zip 非常适合这个 我应该在我投反对票时添加一个帖子。有时您需要从列表中间删除一个项目...我现在必须这样做。别无选择:我有一个有序列表,我必须删除中间的一个项目。这很糟糕,但唯一的其他选择是对一个半罕见的操作进行大量的代码重构。问题是 [ ] 的实现之一,它应该对此类操作有效,但不是。 @NiklasB。 OP 以 random 为例(坦率地说,它应该被忽略,它使问题变得模糊)。 “不要那样做”是不够的。更好的答案是建议一个 Python 数据结构,它支持此类操作,同时提供足够的访问速度(显然不如 arra...er...list)。在 python 2 中,我找不到一个。如果我这样做,我会这样回答。请注意,由于浏览器故障,我无法将其添加到我的原始评论中,我应该添加辅助评论。谢谢你让我诚实:) @MarkGerolimatos 标准库中没有同时具备高效随机访问和插入/删除功能的数据结构。您可能想使用pypi.python.org/pypi/blist 之类的东西我仍然认为在很多用例中可以避免这种情况【参考方案4】:

这是另一种选择:为什么不先打乱列表,然后开始弹出其中的元素,直到没有更多元素为止?像这样:

import random

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

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

【讨论】:

@NiklasB。因为我们正在从列表中删除元素。如果不是绝对需要删除元素,是的,我同意你的看法:[for p in x] 因为它会改变列表,如果您现在只想选择一半元素,稍后再选择另一半,您将在稍后设置剩余的元素。 @Henrik:好的,这就是我问你是否需要剩余列表的原因。你没有回答。【参考方案5】:

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

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

【讨论】:

【参考方案6】:

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

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

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

【讨论】:

random.sample(items, items_needed)【参考方案7】:

这个答案来自@niklas-b:

"你可能想使用类似pypi.python.org/pypi/blist "

引用PYPI page:

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

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

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

但是,如果您的主要用例是用列表做一些奇怪和不自然的事情(如@OP 给出的强制示例,或者我的 Python 2.6 FIFO queue-with-pass-over 问题),那么这将适合账单很好。

【讨论】:

【参考方案8】:

我知道这是一个老问题,但只是为了记录:

如果您(搜索相同问题的人)正在做我认为您正在做的事情,即从列表中随机选择 k 个项目(其中 krandom.sample。但是在不了解用例的情况下,我不知道这是否是您所需要的。

【讨论】:

【参考方案9】:

尽管有很多答案建议使用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


如果列表中没有重复项,

您也可以使用集合来实现您的目的。一旦列表被设置为重复项,将被删除。 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.

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


可选

如果您希望在对列表的头部和尾部执行操作时提高速度,请使用 python dequeue(双端队列)来支持我的声明,这里是图像。一张图片就是千言万语。

【讨论】:

以上是关于从列表中弹出随机元素的最pythonic方法是啥?的主要内容,如果未能解决你的问题,请参考以下文章

从JSON格式的字符串创建python列表的最简单方法是啥[重复]

从长(且合理)稀疏向量中选择随机元素的最有效方法是啥?

使用循环从列表中弹出项目

将对象列表从 c# 传递到糟糕的 c++ win32 本机 dll 的最有效方法是啥?

在 Python 列表上进行排序加 uniq 的最简洁方法是啥?

以块为单位迭代列表的最“pythonic”方式是啥?