【问题标题】:Python: Remove duplicates for a specific item from listPython:从列表中删除特定项目的重复项
【发布时间】:2018-04-07 12:29:35
【问题描述】:

我有一个项目列表,我想在其中删除一个项目的任何重复项,但保留其余项目的任何重复项。 IE。我从以下列表开始

mylist = [4, 1, 2, 6, 1, 0, 9, 8, 0, 9]

我想删除0 的所有重复项,但保留19 的重复项。 我目前的解决方案如下:

mylist = [i for i in mylist if i != 0]
mylist.add(0)

除了以下内容之外,还有什么好方法可以让0 出现一次吗?

for i in mylist:
    if mylist.count(0) > 1:
        mylist.remove(0)

对于本示例,第二种方法花费的时间是该示例的两倍多。

澄清:

  • 目前,我不关心列表中项目的顺序,因为我目前在创建和清理后对其进行排序,但以后可能会改变。

  • 目前,我只需要删除一个特定项目的重复项(在我的示例中为 0

【问题讨论】:

  • 此列表中的顺序重要吗?
  • 您的第一个解决方案有什么问题? [0] + [i for i in mylist if i != 0]
  • 我认为您很可能过于关注微小的性能差异,应该只使用您当前的解决方案之一。
  • 另外,您需要专门删除重复的零,还是需要针对任意其他值的解决方案?
  • @DanielPryden 的意思是可能会编写一个不同的函数,它需要一个排序列表并且比任何其他解决方案都快,特别是如果要删除的项目是列表中可能的最小项目(其中 0 可能)

标签: python python-3.x list


【解决方案1】:

解决办法:

[0] + [i for i in mylist if i]

看起来足够好,除非0 不在mylist 中,在这种情况下你错误地添加了 0。

此外,像这样添加 2 个列表在性能方面并不是很好。我会这样做:

newlist = [i for i in mylist if i]
if len(newlist) != len(mylist):  # 0 was removed, add it back
   newlist.append(0)

(或使用过滤器newlist = list(filter(None,mylist)) 可能会稍微快一些,因为没有原生 python 循环)

在最后一个位置追加到一个列表是非常有效的(list 对象使用预分配并且大部分时间没有内存被复制)。长度测试技巧是O(1) 并允许避免测试0 in mylist

【讨论】:

  • if 0 in mylist 检查也是 O(N) 所以我看不出这会比 OP 的第二个例子更好,后者已经被声明太慢了。
  • @DanielPryden 你是对的。测试长度更好。已编辑。
【解决方案2】:

这是一种基于生成器的方法,复杂度约为 O(n),同时也保留了原始列表的顺序:

In [62]: def remove_dup(lst, item):
    ...:     temp = [item]
    ...:     for i in lst:
    ...:         if i != item:
    ...:             yield i
    ...:         elif i == item and temp:
    ...:             yield temp.pop()
    ...:             

In [63]: list(remove_dup(mylist, 0))
Out[63]: [4, 1, 2, 6, 1, 0, 9, 8, 9]

此外,如果您正在处理更大的列表,您可以使用以下使用 Numpy 的矢量化和优化方法:

In [80]: arr = np.array([4, 1, 2, 6, 1, 0, 9, 8, 0, 9])

In [81]: mask = arr == 0

In [82]: first_ind = np.where(mask)[0][0]

In [83]: mask[first_ind] = False

In [84]: arr[~mask]
Out[84]: array([4, 1, 2, 6, 1, 0, 9, 8, 9])

【讨论】:

  • 为什么是temp.pop()?为什么不只使用布尔局部变量?
  • @DanielPryden 因为它只有一项并且保持代码整洁。此外,它不会对性能产生明显影响。
【解决方案3】:

听起来更好的数据结构是collections.Counter(在标准库中):

import collections

counts = collections.Counter(mylist)
counts[0] = 1
mylist = list(counts.elements())

【讨论】:

  • 设置counts[0] = min(1, counts[0]) 可能会更好,否则此代码会将 0 插入到没有任何内容的列表中。
  • 这是一个非常巧妙的想法,但 (1) 几乎可以肯定比 OP 的解决方案慢,并且 (2) 不保持秩序。
  • 如果顺序无关紧要,那真的很好。不过,顺序似乎并不重要。
  • @AlexHall:它基本上只是一个 pidgeonhole 排序,它是 O(N)。为什么这会比其他方法慢很多?
  • @DanielPryden 因为它将在 Python 空间中执行比在内置(可能是 C)空间中更多的代码。
【解决方案4】:

如果性能是一个问题,并且您乐于使用第 3 方库,请使用 numpy

Python 标准库适用于很多事情。数值数组的计算不是其中之一。

import numpy as np

mylist = np.array([4, 1, 2, 6, 1, 0, 9, 8, 0, 9])

mylist = np.delete(mylist, np.where(mylist == 0)[0][1:])

# array([4, 1, 2, 6, 1, 0, 9, 8, 9])

这里np.delete 的第一个参数是输入数组。第二个参数提取所有出现的 0 的索引,然后提取第二个实例。

性能基准测试

在 Python 3.6.2 / Numpy 1.13.1 上测试。性能将取决于系统和阵列。

%timeit jp(myarr.copy())         # 183 µs
%timeit vui(mylist.copy())       # 393 µs
%timeit original(mylist.copy())  # 1.85 s

import numpy as np
from collections import Counter

myarr = np.array([4, 1, 2, 6, 1, 0, 9, 8, 0, 9] * 1000)
mylist = [4, 1, 2, 6, 1, 0, 9, 8, 0, 9] * 1000

def jp(myarr):
    return np.delete(myarr, np.where(myarr == 0)[0][1:])

def vui(mylist):
    return [0] + list(filter(None, mylist))

def original(mylist):
    for i in mylist:
        if mylist.count(0) > 1:
            mylist.remove(0)

    return mylist

【讨论】:

  • 电脑是什么规格的,你做基准测试的?
  • @NikhilWagh,添加了 Python + Numpy 版本。我提供了代码供您测试。每台机器都会产生不同的结果。
  • 这是一个稍微不公平的比较,因为您假设列表已经是一个 numpy 数组。如果我们要改变输入类型,那么我们应该对基于计数器的方法做同样的事情。否则将构建数组作为 timeit 测试的一部分。
  • @DanielPryden,就您而言,仍然存在假设。在几乎所有情况下,上游进程(从 csv 读取、从计算中检索等)可以通过移至 numpy 来进一步优化。关键是,如果性能是问题,请考虑专门设计用于提高性能的库。或移至 C。
【解决方案5】:

切片应该做

a[start:end] # items start through end-1
a[start:]    # items start through the rest of the list
a[:end]      # items from the beginning through end-1
a[:]         # a copy of the whole list

输入:

mylist = [4,1, 2, 6, 1, 0, 9, 8, 0, 9,0,0,9,2,2,]
pos=mylist.index(0)
nl=mylist[:pos+1]+[i  for i in mylist[pos+1:] if i!=0]

print(nl)

输出:[4, 1, 2, 6, 1, 0, 9, 8, 9, 9, 2, 2]

【讨论】:

    【解决方案6】:

    你可以用这个:

    desired_value = 0
    mylist = [i for i in mylist if i!=desired_value] + [desired_value]
    

    您现在可以更改所需的值, 你也可以把它做成这样的列表

    desired_value = [0, 6]
    mylist = [i for i in mylist if i not in desired_value] + desired_value
    

    【讨论】:

      【解决方案7】:

      也许您可以使用filter

      [0] + list(filter(lambda x: x != 0, mylist))
      

      【讨论】:

      • 你应该总是更喜欢使用列表推导而不是filter + lambda。列表理解会更短、更清晰,而且通常更快。
      • filter(None,mylist) 更好
      • @DanielPryden 谢谢。
      • @Jean-FrançoisFabre 当然可以!这里只是举例,它可能是 '42' ;-)
      • filter(None,x) 是您不需要任何 lambda 或任何函数的确切情况。它只是保持“真实”的价值。
      【解决方案8】:

      这是在线的:其中m是出现一次的数字,并保持顺序

      [x for i,x in enumerate(mylist) if mylist.index(x)==i or x!=m]
      

      结果

      [4, 1, 2, 6, 1, 0, 9, 8, 9]
      

      【讨论】:

      • 这种方法效率极低。调用list.index,所有项目的复杂度为 O(n),外加两个条件检查!
      【解决方案9】:

      您可以使用itertools.count 计数器 每次迭代都会返回 0, 1, ...:

      from itertools import count
      
      mylist = [4, 1, 2, 6, 1, 0, 9, 8, 0, 9]
      
      counter = count()
      
      # next(counter) will be called each time i == 0
      # it will return 0 the first time, so only the first time
      # will 'not next(counter)' be True
      out = [i for i in mylist if i != 0 or not next(counter)]
      print(out)
      
      # [4, 1, 2, 6, 1, 0, 9, 8, 9]
      

      顺序保持不变,可以轻松修改以对任意数量的值进行重复数据删除:

      from itertools import count
      
      mylist = [4, 1, 2, 6, 1, 0, 9, 8, 0, 9]
      
      items_to_dedup = {1, 0}
      counter = {item: count() for item in items_to_dedup}
      
      out = [i for i in mylist if i not in items_to_dedup or not next(counter[i])]
      print(out)
      
      # [4, 1, 2, 6, 0, 9, 8, 9]
      

      【讨论】:

        猜你喜欢
        • 2014-07-05
        • 2011-09-29
        • 2013-08-03
        • 2020-11-10
        • 2019-01-05
        • 1970-01-01
        • 2016-03-14
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多