Pythonic方法删除列表中的反向重复项

时间:2016-12-15 12:51:23

标签: python python-2.7 duplicates

我有一对配对列表:

[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]

我想删除任何重复的地方

[a,b] == [b,a]

所以我们最终只有

[0, 1], [0, 4], [1, 4]

我可以做一个内心的&外部循环检查反向对并附加到列表中,如果不是这样,但我确信有更多的Pythonic方法可以实现相同的结果。

9 个答案:

答案 0 :(得分:18)

如果您需要保留列表中元素的顺序,则可以使用sorted函数并使用map设置理解,如下所示:

lst = [0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]
data = {tuple(item) for item in map(sorted, lst)}
# {(0, 1), (0, 4), (1, 4)}

或者根本就没有map

data = {tuple(sorted(item)) for item in lst}

另一种方法是使用显示为herefrozenset,但请注意,只有列表中包含不同的元素才能使用此功能。因为像set一样,frozenset始终包含唯一值。因此,您最终会在子列表中找到唯一的值(丢失数据),这可能不是您想要的。

要输出列表,您始终可以使用list(map(list, result)),其中result只是Python-3.0或更新版本中的一组元组。

答案 1 :(得分:14)

如果您只想删除反向对并且不想要外部库,则可以使用简单的生成器函数(基于itertools "unique_everseen" recipe):

def remove_reversed_duplicates(iterable):
    # Create a set for already seen elements
    seen = set()
    for item in iterable:
        # Lists are mutable so we need tuples for the set-operations.
        tup = tuple(item)
        if tup not in seen:
            # If the tuple is not in the set append it in REVERSED order.
            seen.add(tup[::-1])
            # If you also want to remove normal duplicates uncomment the next line
            # seen.add(tup)
            yield item

>>> list(remove_reversed_duplicates(a))
[[0, 1], [0, 4], [1, 4]]

生成器函数可能是解决此问题的一种非常快捷的方法,因为set-lookups非常便宜。 此方法还保留了初始列表的顺序,而删除了反向重复项,而更快比大多数替代项

如果您不介意使用外部库并且想要删除所有重复项(反向和相同),则可以选择:iteration_utilities.unique_everseen

>>> a = [[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]]

>>> from iteration_utilities import unique_everseen

>>> list(unique_everseen(a, key=set))
[[0, 1], [0, 4], [1, 4]]

这将检查任何项目是否具有与其他项目相同的仲裁顺序(因此key=set)。在这种情况下,这可以按预期工作,但它也会删除重复的[a, b]而不是[b, a]次出现。您也可以使用key=sorted(与其他答案一样)。像unique_everseen这样的算法复杂度很差,因为key函数的结果不可清除,因此快速查找被慢速查找替换。为了加快这一点,你需要使密钥可以清除,例如将它们转换为已排序的元组(如其他一些答案所示):

>>> from iteration_utilities import chained
>>> list(unique_everseen(a, key=chained(sorted, tuple)))
[[0, 1], [0, 4], [1, 4]]

chained只不过是lambda x: tuple(sorted(x))的更快替代品。

编辑:正如@ jpmc26所提到的,可以使用frozenset代替普通集:

>>> list(unique_everseen(a, key=frozenset))
[[0, 1], [0, 4], [1, 4]]

为了了解性能,我对不同的建议进行了一些timeit比较:

>>> a = [[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]]

>>> %timeit list(remove_reversed_duplicates(a))
100000 loops, best of 3: 16.1 µs per loop
>>> %timeit list(unique_everseen(a, key=frozenset))
100000 loops, best of 3: 13.6 µs per loop
>>> %timeit list(set(map(frozenset, a)))
100000 loops, best of 3: 7.23 µs per loop

>>> %timeit list(unique_everseen(a, key=set))
10000 loops, best of 3: 26.4 µs per loop
>>> %timeit list(unique_everseen(a, key=chained(sorted, tuple)))
10000 loops, best of 3: 25.8 µs per loop
>>> %timeit [list(tpl) for tpl in list(set([tuple(sorted(pair)) for pair in a]))]
10000 loops, best of 3: 29.8 µs per loop
>>> %timeit set(tuple(item) for item in map(sorted, a))
10000 loops, best of 3: 28.5 µs per loop

包含许多重复项的长列表:

>>> import random
>>> a = [[random.randint(0, 10), random.randint(0,10)] for _ in range(10000)]

