Aplanar un diccionario de diccionarios (2 niveles de profundidad) de listas en Python

Estoy tratando de envolver mi cerebro alrededor de esto, pero no es lo suficientemente flexible.

En mi script de Python tengo un diccionario de diccionarios de listas. (En realidad, se profundiza un poco, pero ese nivel no está involucrado en esta pregunta). Quiero aplanar todo esto en una larga lista, desechando todas las claves del diccionario.

Por eso quiero transformarme

{1: {'a': [1, 2, 3], 'b': [0]}, 2: {'c': [4, 5, 1], 'd': [3, 8]}} 

a

 [1, 2, 3, 0, 4, 5, 1, 3, 8] 

Probablemente podría configurar una reducción de mapa para iterar sobre los elementos del diccionario externo para crear una lista secundaria de cada subdiccionario y luego concatenar todas las listas juntas.

Pero eso parece ineficiente para grandes conjuntos de datos, debido a las estructuras de datos intermedias (sublistas) que se desecharán. ¿Hay una manera de hacerlo en una sola pasada?

Si no fuera así, me gustaría aceptar una implementación de dos niveles que funcione … ¡mi map-reduce está oxidado!

Actualización: para aquellos que estén interesados, a continuación se muestra el código que terminé usando.

Tenga en cuenta que aunque solicité anteriormente una lista como salida, lo que realmente necesitaba era una lista ordenada; es decir, la salida del aplanamiento podría ser cualquier iterable que pueda ser ordenado.

 def genSessions(d): """Given the ipDict, return an iterator that provides all the sessions, one by one, converted to tuples.""" for uaDict in d.itervalues(): for sessions in uaDict.itervalues(): for session in sessions: yield tuple(session) 

 # Flatten dict of dicts of lists of sessions into a list of sessions. # Sort that list by start time sessionsByStartTime = sorted(genSessions(ipDict), key=operator.itemgetter(0)) # Then make another copy sorted by end time. sessionsByEndTime = sorted(sessionsByStartTime, key=operator.itemgetter(1)) 

Gracias de nuevo a todos los que ayudaron.

[Actualización: reemplazé nthGetter () con operator.itemgetter (), gracias a @intuited.]

editar : vuelva a leer la pregunta original y la respuesta modificada para asumir que todos los diccionarios que no son diccionarios son aplanados.

En los casos en los que no esté seguro de cuán lejos van los diccionarios, desearía usar una función recursiva. @Arrieta ya ha publicado una función que construye recursivamente una lista de valores que no están en el diccionario.

Este es un generador que produce valores sucesivos que no están en el diccionario en el árbol de diccionarios:

 def flatten(d): """Recursively flatten dictionary values in `d`. >>> hat = {'cat': ['images/cat-in-the-hat.png'], ... 'fish': {'colours': {'red': [0xFF0000], 'blue': [0x0000FF]}, ... 'numbers': {'one': [1], 'two': [2]}}, ... 'food': {'eggs': {'green': [0x00FF00]}, ... 'ham': ['lean', 'medium', 'fat']}} >>> set_of_values = set(flatten(hat)) >>> sorted(set_of_values) [1, 2, 255, 65280, 16711680, 'fat', 'images/cat-in-the-hat.png', 'lean', 'medium'] """ try: for v in d.itervalues(): for nested_v in flatten(v): yield nested_v except AttributeError: for list_v in d: yield list_v 

El doctest pasa el iterador resultante a la función set . Es probable que esto sea lo que usted quiere, ya que, como lo señala el Sr. Martelli, no hay un orden intrínseco a los valores de un diccionario, y por lo tanto no hay razón para mantener un registro del orden en el que se encontraron.

Es posible que desee realizar un seguimiento de la cantidad de ocurrencias de cada valor; esta información se perderá si pasa el iterador para set . Si desea realizar un seguimiento de eso, simplemente pase el resultado de flatten(hat) a alguna otra función en lugar de set . Bajo Python 2.7, esa otra función podría ser collections.Counter . Para la compatibilidad con los pitones menos evolucionados, puede escribir su propia función o combinarla con itertools.groupby (con cierta pérdida de eficiencia).

Espero que te des cuenta de que cualquier orden que veas en un dictado es accidental, está ahí solo porque, cuando se muestra en la pantalla, se debe seleccionar una orden, pero no hay ninguna garantía.

Red de asuntos de orden entre los diferentes sublistas que son catenados,

 [x for d in thedict.itervalues() for alist in d.itervalues() for x in alist] 

Hace lo que quiera sin ninguna ineficiencia ni listas intermedias.

Una función recursiva puede funcionar:

 def flat(d, out=[]): for val in d.values(): if isinstance(val, dict): flat(d, out) else: out+= val 

Si lo intentas con:

 >>> d = {1: {'a': [1, 2, 3], 'b': [0]}, 2: {'c': [4, 5, 6], 'd': [3, 8]}} >>> out = [] >>> flat(d, out) >>> print out [1, 2, 3, 0, 4, 5, 6, 3, 8] 

Observe que los diccionarios no tienen orden, por lo que la lista está en orden aleatorio.

También puede return out (al final del ciclo) y no llamar a la función con un argumento de lista.

 def flat(d, out=[]): for val in d.values(): if isinstance(val, dict): flat(d, out) else: out+= val return out 

llamar como

 my_list = flat(d)