如何通过就地过滤来修改python集合?

Posted

技术标签:

【中文标题】如何通过就地过滤来修改python集合?【英文标题】:How to modify python collections by filtering in-place? 【发布时间】:2011-12-23 15:50:28 【问题描述】:

我想知道,Python 中是否有办法在不创建新集合的情况下修改集合。例如:

lst = [1, 2, 3, 4, 5, 6]
new_lst = [i for i in lst if i > 3]

工作正常,但创建了一个新集合。是否有原因,Python 集合缺少 filter() 方法(或类似方法)来修改集合对象?

【问题讨论】:

如果你真的,绝对必须修改它,为什么不检查每个值,然后弹出(i)你不喜欢的那些? 关于其他方法的“缺乏”,这是因为在 Python 中“应该有一种——最好只有一种——明显的方式来做到这一点。”下面答案中的列表切片操作是进行就地修改的首选方式。它应该自然正交,因为 lst[index] 访问单个元素, lst[start:stop] 访问元素的跨度/切片。 BasicWolf 事实上你是对的:据我所知,没有方法或函数可以处理就地转换以及“为什么会这样?”的问题。已验证。可以编写进行此类转换的 sn-ps 的事实并不是一个合理的理由,否则我们可以编写自己的 sn-ps 来进行序列反转以证明不会反转就足够了( ) 作为内置函数。但是内置功能中有 reversed() ......我赞成,因为你的问题看起来像是令人兴奋的反思。 @eyquem 我认为原因很简单。由于它们不是就地过滤 Python list 的有效方法,因此没有提供这样做的方法——它会鼓励人们尝试在最好不要就地进行操作。然而,一种非常有效的方式来反向迭代它,因此提供了一个解决方案。 仍然有很多无序的集合。例如。过滤 setdict 【参考方案1】:

如果您想就地执行此操作,只需使用

lst[:] = [i for i in lst if i > 3]

这个won't be faster or save any memory,但是如果这是你需要的语义,它会改变对象。

【讨论】:

那么这段代码有什么意义呢? lst = [...] 有同样的效果,不是吗? 如果您在函数内部并且由于某种原因不想返回新列表,您需要就地修改它,以便在外部可以使用更改。 @BasicWolf:主要区别在于这并没有分配一个新的列表,所以如果该列表在客户端之间共享,它会被到处修改。 奇怪的是,您在其他引用的答案中解释了首先评估分配指令的正确成员(因此在内存中的其他位置创建了一个新对象),并且在此答案中您写道指令更改列表。在我看来,这不是纯粹的就地改变。说 id(lst) 在分配后比以前保持不变将是一个更正确的描述,但无论如何都不符合问题,唉 @eyquem 我将其描述为就地,使用 O(n) 额外存储。您所描述的“纯粹就地”,例如我的deque 示例,我会使用 O(1) 额外存储就地调用。我认为“就地”适用于两者;有时人们只需要在原地请求时改变现有对象,有时他们会尽量减少内存使用。【参考方案2】:

其他答案都是正确的;如果您希望所有指向旧列表的名称都指向新列表,您可以使用切片分配。

但是,这并不是真正的就地创建;新列表首先在别处创建。斯文的答案中的链接很好。

原因没有一个真正就地操作的原因是,当创建一个像这样的新列表是 O(n) 时,每个真正就地删除的项目将是 O(k ) 本身,其中k 是从删除点开始的列表长度。使用 Python 列表避免这种情况的唯一方法是使用一些临时存储,这就是您正在使用切片分配所做的。

collections.deque 上的就地 O(n) 过滤器示例,以防您不需要将数据存储在 list 中:

from collections import deque

def dequefilter(deck, condition):
    for _ in xrange(len(deck)):
        item = deck.popleft()
        if condition(item):
            deck.append(item)

deck = deque((1, 2, 3, 4, 5))
dequefilter(deck, lambda x: x > 2) # or operator.gt(2)
print deck
# deque([3, 4, 5])

