2009-06-23 12 views
14

Otrzymuję słownik jako dane wejściowe i chcę zwrócić listę kluczy, dla których wartości słownika są unikalne w zakresie tego słownika.Python: znajdowanie kluczy z unikalnymi wartościami w słowniku?

Wyjaśnię na przykładzie. Powiedzieć, że moje wejście jest słownika a, skonstruowany w następujący sposób:

a = dict() 
a['cat'] =  1 
a['fish'] =  1 
a['dog'] =  2 # <-- unique 
a['bat'] =  3 
a['aardvark'] = 3 
a['snake'] = 4 # <-- unique 
a['wallaby'] = 5 
a['badger'] = 5 

Wynik Spodziewam jest ['dog', 'snake'].

Istnieją oczywiste sposoby, aby osiągnąć ten cel, jednak zastanawiałem się, czy istnieje czysty sposób Pythonian, aby wykonać zadanie.

Odpowiedz

12

myślę skuteczny sposób, jeśli DICT jest zbyt duża będzie

countMap = {} 
for v in a.itervalues(): 
    countMap[v] = countMap.get(v,0) + 1 
uni = [ k for k, v in a.iteritems() if countMap[v] == 1] 
+1

Byłoby ładniej z collections.defaultdict (int), IMO –

+0

tak, ale chciałbym zostawić to tak, aby ludzie wiedzieli, co robimy, gdy nie było defaultdicts –

+0

WASTEFUL: robi 'dla k, v w a.iteritems (): 'ale nie używa k !!! –

5

Zauważ, że to rzeczywiście jest bruteforce:

l = a.values() 
b = [x for x in a if l.count(a[x]) == 1] 
+0

nie będzie wyjścia [ 'pies', 'wąż'] –

+0

Czy nie l.count ('pies') do zera? l to [3, 3, 2, 1, 4, 5, 1, 5] w moim systemie. –

+0

ok, widzę, że cobbal już poprawił kod. dzięki. –

-1

Można zrobić coś takiego (tylko policzyć liczbę wystąpień dla każdej wartości):

def unique(a): 
    from collections import defaultdict 
    count = defaultdict(lambda: 0) 
    for k, v in a.iteritems(): 
     count[v] += 1 
    for v, c in count.iteritems(): 
     if c <= 1: 
      yield v 
+0

To przynosi wartości (2, 4), kiedy powinno dać klucze ("pies", "wąż"). –

+1

Uważam, że 'defaultdict (int)' jest nieco bardziej przejrzysty niż 'defaultdict (lambda: 0)'. Ponieważ domyślny dyktat prawie każdego innego typu użyje po prostu nazwy typu. –

+0

Ah, podając niewłaściwą wartość, przepraszam. –

4
>>> b = [] 
>>> import collections 
>>> bag = collections.defaultdict(lambda: 0) 
>>> for v in a.itervalues(): 
...  bag[v] += 1 
... 
>>> b = [k for (k, v) in a.iteritems() if bag[v] == 1] 
>>> b.sort() # optional 
>>> print b 
['dog', 'snake'] 
>>> 
+0

collections.defaultdict (int) będzie również działać –

+0

@Ryan: Prawda, ale 'lambda: 0' jest bardziej jawna niż' int' ... AFAICT, dopóki nie pojawi się defaultdict [2.5], liczba osób, które wiedziały, że int() wyprodukowało 0 [od 2.2] zamiast wyjątku był

-2

Użyj zagnieżdżonych sprawdzeń list!

print [v[0] for v in 
      dict([(v, [k for k in a.keys() if a[k] == v]) 
        for v in set(a.values())]).values() 
     if len(v) == 1] 
+1

Nie rozumiem, w jaki sposób używanie rozumienia list w ten sposób jest wygraną. Dla mnie to właśnie sprawia, że ​​rozwiązanie jest trudniejsze do zrozumienia (gra słów nie jest przeznaczona). Czytelność jest kluczowa, a to rozwiązanie nie jest po prostu czytelne IMO. –

+0

Rax poprosił o "schludny Pythonie sposób, aby wykonać zadanie", w przeciwieństwie do "oczywistych" rozwiązań problemu, który jest inny niż trywialny. –

+0

