【问题标题】:Python list subtraction operationPython列表减法运算
【发布时间】:2011-03-26 14:32:21
【问题描述】:

我想做类似的事情:

>>> x = [1,2,3,4,5,6,7,8,9,0]  
>>> x  
[1, 2, 3, 4, 5, 6, 7, 8, 9, 0]  
>>> y = [1,3,5,7,9]  
>>> y  
[1, 3, 5, 7, 9]  
>>> y - x   # (should return [2,4,6,8,0])

但这不被 python 列表支持 最好的方法是什么?

【问题讨论】:

  • @ezdazuzena 这不是减法。这是两个列表之间的区别。您的分享不是对这个问题的重复。
  • [2, 2] - [2] 应该返回什么? []? [2]?
  • @McKay [2,2] - [2] 应该返回 [2]。 [2,2] - [1,2,2,3] 应该返回 []
  • 这个问题是关于列表减法,但接受的答案更接近于集合减法。
  • 应该 [2, 1, 2, 3, 2, 4, 2] - [2, 3, 2] 返回什么,为什么?它应该在中间找到232并返回2142吗?还是应该每次都找到第一个并返回1242?或者是其他东西?我的意思是,这些不是显而易见的答案,取决于需要。

标签: python list


【解决方案1】:

使用列表推导:

[item for item in x if item not in y]

如果你想使用-中缀语法,你可以这样做:

class MyList(list):
    def __init__(self, *args):
        super(MyList, self).__init__(args)

    def __sub__(self, other):
        return self.__class__(*[item for item in self if item not in other])

你可以像这样使用它:

x = MyList(1, 2, 3, 4)
y = MyList(2, 5, 2)
z = x - y   

但是,如果您绝对不需要列表属性(例如,排序),只需按照其他答案的建议使用集合。

【讨论】:

  • @admica,不要使用 list 作为变量名,因为它会影响 list 构造函数。如果您确实使用“列表”,请在其前面加上下划线。另外,通过删除*,你破坏了我的代码......
  • 如果您执行[1,1,2,2] - [1,2],您将得到空列表。 [1,1,2,2] - [2] 给了[1,1] 所以它不是真正的列表减法,它更像是 "List from List X without elements from set Y".
  • @AlfredZien 他说了什么
  • 列表理解方法比设置差异方法慢(在我的示例中)。
  • @BarnabasSzabolcs:这不会节省任何东西,因为它会在 every 检查之前将y 转换为set(这与原始工作的成本相似) .您需要在 listcomp 之外执行 yset = set(y),然后测试 if item not in yset,或者作为一个令人震惊的 hack,执行 [item for yset in [set(y)] for item in x if item not in yset],它滥用嵌套的 listcomps 将 yset 缓存为单行。使用 list(itertools.filterfalse(set(y).__contains__, x)) 的一个稍微不那么难看但性能良好的单线解决方案是因为 filterfalse 的参数只构造一次。
【解决方案2】:

使用set difference

>>> z = list(set(x) - set(y))
>>> z
[0, 8, 2, 4, 6]

或者您可能只是设置了 x 和 y,因此您不必进行任何转换。

【讨论】:

  • 这将失去任何排序。这可能会或可能不重要,具体取决于上下文。
  • 这也将丢失可能需要/想要维护的任何可能的重复项。
  • 我收到TypeError: unhashable type: 'dict'
  • 在被比较的列表很大的情况下,这会更快
  • 如果列表中项目的排序和重复对上下文不重要,这是一个很好的答案,而且可读性很强。
【解决方案3】:

如果重复和订购商品有问题:

[i for i in a if not i in b or b.remove(i)]

a = [1,2,3,3,3,3,4]
b = [1,3]
result: [2, 3, 3, 3, 4]

【讨论】:

  • 这行得通,尽管它是 O(m * n) 运行时(每当 listcomp 包含副作用时,我都会畏缩); you can improve on it using collections.Counter 获取 O(m + n) 运行时。
  • 我很难理解这一点,谁能解释一下?
【解决方案4】:

这是一个“集合减法”操作。为此使用设置的数据结构。

在 Python 2.7 中:

x = {1,2,3,4,5,6,7,8,9,0}
y = {1,3,5,7,9}
print x - y

输出:

>>> print x - y
set([0, 8, 2, 4, 6])

【讨论】:

  • list(set([1,2,3,4,5]) - set([1,2,3])) = [4, 5] 所以这是每个要首先设置的列表,然后减去(或单向差异)并返回列表。
  • 如果你想保持x set的原始项目顺序,那就不好了。
【解决方案5】:

对于许多用例,您想要的答案是:

ys = set(y)
[item for item in x if item not in ys]

这是aaronasterling's answerquantumSoup's answer 的混合体。

