2012-06-30 4 views
6

Supponiamo di avere una lunga lista di parole. Per un esempio:Aggiungere a un gruppo di elenchi con una comprensione del ditt.

>>> with open('/usr/share/dict/words') as f: 
...  words=[word for word in f.read().split('\n') if word] 

Se ho voluto costruire un indice per iniziale di questo elenco di parole, questo è facile:

d={} 
for word in words: 
    if word[0].lower() in 'aeiou': 
     d.setdefault(word[0].lower(),[]).append(word) 
     # You could use defaultdict here too... 

Risultati in qualcosa di simile:

{'a':[list of 'a' words], 'e':[list of 'e' words], 'i': etc...} 

C'è un modo per farlo con la comprensione di Python 2.7, 3+ dict? In altre parole, è possibile con la sintassi di comprensione del dict aggiungere la lista rappresentata dalla chiave mentre il dict viene costruito?

cioè:

index={k[0].lower():XXX for k in words if k[0].lower() in 'aeiou'} 

dove XXX esegue un'operazione di aggiunta o la creazione della lista per la chiave, come si sta creando index.

Modifica

Prendendo i suggerimenti e le analisi comparativa:

def f1(): 
    d={} 
    for word in words: 
     c=word[0].lower() 
     if c in 'aeiou': 
      d.setdefault(c,[]).append(word) 

def f2(): 
    d={} 
    {d.setdefault(word[0].lower(),[]).append(word) for word in words 
     if word[0].lower() in 'aeiou'} 

def f3(): 
    d=defaultdict(list)      
    {d[word[0].lower()].append(word) for word in words 
      if word[0].lower() in 'aeiou'}   

def f4(): 
    d=functools.reduce(lambda d, w: d.setdefault(w[0], []).append(w[1]) or d, 
     ((w[0].lower(), w) for w in words 
     if w[0].lower() in 'aeiou'), {}) 

def f5(): 
    d=defaultdict(list) 
    for word in words: 
     c=word[0].lower() 
     if c in 'aeiou': 
      d[c].append(word)  

Produce questo benchmark:

rate/sec f4  f2  f1  f3  f5 
f4  11 -- -21.8% -31.1% -31.2% -41.2% 
f2  14 27.8%  -- -11.9% -12.1% -24.8% 
f1  16 45.1% 13.5%  -- -0.2% -14.7% 
f3  16 45.4% 13.8% 0.2%  -- -14.5% 
f5  18 70.0% 33.0% 17.2% 16.9%  -- 

Il ciclo dritto con un dict di default è più veloce seguito da comprensione set e ciclo con setdefault.

Grazie per le idee!

+1

meno che non sei in codice golf si prega di non scrivere codice illeggibile per brevità. [* Contabilità. *] (Http://www.python.org/dev/peps/pep-0020/) – ThiefMaster

+1

'{w [0]: [ww per ww in words if ww.startswith (w [0])] per w in words} ' – astynax

+0

@astynax: Intelligente, ma molto lento –

risposta

3

non è possibile (almeno facilmente o direttamente) con la comprensione dict.

E 'possibile, ma potenzialmente abusiva della sintassi, con un set o una lista di comprensione:

# your code:  
d={} 
for word in words: 
    if word[0].lower() in 'aeiou': 
     d.setdefault(word[0].lower(),[]).append(word)   

# a side effect set comprehension: 
index={} 
r={index.setdefault(word[0].lower(),[]).append(word) for word in words 
     if word[0].lower() in 'aeiou'}  

print r 
print [(k, len(d[k])) for k in sorted(d.keys())] 
print [(k, len(index[k])) for k in sorted(index.keys())] 

Stampe:

set([None]) 
[('a', 17094), ('e', 8734), ('i', 8797), ('o', 7847), ('u', 16385)] 
[('a', 17094), ('e', 8734), ('i', 8797), ('o', 7847), ('u', 16385)] 

La comprensione insieme produce un insieme con i risultati del setdefault() metodo dopo l'iterazione sull'elenco words. La somma totale di set([None]) in questo caso. Produce anche il tuo effetto collaterale desiderato nel produrre il tuo dettato di liste.

Non è leggibile (IMHO) come il costrutto di looping diritto e deve essere evitato (IMHO). Non è più breve e probabilmente non è materialmente più veloce. Questa è una curiosità più interessante su Python che utile - IMHO ... Forse per vincere una scommessa?

+0

Grazie per avermi mostrato esattamente perché non dovrei farlo! – thnee

9

Le no - dict comprehensions sono progettate per generare chiavi non sovrapposte ad ogni iterazione; non supportano l'aggregazione. Per questo particolare caso d'uso, un ciclo è il modo corretto per eseguire l'attività in modo efficiente (in tempo lineare).

3

userei filter:

>>> words=['abcd','abdef','eft','egg','uck','ice'] 
>>> index={k.lower():list(filter(lambda x:x[0].lower()==k.lower(),words)) for k in 'aeiou'} 
>>> index 
{'a': ['abcd', 'abdef'], 'i': ['ice'], 'e': ['eft', 'egg'], 'u': ['uck'], 'o': []} 
+0

Questo si interromperà se 'words' è un iteratore. – Amber

+1

ma OP sta prendendo un elenco di parole, che è un 'iterable'. –

+0

Certo - ma ci sono anche altri aspetti negativi di questo approccio (ad esempio quintuplicando il tempo necessario per l'esecuzione - a seconda di quanto grande è l'input, i fattori costanti * sono * a volte evidenti.) È anche meno leggibile di un semplice 'for' loop – Amber

1

Questa non è esattamente la comprensione dict, ma:

reduce(lambda d, w: d.setdefault(w[0], []).append(w[1]) or d, 
     ((w[0].lower(), w) for w in words 
     if w[0].lower() in 'aeiou'), {}) 
Problemi correlati