Найти дубликаты во всех списках списка списков и удалить их - PullRequest
2 голосов
/ 02 апреля 2020

Я прочитал множество примеров, но не совсем нашел то, что я ищу. Пробовал несколько способов сделать это, но в поисках лучшего.

Итак, идея такова:

s1 = ['a','b','c']
s2 = ['a','potato','d']
s3 = ['a','b','h']
strings=[s1,s2,s3]

результаты должны быть:

['c']
['potato','d']
['h']

, потому что они элементы уникальны во всем списке списков.

Спасибо за любые предложения:)

Ответы [ 5 ]

3 голосов
/ 02 апреля 2020

В качестве общего подхода вы можете сохранить счетчик всех элементов, а затем сохранить те, которые появились только один раз.

In [21]: from collections import Counter 

In [23]: counts = Counter(s1 + s2 + s3)                                                                                                                                                                     

In [24]: [i for i in s1 if counts[i] == 1]                                                                                                                                                                  
Out[24]: ['c']

In [25]: [i for i in s2 if counts[i] == 1]                                                                                                                                                                  
Out[25]: ['potato', 'd']

In [26]: [i for i in s3 if counts[i] == 1]                                                                                                                                                                  
Out[26]: ['h']

А если у вас есть вложенный список, вы можете сделать следующее:

In [28]: s = [s1, s2, s3]                                                                                                                                                                                   

In [30]: from itertools import chain                                                                                                                                                                        

In [31]: counts = Counter(chain.from_iterable(s))                                                                                                                                                           

In [32]: [[i for i in lst if counts[i] == 1] for lst in s]                                                                                                                                                  
Out[32]: [['c'], ['potato', 'd'], ['h']]
1 голос
/ 02 апреля 2020

Счетчик (из коллекций) - это путь к go для этого:

from collections import Counter

s1 = ['a','b','c']
s2 = ['a','potato','d']
s3 = ['a','b','h']
strings=[s1,s2,s3]

counts  = Counter(s for sList in strings for s in sList)
uniques = [ [s for s in sList if counts[s]==1] for sList in strings ]

print(uniques) # [['c'], ['potato', 'd'], ['h']]

Если вам не разрешено использовать импортированный модуль, вы можете сделать это с помощью метода count () списка. но это было бы намного менее эффективно:

allStrings = [ s for sList in strings for s in sList ]
unique     = [[ s for s in sList if allStrings.count(s)==1] for sList in strings]

Это можно сделать более эффективным, используя набор для определения повторяющихся значений:

allStrings = ( s for sList in strings for s in sList )
seen       = set()
repeated   = set( s for s in allStrings if s in seen or seen.add(s))
unique     = [ [ s for s in sList if s not in repeated] for sList in strings ]
1 голос
/ 02 апреля 2020

Предполагая, что вы хотите, чтобы это работало для произвольного числа последовательностей, прямой (но, вероятно, не самый эффективный - вероятно, объект others может быть создан из последней итерации) способ решить эту проблему:

def deep_unique_set(*seqs):
    for i, seq in enumerate(seqs):
        others = set(x for seq_ in (seqs[:i] + seqs[i + 1:]) for x in seq_)
        yield [x for x in seq if x not in others]

или чуть более быстрый, но менее эффективный по памяти и в остальном идентичный:

def deep_unique_preset(*seqs):
    pile = list(x for seq in seqs for x in seq)
    k = 0
    for seq in seqs:
        num = len(seq)
        others = set(pile[:k] + pile[k + num:])
        yield [x for x in seq if x not in others]
        k += num

Тестирование с предоставленным входом:

s1 = ['a', 'b', 'c']
s2 = ['a', 'potato', 'd']
s3 = ['a', 'b', 'h']

print(list(deep_unique_set(s1, s2, s3)))
# [['c'], ['potato', 'd'], ['h']]
print(list(deep_unique_preset(s1, s2, s3)))
# [['c'], ['potato', 'd'], ['h']]

Обратите внимание, что если вход содержат дубликаты в одном из списков, они не удаляются, то есть:

s1 = ['a', 'b', 'c', 'c']
s2 = ['a', 'potato', 'd']
s3 = ['a', 'b', 'h']

print(list(deep_unique_set(s1, s2, s3)))
# [['c', 'c'], ['potato', 'd'], ['h']]
print(list(deep_unique_preset(s1, s2, s3)))
# [['c', 'c'], ['potato', 'd'], ['h']]

Если все дубликаты должны быть удалены, лучшим подходом является подсчет значений. Метод выбора для этого заключается в использовании collections.Counter, как предложено в @ Kasramvd answer :

def deep_unique_counter(*seqs):
    counts = collections.Counter(itertools.chain.from_iterable(seqs))
    for seq in seqs:
        yield [x for x in seq if counts[x] == 1]
s1 = ['a', 'b', 'c', 'c']
s2 = ['a', 'potato', 'd']
s3 = ['a', 'b', 'h']
print(list(deep_unique_counter(s1, s2, s3)))
# [[], ['potato', 'd'], ['h']]

В качестве альтернативы можно отслеживать повторы, например:

def deep_unique_repeat(*seqs):
    seen = set()
    repeated = set(x for seq in seqs for x in seq if x in seen or seen.add(x))
    for seq in seqs:
        yield [x for x in seq if x not in repeated]

, который будет вести себя так же, как и collections.Counter подход:

s1 = ['a', 'b', 'c', 'c']
s2 = ['a', 'potato', 'd']
s3 = ['a', 'b', 'h']
print(list(deep_unique_repeat(s1, s2, s3)))
# [[], ['potato', 'd'], ['h']]

, но немного быстрее, так как ему не нужно отслеживать неиспользуемые подсчеты.

Другой, крайне неэффективный, использует list.count() для подсчета вместо глобального счетчика:

def deep_unique_count(*seqs):
    pile = list(x for seq in seqs for x in seq)
    for seq in seqs:
        yield [x for x in seq if pile.count(x) == 1]

Эти два последних подхода также предложены в @ AlainT. ответ .


Ниже приведены некоторые сроки:

n = 100
m = 100
s = tuple([random.randint(0, 10 * n * m) for _ in range(n)] for _ in range(m))
for func in funcs:
    print(func.__name__)
    %timeit list(func(*s))
    print()

# deep_unique_set
# 10 loops, best of 3: 86.2 ms per loop

# deep_unique_preset
# 10 loops, best of 3: 57.3 ms per loop

# deep_unique_count
# 1 loop, best of 3: 1.76 s per loop

# deep_unique_repeat
# 1000 loops, best of 3: 1.87 ms per loop

# deep_unique_counter
# 100 loops, best of 3: 2.32 ms per loop
1 голос
/ 02 апреля 2020

Чтобы найти уникальные элементы в 3 списках, вы можете использовать операцию set Symmetri c diff (^) вместе с операцией union (|), так как у вас есть 3 списки.

>>> s1 = ['a','b','c']
>>> s2 = ['a','potato','d']
>>> s3 = ['a','b','h']

>>> (set(s1) | (set(s2)) ^ set(s3)
1 голос
/ 02 апреля 2020

Как насчет:

[i for i in s1 if i not in s2+s3] #gives ['c']
[j for j in s2 if j not in s1+s3] #gives ['potato', 'd']
[k for k in s3 if k not in s1+s2] #gives ['h']

Если вы хотите, чтобы все они были в списке:

uniq = [[i for i in s1 if i not in s2+s3],
[j for j in s2 if j not in s1+s3],
[k for k in s3 if k not in s1+s2]]

#output
[['c'], ['potato', 'd'], ['h']]
...