【问题标题】:Create a complement of list preserving duplicate values创建一个保留重复值的列表的补充
【发布时间】:2017-07-05 13:10:06
【问题描述】:

给定列表a = [1, 2, 2, 3] 及其子列表b = [1, 2] 找到一个以sorted(a) == sorted(b + complement) 的方式补充b 的列表。在上面的示例中,complement 将是[2, 3] 的列表。

使用列表推导很诱人:

complement = [x for x in a if x not in b]

或设置:

complement = list(set(a) - set(b))

但是,这两种方式都会返回complement = [3]

一个明显的方法是:

complement = a[:]
for element in b:
    complement.remove(element)

但这让人非常不满意,而且不是很Pythonic。我错过了一个明显的成语还是这样?

正如下面指出的那样,性能如何,这是O(n^2) 有没有更有效的方法?

【问题讨论】:

  • 您可以从 *unsatisfying 选项创建列表理解。这会让它*令人满意吗?
  • 看不出来怎么发!
  • 我真的不明白它有什么un-Pythonic。它不是完全声明性的,但仍然非常可读。我会更关心性能,因为这将在 O(n^2) 中运行,这不是很好...
  • @JanHak 只是用未分配的[complement.remove(element) for element in b] 替换最后两行,但正如@Willem 所说,这与外观无关。这几乎是列表理解的 *hack,因为我们不是创建列表,而是简单地使用语法来做其他事情

标签: python list list-comprehension


【解决方案1】:

我想到的唯一更多声明性因此Pythonic的方式并且提高了大型b(和@987654322)的性能 @) 是使用某种递减计数器:

from collections import Counter

class DecrementCounter(Counter):

    def decrement(self,x):
        if self[x]:
            self[x] -= 1
            return True
        return False

现在我们可以使用列表推导:

b_count = DecrementCounter(b)
complement = [x for x in a if not b_count.decrement(x)]

因此,我们在这里跟踪b 中的计数,对于a 中的每个元素,我们查看它是否是b_count 的一部分。如果确实如此,我们会减少计数器并忽略该元素。否则我们将它添加到complement。请注意,这只有在我们确定这样的complement 存在时才有效。

你构造了complement之后,你可以检查补码是否存在:

not bool(+b_count)

如果这是False,则无法构造此类补码(例如a=[1]b=[1,3])。所以一个完整的实现可能是:

b_count = DecrementCounter(b)
complement = [x for x in a if not b_count.decrement(x)]
if +b_count:
    raise ValueError('complement cannot be constructed')

如果字典查找在 O(1) 中运行(它通常这样做,只有在极少数情况下它是 O(n)),那么这个算法在 O(n) 中运行em>O(|a|+|b|) (即列表大小的总和)。而remove 方法通常会在O(|a|×|b|)中运行。

【讨论】:

  • 感谢@Willem 很好的回答学到了一些东西!我不得不考虑调用减量方法的副作用 - 有创意!
  • @Hironsan:这就是为什么(甚至用粗体字),答案说:“这样的补充存在。”。如果你交换ab,这样的补码不存在。
  • @Hironsan:我包含了一个测试,如果无法构建这样的补码,它将引发ValueError
  • ...为什么bool(+b_count)?这不等同于布尔上下文中的 b_count 吗? AFAIK +counter == counter if isinstance(counter, Counter),所以 + 实际上什么都不做,除了引起混淆,然后再次 bool ......这就是 python 在检查值的真实性时所做的事情。
  • 对于计数器,+ 删除负数和零倒计时。 bool 确实可以省略。
【解决方案2】:

为了降低您已经有效的方法的复杂性,您可以使用collections.Counter(这是一个具有快速查找功能的专用字典)来计算两个列表中的项目。

然后通过减去值来更新计数,最后通过仅保留计数大于 0 的项目过滤列表并使用itertools.chain 重建它/链接它

from collections import Counter
import itertools

a  = [1, 2, 2, 2, 3]
b = [1, 2]

print(list(itertools.chain.from_iterable(x*[k] for k,x in (Counter(a)-Counter(b)).items() if x > 0)))

结果:

[2, 2, 3]

【讨论】:

  • Counter 订单是什么?是O(n)吗?
  • 但是这会不会导致如果complement 应该有两个二,它只会有一个?比如a=[1,2,2,2]b=[1,2]...这里的条件是如果x > 0,你只发出一次。
  • 计数器是一个字典:查找小于O(n)
  • 列表中的计数元素小于 O(n) 使用 Counter?!
  • 不计算,查找。您必须分析数据,因为您必须运行一次。但在那之后,查找速度很快。
【解决方案3】:

O(n log n)

a = [1, 2, 2, 3]
b = [1, 2]
a.sort()
b.sort()

L = []
i = j = 0
while i < len(a) and j < len(b):
    if a[i] < b[j]:
        L.append(a[i])
        i += 1
    elif a[i] > b[j]:
        L.append(b[j])
        j += 1
    else:
        i += 1
        j += 1

while i < len(a):
    L.append(a[i])
    i += 1

while j < len(b):
    L.append(b[j])
    j += 1

print(L)

【讨论】:

  • 如果我交换ab,它没有效果:)
【解决方案4】:

如果补语中元素的顺序无关紧要,那么只需要 collections.Counter:

from collections import Counter

a = [1, 2, 3, 2]
b = [1, 2]

complement = list((Counter(a) - Counter(b)).elements())  # complement = [2, 3]

如果补语中的项目顺序应与原始列表中的顺序相同,则使用以下内容:

from collections import Counter, defaultdict
from itertools import count

a = [1,2,3,2]
b = [2,1]

c = Counter(b)
d = defaultdict(count)

complement = [x for x in a if next(d[x]) >= c[x]]  # complement = [3, 2]

【讨论】:

    【解决方案5】:

    主要思想:如果值不唯一,则使其唯一

    def add_duplicate_position(items):
        element_counter = {}
        for item in items:
            element_counter[item] = element_counter.setdefault(item,-1) + 1
            yield element_counter[item], item
        
    assert list(add_duplicate_position([1, 2, 2, 3])) == [(0, 1), (0, 2), (1, 2), (0, 3)]
    
    def create_complementary_list_with_duplicates(a,b):
        a = list(add_duplicate_position(a))
        b = set(add_duplicate_position(b))
        return [item for _,item in [x for x in a if x not in b]]
      
    a = [1, 2, 2, 3]
    b = [1, 2]
    assert create_complementary_list_with_duplicates(a,b) == [2, 3]
    

    【讨论】:

      猜你喜欢
      • 2021-09-21
      • 2023-01-10
      • 1970-01-01
      • 2023-02-22
      • 2018-11-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多