【讨论】:

非常感谢 agf(和其他人 :) 我真的很想知道原因。 可以通过使用读取指针到列表中来实现一个需要线性时间的低级就地过滤功能。 顺便说一句,在链表的情况下 - 整个列表的过滤将花费 O(n)。我想您的意思是类向量结构的 O(k)? @BasicWolf,Python 的列表 类向量结构,因此关于 O(k) 的陈述对于 Python 列表是正确的,并且天真地删除了我们不想要的每个元素方法。 Python 中没有原生链表结构;如果需要,您显然可以对它们进行编码,但使用内置类型几乎总是更可取的。 @agf:切片分配是单字节码指令。当使用带有线程的 CPython 时,由于 GIL,它将是原子的。【参考方案3】:

纠正@larsmans original solution,你可以这样做

    i = 0
    while i < len(lst):
        if lst[i] <= 3:
            del lst[i]
        else:
            i += 1

    i = len(lst)
    while i > 0:
        if lst[i-1] <= 3:
            del lst[i-1]
        i -= 1

原因是del 发生的“索引移位”。如果我在某个索引处del,则需要重新检查该索引,因为它现在拥有不同的值。

【讨论】:

虽然这个纠正了 larsman 的解决方案,但并没有改进它——它仍然是二次时间。 嗯,这取决于定义。它并没有提高它的性能,而是它的可用性。正确的解决方案比错误的解决方案更好,恕我直言,“改进”主要是指“做得更好”。 有时候,我不懂人。在我写的这一刻,Sven 的答案,这不是该问题的确切答案,已经被投票了 7 次,而这个 glglgl 的答案在纠正另一个错误答案的同时给出了正确的答案,没有投票。此外,从 i = len(lst] 开始并递减 i 可以避免在循环的每一轮计算 len(lst),这很棘手。这个好的答案没有被投票,而是收到了一个与问题无关的批评意见(因为关于速度)。就个人而言,我显然 +1【参考方案4】:

@Sven Marnach 的 lst[:] 解决方案是一种选择。您还可以使用恒定的额外内存就地执行此操作,使用

>>> i = 0
>>> while i < len(lst):
...  if lst[i] <= 3:
...   del lst[i]
...  else:
...   i += 1
... 
>>> lst
[4, 5, 6]

...但是这个解决方案的可读性不是很好,并且由于涉及到所有元素的移动,所以需要二次时间。

【讨论】:

但是每个del lst[i] 都需要线性时间,这就是默认情况下不存在的原因。 @agf:我第二次写常数的地方,我的意思是二次的。已更新,谢谢。 当我尝试使用原始列表时,我的结果列表变为[2, 4, 5, 6]。写一个自己的答案。 我花了 10 分钟才明白,这个答案是在 glglgl 发表评论后编辑的,没有发出信号:( 谢谢提示 - 我现在指的是他的旧版本,尊重他的编辑。【参考方案5】:

因为它是not needed。

lst[:] = [i for i in lst if i > 3]

【讨论】:

【参考方案6】:

我认为是就地改造;

lst = [1,2,3,4,5,6,7,8,9,10,11]
to_exclude = [8,4,11,9]
print 'lst == %s\nto_exclude == %s' % (lst,to_exclude)

for i in xrange(len(lst)-1,-1,-1):
    if lst[i] in to_exclude:
        lst.pop(i)

print '\nlst ==',lst

结果

lst == [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
to_exclude == [8, 4, 11, 9]

lst == [1, 2, 3, 5, 6, 7, 10]

【讨论】:

以上是关于如何通过就地过滤来修改python集合?的主要内容,如果未能解决你的问题,请参考以下文章

通过递归就地修改

如何使用 Kotlin 就地过滤列表?

在Python列表中进行就地修改

如何在python中同时使用applymap、lambda和dataframe来过滤/修改dataframe?

Adam优化器错误:梯度计算所需的变量之一已通过就地操作进行了修改

如何修改 Spark 数据框中的 numpy 数组?