2012-06-30 21 views
6

Supongamos que tengo una larga lista de palabras. Por ejemplo:Agregar a un dict de listas con una comprensión dict

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

Si quería construir un índice por la primera letra de esta lista de palabras, esto es fácil:

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

Resultados en algo como esto:

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

¿Hay alguna manera de hacer esto con Python 2.7, 3+ comprensión del dict? En otras palabras, ¿es posible con la sintaxis de comprensión dict anexar la lista representada por la clave a medida que se construye el dict?

es decir:

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

donde XXX realiza una operación append o creación de la lista para la tecla de como se está creando index.

Editar

Tomando las sugerencias y la evaluación 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 este punto de referencia:

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%  -- 

El bucle recta con un diccionario por defecto es más rápido seguido por la comprensión set y bucle con setdefault.

Gracias por las ideas!

+1

A menos que sea de código de golf por favor no escribir código ilegible en aras de la brevedad. [* La legibilidad cuenta. *] (Http://www.python.org/dev/peps/pep-0020/) – ThiefMaster

+1

'{w [0]: [ww para ww en palabras si ww.startswith (w [0])] para w en palabras} ' – astynax

+0

@astynax: Inteligente, pero realmente lento –

Respuesta

3

No es posible (al menos fácilmente o directamente) con una comprensión dict.

Es posible, pero potencialmente abusivo de la sintaxis, con un conjunto o lista por comprensión:

# 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())] 

Lienzo:

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

La comprensión conjunto produce un conjunto con los resultados de la setdefault() método después de iterar sobre la lista words. La suma total de set([None]) en este caso. También produce el efecto secundario deseado de producir su dict de listas.

No es tan legible (en mi humilde opinión) como la construcción de bucle recto y debe evitarse (en mi humilde opinión). No es más corto y probablemente no materialmente más rápido. Esta es una trivia más interesante sobre Python que útil - en mi humilde opinión ... ¿Tal vez para ganar una apuesta?

+0

Gracias por mostrarme exactamente por qué no debería estar haciendo esto! – thnee

9

Las comprensiones no dictadas están diseñadas para generar claves no superpuestas con cada iteración; no son compatibles con la agregación. Para este caso de uso particular, un bucle es la forma correcta de realizar la tarea de manera eficiente (en tiempo lineal).

3

que haría uso de 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

Esto se romperá si 'words' es un iterador. – Amber

+1

pero OP toma una lista de palabras, que es un 'iterable'. –

+0

Claro, pero también hay otras desventajas en este enfoque (por ejemplo, quintuplicar el tiempo que demora la ejecución: dependiendo de cuán grande sea la entrada, los factores constantes * son * a veces perceptibles. También es menos legible que el simple uso de un 'for' – Amber

1

Esto no es exactamente una comprensión dict, pero:

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'), {}) 
Cuestiones relacionadas