aaronasterling 的版本对x 中的每个元素进行len(y) 项目比较,因此需要二次时间。 quantumSoup 的版本使用集合,因此它对x 中的每个元素进行一次恒定时间集合查找——但是,因为它将both xy 转换为集合,它失去了顺序你的元素。

通过仅将y 转换为一个集合,并按顺序迭代x,您可以获得两全其美——线性时间和顺序保留。*


但是,quantumSoup 的版本仍然存在问题:它要求您的元素是可散列的。这几乎是集合的本质。**如果您尝试从另一个 dicts 列表中减去一个 dicts 列表,但要减去的列表很大,您会怎么做?

如果您可以以某种方式装饰您的值,使其可散列,那么问题就解决了。例如,使用其值本身可散列的平面字典:

ys = {tuple(item.items()) for item in y}
[item for item in x if tuple(item.items()) not in ys]

如果您的类型有点复杂(例如,您经常处理与 JSON 兼容的值,这些值是可散列的,或者值递归地为相同类型的列表或字典),您仍然可以使用此解决方案。但有些类型就是不能转换成任何可散列的东西。


如果您的项目不是,也无法制作,可散列,但它们是可比较的,您至少可以获得对数线性时间(O(N*log M),这比 O(N*M) 的时间要好得多列表解决方案,但不如设置解决方案的O(N+M)时间)通过排序和使用bisect

ys = sorted(y)
def bisect_contains(seq, item):
    index = bisect.bisect(seq, item)
    return index < len(seq) and seq[index] == item
[item for item in x if bisect_contains(ys, item)]

如果您的项目既不可散列也不可比较,那么您将陷入二次解。


* 请注意,您也可以通过使用一对OrderedSet 对象来执行此操作,您可以找到它们的配方和第三方模块。但我认为这更简单。

** 集合查找是恒定时间的原因是它所要做的就是散列值并查看该散列是否存在条目。如果它不能散列值,这将不起作用。