(1) Użyj 'k in a' zamiast' k in a.keys() '(2) Użyj' whatever.itervalues ​​() 'zamiast' whatever.values ​​() '(3) The dict (yadda yadda) część buduje już nadmierną odwrotność "a" nieefektywnie (4) Nie jest ani zgrabna, ani Python (ic | ian) ... ale na pewno nie jest to oczywiste! (5) Policz liczbę respondentów, których pierwszym wysiłkiem przy tak zwanym trywialnym problemie była falsyfikacja. –

0

Oto kolejna odmiana.

>>> import collections 
>>> inverse= collections.defaultdict(list) 
>>> for k,v in a.items(): 
...  inverse[v].append(k) 
... 
>>> [ v[0] for v in inverse.values() if len(v) == 1 ] 
['dog', 'snake'] 

Jestem stronniczy, ponieważ odwrócony słownik jest tak powszechnym wzorcem projektowym.

+0

Chcesz [v [0] dla k, v ...] w ostatnim wierszu, aby otrzymać [" pies "," wąż "] zgodnie z żądaniem. –

+0

(1) Zamiast .items(), użyj .iteritems(). (2) Ostatnia linia wydobywa klucz niepotrzebnie; powinno być '[v [0] dla v inverse.itervalues ​​(), jeśli len (v) == 1' (3) W każdym przypadku budowanie całego odwróconego dict jest przesadą. –

5

Oto rozwiązanie, które wymaga jedynie przemierzając dict raz:

def unique_values(d): 
    seen = {} # dict (value, key) 
    result = set() # keys with unique values 
    for k,v in d.iteritems(): 
     if v in seen: 
      result.discard(seen[v]) 
     else: 
      seen[v] = k 
      result.add(k) 
    return list(result) 
+0

Jeśli wartość wystąpi 3 razy, spróbujesz usunąć nieistniejący element z 'wyniku' ... docs say" "" remove (elem) Usuń element elem z zestawu.Raises KeyError jeśli elem nie jest zawarty w zestaw. "" " –

+0

Dobrze, jesteś! Poprawiłem go, aby użyć zamiast tego discard(). –

2

Nieco bardziej rozwlekły, ale musi tylko jedną przepustkę nad:

revDict = {} 
for k, v in a.iteritems(): 
    if v in revDict: 
    revDict[v] = None 
    else: 
    revDict[v] = k 

[ x for x in revDict.itervalues() if x != None ] 

(mam nadzieję, że to działa, ponieważ nie mogę tego przetestować tutaj)

+1

Nie działa, jeśli jednym z klawiszy słownika jest Brak. Na przykład, jeśli a jest {None: 1}, wyjście powinno być [None], ale powyższy kod wytworzy []. Również: 'x is not None' jest lepsze od' x! = None'. –

+0

Dzięki za komentarz! Masz całkowitą rację. W praktyce rzadko zdarza się, że None jest używany ... ale nawet wtedy można stworzyć DummyObject: "Dummy = object()" zamiast używać None. – Juergen

2

Co z podklasami?

class UniqueValuesDict(dict): 

    def __init__(self, *args): 
     dict.__init__(self, *args) 
     self._inverse = {} 

    def __setitem__(self, key, value): 
     if value in self.values(): 
      if value in self._inverse: 
       del self._inverse[value] 
     else: 
      self._inverse[value] = key 
     dict.__setitem__(self, key, value) 

    def unique_values(self): 
     return self._inverse.values() 

a = UniqueValuesDict() 

a['cat'] =  1 
a['fish'] =  1 
a[None] =  1 
a['duck'] =  1 
a['dog'] =  2 # <-- unique 
a['bat'] =  3 
a['aardvark'] = 3 
a['snake'] = 4 # <-- unique 
a['wallaby'] = 5 
a['badger'] = 5 

assert a.unique_values() == ['dog', 'snake'] 
+0

Ma to tę zaletę, że ma mniejszy ślad pamięci, ale kończy się przeprowadzaniem przeszukiwania O (N) przy każdym ustawieniu elementu, więc może być dużo wolniejsze niż metoda tabulacji słownikowej. Myślę też, że możesz użyć zestawu dla _przeczytania zamiast dyktowania. –

+0

Kolejny problem: OP nie stworzył żadnych ograniczeń co do tego, jak uzyskano treść dyktatu. Można się więc spodziewać, że "del a ['bat']; print a.unique_values ​​() 'sprawiłoby, że' aardvark' pojawiłby się na wyjściu, ale niestety nie jest i nie wymaga naprawy, co wymagałoby jeszcze więcej zawinięć i __double__underscores__ :-( –

Powiązane problemy