>>> %timeit list(remove_reversed_duplicates(a))
100 loops, best of 3: 12.5 ms per loop
>>> %timeit list(unique_everseen(a, key=frozenset))
100 loops, best of 3: 10 ms per loop
>>> %timeit set(map(frozenset, a))
100 loops, best of 3: 10.4 ms per loop

>>> %timeit list(unique_everseen(a, key=set))
10 loops, best of 3: 47.7 ms per loop
>>> %timeit list(unique_everseen(a, key=chained(sorted, tuple)))
10 loops, best of 3: 22.4 ms per loop
>>> %timeit [list(tpl) for tpl in list(set([tuple(sorted(pair)) for pair in a]))]
10 loops, best of 3: 24 ms per loop
>>> %timeit set(tuple(item) for item in map(sorted, a))
10 loops, best of 3: 35 ms per loop

重复次数减少:

>>> a = [[random.randint(0, 100), random.randint(0,100)] for _ in range(10000)]

>>> %timeit list(remove_reversed_duplicates(a))
100 loops, best of 3: 15.4 ms per loop
>>> %timeit list(unique_everseen(a, key=frozenset))
100 loops, best of 3: 13.1 ms per loop
>>> %timeit set(map(frozenset, a))
100 loops, best of 3: 11.8 ms per loop


>>> %timeit list(unique_everseen(a, key=set))
1 loop, best of 3: 1.96 s per loop
>>> %timeit list(unique_everseen(a, key=chained(sorted, tuple)))
10 loops, best of 3: 24.2 ms per loop
>>> %timeit [list(tpl) for tpl in list(set([tuple(sorted(pair)) for pair in a]))]
10 loops, best of 3: 31.1 ms per loop
>>> %timeit set(tuple(item) for item in map(sorted, a))
10 loops, best of 3: 36.7 ms per loop

因此,remove_reversed_duplicatesunique_everseenkey=frozenset)和set(map(frozenset, a))的变体似乎是目前最快的解决方案。哪一个取决于输入的长度和重复的数量。

答案 2 :(得分:6)

TL; DR

set(map(frozenset, lst))

解释

如果这些对在逻辑上是无序的,则它们更自然地表示为集合。在你达到这一点之前将它们作为集合更好,但你可以像这样转换它们:

lst = [[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]]
lst_as_sets = map(frozenset, lst)

然后在迭代中消除重复的自然方法是将其转换为set

deduped = set(lst_as_sets)

(这是我在第一步中选择frozenset的主要原因。可变set s不可清,因此无法将其添加到set。)

或者你可以像TL中那样在一行中进行; DR部分。

我认为这更简单,更直观,并且与您对数据的思考的方式更加匹配,而不是与排序和元组混淆。

转换回来

如果出于某种原因,您确实需要list list作为最终结果,那么转换回来是微不足道的:

result_list = list(map(list, deduped))

但是尽可能长时间地将它全部留作set可能更合乎逻辑。我只能想到你可能需要这个的一个原因,以及它与现有代码/库的兼容性。

答案 3 :(得分:4)

您可以对每对进行排序,将您的对列表转换为一组元组,然后再将其转换回来:

l = [[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]]
[list(tpl) for tpl in list(set([tuple(sorted(pair)) for pair in l]))]
#=> [[0, 1], [1, 4], [0, 4]]

这些步骤可能比长期单行更容易理解:

>>> l = [[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]]
>>> [sorted(pair) for pair in l]
# [[0, 1], [0, 4], [0, 1], [1, 4], [0, 4], [1, 4]]
>>> [tuple(pair) for pair in _]
# [(0, 1), (0, 4), (0, 1), (1, 4), (0, 4), (1, 4)]
>>> set(_)
# set([(0, 1), (1, 4), (0, 4)])
>>> list(_)
# [(0, 1), (1, 4), (0, 4)]
>>> [list(tpl) for tpl in _]
# [[0, 1], [1, 4], [0, 4]]

答案 4 :(得分:4)

您可以使用内置filter功能。

from __future__ import print_function

def my_filter(l):
    seen = set()

    def not_seen(it):
        s = min(*it), max(*it)
        if s in seen:
            return False
        else:
            seen.add(s)
            return True

    out = filter(not_seen, l)

    return out

myList = [[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]]
print(my_filter(myList)) # [[0, 1], [0, 4], [1, 4]]

作为补充,我会将你定位到描述unique_everseen函数的Python itertools module,该函数与上面的函数基本相同,但是在基于生成器的惰性版本中。如果您正在处理大型阵列,可能比我们的任何解决方案都要好。以下是如何使用它:

from itertools import ifilterfalse