【讨论】:

    【解决方案6】:

    如果列表允许重复元素,您可以使用集合中的计数器:

    from collections import Counter
    result = list((Counter(x)-Counter(y)).elements())
    

    如果您需要保留 x 中的元素顺序:

    result = [ v for c in [Counter(y)] for v in x if not c[v] or c.subtract([v]) ]
    

    【讨论】:

    • 这很好,虽然它确实失去了排序;修复 is a bit more complicated.
    • @ShadowRanger,确实如此。但只是一点点。
    • 别介意,我只是对带有缓存和副作用的 listcomps 感到不寒而栗(尽管我认为两者的结合消除了外部可见的副作用?)。 :-)
    • 另外,这段代码不会像写的那样工作; Counter.subtract 不会删除零值元素(--= 可以,但subtract 不会),因此您永远不会停止删除元素。您希望将not v in c 替换为not c[v](对于不存在的元素返回零,因此您可以通过not 安全地测试“零性”的返回)。
    • @ShadowRanger,好收获!现在修好了。
    【解决方案7】:

    我认为最简单的方法是使用 set()。

    >>> x = [1,2,3,4,5,6,7,8,9,0]  
    >>> y = [1,3,5,7,9]  
    >>> list(set(x)- set(y))
    [0, 2, 4, 6, 8]
    

    【讨论】:

    • 小心集合是无序的:&gt;&gt;&gt; list(set([9,8,7,6])) [8, 9, 6, 7]
    【解决方案8】:

    在集合中查找值比在列表中查找更快:

    [item for item in x if item not in set(y)]
    

    我相信这会比:

    [item for item in x if item not in y]
    

    两者都保留列表的顺序。

    【讨论】:

    • 它是否会缓存set(y) 而不会在每个循环中将y 转换为新集合?否则,您需要 abarnert 的回答:ys = set(y); [i for i in x if i not in ys]
    • 一些粗略的测试表明if i not in set(y)if i not in y 花费的时间长25%(其中y 是一个列表)。预转换套件所需的时间减少了 55%。使用相当短的 xy 进行测试,但如果有的话,差异应该会随着长度而变得更加明显。
    • @Jacktose:是的,这个解决方案做得更多,因为它必须迭代和散列 yevery 元素的 every 元素x;除非相等比较相对于哈希计算真的很昂贵,否则这总是会输给普通的item not in y
    • @ShadowRanger 这是有道理的。如果集合转换是一种可靠、更快的检查方式,您会认为编译器总是会以这种方式进行检查。
    【解决方案9】:

    其他解决方案存在以下几个问题之一:

    1. 他们不维护秩序,或者
    2. 它们不会删除精确计数的元素,例如对于x = [1, 2, 2, 2]y = [2, 2],它们将y 转换为set,并在行为正确时删除所有匹配元素(仅留下[1])或删除每个唯一元素之一(留下[1, 2, 2])将删除2 两次,留下[1, 2],或者
    3. 他们在 O(m * n) 工作,而最佳解决方案可以在 O(m + n) 工作

    Alain was on the right track with Counter 解决 #2 和 #3,但该解决方案将失去排序。保留顺序的解决方案(删除每个值的第一个 n 副本以在要删除的值的 list 中重复 n)是:

    from collections import Counter
    
    x = [1,2,3,4,3,2,1]  
    y = [1,2,2]  
    remaining = Counter(y)
    
    out = []
    for val in x:
        if remaining[val]:
            remaining[val] -= 1
        else:
            out.append(val)
    # out is now [3, 4, 3, 1], having removed the first 1 and both 2s.
    

    Try it online!

    要使其删除每个元素的 最后 个副本,只需将 for 循环更改为 for val in reversed(x): 并在退出 for 循环后立即添加 out.reverse()

    根据y的长度构造CounterO(n),根据x的长度迭代xO(n),而Counter成员资格测试和变异是@ 987654349@,而list.append 摊销O(1)(给定的append 可以是O(n),但对于许多appends,整体大O 平均值为O(1),因为越来越少的它们需要重新分配),所以完成的整体工作是O(m + n)

    您还可以通过测试来确定y 中是否有没有从x 中删除的元素:

    remaining = +remaining  # Removes all keys with zero counts from Counter
    if remaining:
        # remaining contained elements with non-zero counts
    

    【讨论】:

    • 注意:这确实要求值是可散列的,但是任何不需要可散列对象的解决方案也不是通用的(例如可以算ints到固定长度数组中)或必须做的不仅仅是O(m + n) 工作(例如,下一个最佳大O 将是对唯一值/计数对进行排序list,将O(1)dict 查找更改为@987654365 @二分搜索;您需要具有计数的唯一值,而不仅仅是排序的非唯一值,否则您将支付O(n) 的费用来从排序的list 中删除元素。
    【解决方案10】:

    试试这个。

    def subtract_lists(a, b):
        """ Subtracts two lists. Throws ValueError if b contains items not in a """
        # Terminate if b is empty, otherwise remove b[0] from a and recurse
        return a if len(b) == 0 else [a[:i] + subtract_lists(a[i+1:], b[1:]) 
                                      for i in [a.index(b[0])]][0]
    
    >>> x = [1,2,3,4,5,6,7,8,9,0]
    >>> y = [1,3,5,7,9]
    >>> subtract_lists(x,y)
    [2, 4, 6, 8, 0]
    >>> x = [1,2,3,4,5,6,7,8,9,0,9]
    >>> subtract_lists(x,y)
    [2, 4, 6, 8, 0, 9]     #9 is only deleted once
    >>>
    

    【讨论】:

      【解决方案11】:

      @aaronasterling 提供的答案看起来不错,但是它与列表的默认接口不兼容:x = MyList(1, 2, 3, 4) vs x = MyList([1, 2, 3, 4])。因此,下面的代码可以用作对 python-list 更友好的代码:

      class MyList(list):
          def __init__(self, *args):
              super(MyList, self).__init__(*args)
      
          def __sub__(self, other):
              return self.__class__([item for item in self if item not in other])
      

      例子:

      x = MyList([1, 2, 3, 4])
      y = MyList([2, 5, 2])
      z = x - y
      

      【讨论】:

        【解决方案12】:

        我们也可以使用 set 方法来找出两个列表之间的差异

        x = [1, 2, 3, 4, 5, 6, 7, 8, 9, 0]
        y = [1, 3, 5, 7, 9]
        list(set(x).difference(y))
        [0, 2, 4, 6, 8]
        

        【讨论】:

          【解决方案13】:

          这个例子减去两个列表:

          # List of pairs of points
          list = []
          list.append([(602, 336), (624, 365)])
          list.append([(635, 336), (654, 365)])
          list.append([(642, 342), (648, 358)])
          list.append([(644, 344), (646, 356)])
          list.append([(653, 337), (671, 365)])
          list.append([(728, 13), (739, 32)])
          list.append([(756, 59), (767, 79)])
          
          itens_to_remove = []
          itens_to_remove.append([(642, 342), (648, 358)])
          itens_to_remove.append([(644, 344), (646, 356)])
          
          print("Initial List Size: ", len(list))
          
          for a in itens_to_remove:
              for b in list:
                  if a == b :
                      list.remove(b)
          
          print("Final List Size: ", len(list))
          

          【讨论】:

          • 避免这个,它是 O(N^2)
          【解决方案14】:

          如果值是唯一的,你也可以试试这个:

          list(set(x) - set(y))
          

          【讨论】:

          • 一个好的答案将始终包括解释为什么这会解决问题,以便 OP 和任何未来的读者可以从中学习。
          猜你喜欢
          • 2015-09-07
          • 2013-09-18
          • 2013-07-12
          • 1970-01-01
          • 2013-10-16
          • 2014-08-11
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多