Przeczytałem mnóstwo przykładów, ale nie do końca znalazłem to, czego szukam. Wypróbowałem kilka sposobów, ale szukałem najlepszego.

Więc pomysł jest następujący:

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

Wyniki powinny być:

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

Ponieważ te pozycje są unikalne na całej liście list.

Dziękuję za wszelkie sugestie :)

2
Zen42 2 kwiecień 2020, 14:17

5 odpowiedzi

Najlepsza odpowiedź

Ogólnie możesz zachować licznik wszystkich przedmiotów, a następnie zachować te, które pojawiły się tylko raz.

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']

A jeśli masz zagnieżdżoną listę, możesz wykonać następujące czynności:

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']]
3
Kasravnd 2 kwiecień 2020, 11:39

Co powiesz na:

[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']

Jeśli chcesz, aby wszystkie znalazły się na liście:

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']]
1
Junkrat 2 kwiecień 2020, 11:25

Aby znaleźć unikalne elementy na 3 listach, możesz użyć operacji set Różnica symetryczna (^) wraz z operacją union (|), ponieważ masz 3 listy.

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

>>> (set(s1) | (set(s2)) ^ set(s3)
1
Mohammed Shabeer kp 2 kwiecień 2020, 11:36

Licznik (z kolekcji) jest drogą do tego:

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']]

Jeśli nie możesz użyć zaimportowanego modułu, możesz to zrobić za pomocą metody count () listy, ale byłoby to znacznie mniej wydajne:

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]

Można to uczynić bardziej efektywnym, używając zestawu do identyfikacji powtarzających się wartości:

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
Alain T. 2 kwiecień 2020, 12:35

Zakładając, że chcesz, aby to zadziałało dla dowolnej liczby sekwencji, bezpośrednim (ale prawdopodobnie nie najbardziej wydajnym - prawdopodobnie obiekt others można skonstruować z ostatniej iteracji) sposobem rozwiązania tego byłoby:

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]

Lub nieco szybszy, ale mniej wydajny w pamięci i poza tym identyczny:

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

Testowanie z podanym wejściem:

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']]

Zwróć uwagę, że jeśli dane wejściowe zawierają duplikaty w ramach jednej z list, nie są one usuwane, tj .:

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']]

Jeśli wszystkie duplikaty powinny zostać usunięte, lepszym podejściem jest policzenie wartości. Metodą do wyboru jest użycie collections.Counter, zgodnie z propozycją w @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']]

Alternatywnie można śledzić powtórzenia, np .:

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]

Który będzie miał takie samo zachowanie jak podejście oparte na 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']]

Ale jest nieco szybszy, ponieważ nie musi śledzić niewykorzystanych liczników.

Inny, wysoce nieefektywny, wykorzystuje list.count() do liczenia zamiast licznika globalnego:

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]

Te dwa ostatnie podejścia są również proponowane w @AlainT. odpowiedź.


Poniżej podano niektóre terminy:

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
norok2 2 kwiecień 2020, 15:20