2008-12-19 12 views
9

He anidados:La forma de hacerlo - los diccionarios diccionario de Python transversal y buscar

{'key0': {'attrs': {'entity': 'p', 'hash': '34nj3h43b4n3', 'id': '4130'}, 
      u'key1': {'attrs': {'entity': 'r', 
           'hash': '34njasd3h43b4n3', 
           'id': '4130-1'}, 
        u'key2': {'attrs': {'entity': 'c', 
             'hash': '34njasd3h43bdsfsd4n3', 
             'id': '4130-1-1'}}}, 
      u'key3': {'attrs': {'entity': 'r', 
           'hash': '34njasasasd3h43b4n3', 
           'id': '4130-2'}, 
        u'key4': {'attrs': {'entity': 'c', 
             'hash': '34njawersd3h43bdsfsd4n3', 
             'id': '4130-2-1'}}, 
        u'key5': {'attrs': {'entity': 'c', 
             'hash': '34njawersd3h43bdsfsd4n3', 
             'id': '4130-2-2'}}}}, 
'someohterthing': 'someothervalue', 
'something': 'somevalue'} 

les da una id - uno de todo el ids como 4130 a 4130-2-2.
¿Cuál es la forma más fácil de navegar al diccionario correcto?

Al igual que si lo dado es id4130-2-1 entonces se deberá llegar a la diccionario con key=key5

no xml se acerca por favor.

Editar (1): La anidación es entre 1 a 4 niveles, pero sé que la anidación antes de analizar.

Editar (2): Se corrigió el código.

** Editar (3): ** Se corrigió el código nuevamente para los valores de cadena de ids. Disculpe por la confusión creada. Esto es definitivo. Espero :)

+0

para '4130-2-1' que quieren 'key4', no 'key5' ¿verdad? 'key5' parece contener '4130-2-2'. –

+0

** Vea también: ** https://stackoverflow.com/questions/7681301/search-for-a-key-in-a-nested-python-dictionary https://stackoverflow.com/a/16508328/42223 – dreftymac

Respuesta

14

Tu estructura es desagradablemente irregular. Aquí hay una versión con una función Visitor que atraviesa los sub-diccionarios attrs.

def walkDict(aDict, visitor, path=()): 
    for k in aDict: 
     if k == 'attrs': 
      visitor(path, aDict[k]) 
     elif type(aDict[k]) != dict: 
      pass 
     else: 
      walkDict(aDict[k], visitor, path+(k,)) 

def printMe(path, element): 
    print path, element 

def filterFor(path, element): 
    if element['id'] == '4130-2-2': 
     print path, element 

Lo usarías así.

walkDict(myDict, filterFor) 

Esto se puede convertir en un generador en lugar de un visitante ; sería yield path, aDict[k] en lugar de invocar la función de visitante.

Lo usarías en un bucle for.

for path, attrDict in walkDictIter(aDict): 
    # process attrDict... 
+0

Tengo una gran colección de estos, si puede sugerir una mejor estructura con soporte de nivel arbitrario, facilidad de inserción y recuperación, será genial. Para cuando comprenda esa estructura, intentaré su solución. Gracias. –

+3

@JV: Los diccionarios internos "attrs" son desaconsejables. Esos candidatos por ser objetos de una clase definida, no solo diccionarios anónimos. –

+0

+1 para usar Visitor –

0

Bueno, si tiene que hacerlo solo unas pocas veces, puede usar los dict.iteritems anidados() para encontrar lo que está buscando.

Si planea hacerlo varias veces, las actuaciones se convertirán rápidamente en un problema. En ese caso, puede:

  • cambiar la forma en que sus datos le son devueltos a algo más adecuado.

  • si no puede, convierta los datos una vez que vuela a un dict entre id y keys (usando iteritems). Entonces úsalo.

+0

la idea cuando creamos esta estructura fue para acceder a ella a través de claves, como - key1, key2, etc. Ahora tropecé con un requisito para acceder a los ID. El segundo punto es una buena sugerencia, lo intentaré. –

12

Si se quiere resolver el problema de una manera general, no importa cuántas nivel de anidamiento que tiene en su dict, a continuación, crear una función recursiva que recorrer el árbol:

