2016-06-03 14 views
8

Stavo cercando un metodo set() -like per deduplicare un elenco, tranne per il fatto che gli elementi che compaiono nell'elenco originale non sono lavabili (sono dict s).Ho reinventato la ruota con questa funzione di deduplicazione?

ho trascorso un po 'alla ricerca di qualcosa di adeguato, e ho finito per scrivere questa piccola funzione:

def deduplicate_list(lst, key): 
    output = [] 
    keys = [] 
    for i in lst: 
     if not i[key] in keys: 
      output.append(i) 
      keys.append(i[key]) 

    return output 

a condizione che sia correttamente key dato ed è una string, questa funzione fa il suo lavoro abbastanza bene. Inutile dire che se imparassi un built-in o un modulo di libreria standard che consente la stessa funzionalità, lascerò volentieri la mia piccola routine a favore di una scelta più standard e robusta.

Sei a conoscenza di tale implementazione?

- Nota

Il seguente one-liner found from this answer,

[dict(t) for t in set([tuple(d.items()) for d in l])] 

mentre intelligente, non funzionerà perché devo lavorare con oggetti come nidificati dict s.

- Esempio

Per fini di chiarezza, ecco un esempio di utilizzo di tale procedura:

with_duplicates = [ 
    { 
     "type": "users", 
     "attributes": { 
      "first-name": "John", 
      "email": "[email protected]", 
      "last-name": "Smith", 
      "handle": "jsmith" 
     }, 
     "id": "1234" 
    }, 
    { 
     "type": "users", 
     "attributes": { 
      "first-name": "John", 
      "email": "[email protected]", 
      "last-name": "Smith", 
      "handle": "jsmith" 
     }, 
     "id": "1234" 
    } 
] 

without_duplicates = deduplicate_list(with_duplicates, key='id') 
+0

Potresti fornire una chiamata di esempio di 'deduplicate_list' nell'elenco? (Non riesco a vedere chiaramente cosa fa) :) –

+1

Non dovresti passare una lista di chiavi? – Darina

+0

guarda [questo] (http: // StackOverflow.it/a/8714242/5741205) risposta: potrebbe aiutarti a cancellare gli elementi del tuo elenco – MaxU

risposta

0

Questa answer contribuirà a risolvere un problema più generico - trovare elementi unici non per un singolo attributo (id nel tuo caso), ma se qualsiasi attributo nidificato differisce da

Il seguente cod e restituirà un elenco di indici degli elementi unici

import copy 

def make_hash(o): 

    """ 
    Makes a hash from a dictionary, list, tuple or set to any level, that contains 
    only other hashable types (including any lists, tuples, sets, and 
    dictionaries). 
    """ 

    if isinstance(o, (set, tuple, list)): 

    return tuple([make_hash(e) for e in o])  

    elif not isinstance(o, dict): 

    return hash(o) 

    new_o = copy.deepcopy(o) 
    for k, v in new_o.items(): 
    new_o[k] = make_hash(v) 

    return hash(tuple(frozenset(sorted(new_o.items())))) 

l = [ 
    { 
     "type": "users", 
     "attributes": { 
      "first-name": "John", 
      "email": "[email protected]", 
      "last-name": "Smith", 
      "handle": "jsmith" 
     }, 
     "id": "1234" 
    }, 
    { 
     "type": "users", 
     "attributes": { 
      "first-name": "AAA", 
      "email": "[email protected]", 
      "last-name": "XXX", 
      "handle": "jsmith" 
     }, 
     "id": "1234" 
    }, 
    { 
     "type": "users", 
     "attributes": { 
      "first-name": "John", 
      "email": "[email protected]", 
      "last-name": "Smith", 
      "handle": "jsmith" 
     }, 
     "id": "1234" 
    }, 
] 

# get indicies of unique elements 
In [254]: list({make_hash(x):i for i,x in enumerate(l)}.values()) 
Out[254]: [1, 2] 
+1

Non utilizzare hash per determinare se due oggetti sono uguali. Hai sempre il rischio di collisioni di hash. – Rob

+0

@Rob, l'implementazione 'set' non usa l'hash (implementazione di C o Cython di questa funzione) per generare chiavi" virtuali "? E credo che sia una pratica comune usare 'set (lst)' - per gli elenchi di deduplicazione ... – MaxU

+0

'set' usa effettivamente gli hash, ma solo per rendere l'implementazione più veloce. Se due oggetti hanno lo stesso hash ma valori diversi, saranno comunque separati in un 'set', ma non in questa implementazione. – Rob

0

Si potrebbe provare una versione breve che si basa sul collegamento per la risposta, che hai fornito nella domanda:

key = "id" 
deduplicated = [val for ind, val in enumerate(l) 
       if val[key] not in [tmp[key] for tmp in l[ind + 1:]]] 
print(deduplicated) 

nota, questo prenderà l'ultimo elemento dei duplicati

3

Si sta selezionando solo il primo dict nell'elenco per ogni valore distinto per key. itertools.groupby è lo strumento incorporato che può fare per voi - ordinamento e di gruppo da key e prendere solo la prima di ogni gruppo:

from itertools import groupby 

def deduplicate(lst, key): 
    fnc = lambda d: d.get(key) # more robust than d[key] 
    return [next(g) for k, g in groupby(sorted(lst, key=fnc), key=fnc)] 
+0

no, dà: 'TypeError: tipi non ordinabili: dict() MaxU

+0

Hmm .. ha funzionato per me e la lista esatta dal tuo esempio. Python 2.7.11 Ubuntu 14.04, vedo. Quindi forse non in Python 3.x. – schwobaseggl

+0

sto usando Python 3.5.1. È 'ordinato (l)' - mi dà quell'errore su Python 3 – MaxU

0

Nel tuo esempio, il valore restituito dalla chiave è hashable. Se questo è sempre il caso, quindi utilizzare questo:

def deduplicate(lst, key): 
    return list({item[key]: item for item in lst}.values()) 

Se ci sono duplicati, solo l'ultimo abbinamento duplicato viene mantenuto.

Problemi correlati