def unique_everseen(iterable, key=None):
    "List unique elements, preserving order. Remember all elements ever seen."
    # unique_everseen('AAAABBBCCDAABBB') --> A B C D
    # unique_everseen('ABBCcAD', str.lower) --> A B C D
    seen = set()
    seen_add = seen.add
    if key is None:
        for element in ifilterfalse(seen.__contains__, iterable):
            seen_add(element)
            yield element
    else:
        for element in iterable:
            k = key(element)
            if k not in seen:
                seen_add(k)
                yield element

gen = unique_everseen(myList, lambda x: (min(x), max(x))) # gen is an iterator
print(gen) # <generator object unique_everseen at 0x7f82af492fa0>
result = list(gen) # consume generator into a list.
print(result) # [[0, 1], [0, 4], [1, 4]]

我还没有做任何指标来查看谁是最快的。然而,在这个版本中,内存效率和O复杂度似乎更好。

时间最小/最大与排序

内置sorted函数可以传递给unique_everseen来订购内部向量中的项目。相反,我通过lambda x: (min(x), max(x))。因为我知道矢量大小正好是2,所以我可以像这样进行。

要使用sorted我需要传递lambda x: tuple(sorted(x)),这会增加开销。不是戏剧性的,但仍然。

myList = [[random.randint(0, 10), random.randint(0,10)] for _ in range(10000)]
timeit.timeit("list(unique_everseen(myList, lambda x: (min(x), max(x))))", globals=globals(), number=20000)
>>> 156.81979029000013
timeit.timeit("list(unique_everseen(myList, lambda x: tuple(sorted(x))))", globals=globals(), number=20000)
>>> 168.8286430349999

在Python 3中完成计时,将globals kwarg添加到timeit.timeit

答案 5 :(得分:3)

一个简单的 unnested 解决方案:

pairs = [[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]]
s=set()
for p in pairs:
    # Lists are unhashable so make the "elements" into tuples
    p = tuple(p)
    if p not in s and p[::-1] not in s:
        s.add(p)

print s

答案 6 :(得分:3)

编辑以更好地解释

首先对每个列表进行排序,然后使用词典键获取一组唯一的元素,并列出列表理解。

为什么选择元组?为了避免使用&#34; unhashable&#34;通过fromkeys()函数时出错

my_list = [[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]]
tuple_list = [ tuple(sorted(item)) for item in my_list ]
final_list = [ list(item) for item in list({}.fromkeys(tuple_list)) ]

使用OrderedDict甚至可以保留列表顺序。

from collections import OrderedDict

my_list = [[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]]
tuple_list = [ tuple(sorted(item)) for item in my_list ]
final_list = [ list(item) for item in list(OrderedDict.fromkeys(tuple_list)) ]

以上代码将生成所需的列表

[[0, 1], [0, 4], [1, 4]]

答案 7 :(得分:1)

如果对和配对项的顺序很重要,那么通过测试成员资格来创建新列表可能就是这里的方法。

pairs = [0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]
no_dups = []
for pair in pairs:
    if not any( all( i in p for i in pair ) for p in no_dups ):
        no_dups.append(pair)

否则,我会选择Styvane's answer

顺便提一下,上述解决方案不适用于匹配对的情况。例如,[0,0]不会添加到列表中。为此,您需要添加一个额外的支票:

for pair in pairs:
    if not any( all( i in p for i in pair ) for p in no_dups ) or ( len(set(pair)) == 1 and not pair in no_dups ):
        no_dups.append(pair)

但是,该解决方案不会选择“对”(例如[])。为此,您还需要进行一次调整:

    if not any( all( i in p for i in pair ) for p in no_dups ) or ( len(set(pair)) in (0,1) and not pair in no_dups ):
        no_dups.append(pair)

要求and not pair in no_dups位阻止将[0,0][]添加到no_dups 两次

答案 8 :(得分:1)

好吧,我是&#34;检查反向对并附加到列表中,如果情况并非如此&#34;正如你所说,你可以这样做,但我使用的是单循环。

bundle update rmagick

现有答案的优势在于,IMO更具可读性。这里不需要深入了解标准库。没有跟踪任何复杂的事情。对于初学者而言,唯一可能不熟悉的概念是x=[[0, 1], [0, 4], [1, 0], [1, 4], [4, 0], [4, 1]] out = [] for pair in x: if pair[::-1] not in out: out.append(pair) print out 还原该对。

但性能为O(n ** 2),因此如果性能问题和/或列表很大,请不要使用。