def traverse_tree(dictionary, id=None): 
    for key, value in dictionary.items(): 
     if key == 'id': 
      if value == id: 
       print dictionary 
     else: 
      traverse_tree(value, id) 
    return 

>>> traverse_tree({1: {'id': 2}, 2: {'id': 3}}, id=2) 
{'id': 2} 
+0

Esto no funciona cuando lo pruebo en mi máquina. – PEZ

+0

He reparado el código de ejemplo en cuestión, por favor, vuelva a revisar –

+0

Le he votado, no sé cómo seleccionar 2 respuestas; de lo contrario, también habría seleccionado este. :) –

9

Este tipo de problema a menudo se resuelve mejor con las definiciones de clase adecuadas, no diccionarios genéricos.

class ProperObject(object): 
    """A proper class definition for each "attr" dictionary.""" 
    def __init__(self, path, attrDict): 
     self.path= path 
     self.__dict__.update(attrDict) 
    def __str__(self): 
     return "path %r, entity %r, hash %r, id %r" % (
      self.path, self.entity, self.hash, self.id) 

masterDict= {} 
def builder(path, element): 
    masterDict[path]= ProperObject(path, element) 

# Use the Visitor to build ProperObjects for each "attr" 
walkDict(myDict, builder) 

# Now that we have a simple dictionary of Proper Objects, things are simple 
for k,v in masterDict.items(): 
    if v.id == '4130-2-2': 
     print v 

Además, ahora que tiene las definiciones adecuadas de objetos, puede hacer lo siguiente

# Create an "index" of your ProperObjects 
import collections 
byId= collections.defaultdict(list) 
for k in masterDict: 
    byId[masterDict[k].id].append(masterDict[k]) 

# Look up a particular item in the index 
print map(str, byId['4130-2-2']) 
+0

Si realiza muchas búsquedas, el costo de transformación a Objects y luego a un índice en 'id' se amortiza en las búsquedas. La construcción de los objetos es O (n). La construcción del índice es O (n) y se puede hacer a medida que se construyen los objetos. La búsqueda en id es O (1). –

4

Ésta es una cuestión de edad, pero todavía un resultado google superior, así que voy a actualizar:

Un amigo y yo publicamos una biblioteca para resolver (casi) este problema exacto. dpath-python (sin relación con el módulo perl dpath que hace cosas similares).

http://github.com/akesterson/dpath-python

Todo lo que tendría que hacer es algo como esto:

$ easy_install dpath 
>>> import dpath.util 
>>> results = [] 
>>> for (path, value) in dpath.util.search(my_dictionary, "*/attrs/entity/4130*", yielded=True): 
>>> ... parent = dpath.util.search("/".join(path.split("/")[:-2]) 
>>> ... results.append(parent) 

... que le daría una lista de todos los objetos del diccionario de sus criterios, es decir, toda la objetos que tenían (clave = 4130 *). El bit padre es un poco tonto, pero funcionaría.

+0

Esta es una gran biblioteca. Esto merece mucha más atención. – dreftymac

1

Desde recursión se sabe que es limitado en pitón (ver What is the maximum recursion depth in Python, and how to increase it?) yo preferiría tener una respuesta basada bucle a esta pregunta, por lo que la respuesta puede ser adaptado a cualquier nivel de profundidad en el diccionario. Por eso, la función

def walkDict(aDict, visitor, path=()): 
    for k in aDict: 
     if k == 'attrs': 
      visitor(path, aDict[k]) 
     elif type(aDict[k]) != dict: 
      pass 
     else: 
      walkDict(aDict[k], visitor, path+(k,)) 

se puede sustituir por:

def walkDictLoop(aDict, visitor, path=()): 
    toProcess = [(aDict, path)] 
    while toProcess: 
     dictNode, pathNode = toProcess.pop(0) 
     for k in dictNode: 
      if k == 'attrs': 
       visitor(pathNode, dictNode[k]) 
      if isinstance(dictNode[k], dict): 
       toProcess.append((dictNode[k], pathNode+(k,))) 
Cuestiones relacionadas