2011-10-12 9 views
7

Estoy casi seguro de que hay una solución simple para esto, pero he pasado horas leyendo y releyendo el mismo conjunto de resultados relacionados que don No responderé mi problema.Caminando/iterando sobre un diccionario anidado de profundidad arbitraria (el diccionario representa un árbol de directorio)

contexto de esta pregunta (incluido para la terminación pero no dude en saltarse este)

Esto ocurrió porque quiero que un usuario sea capaz de seleccionar un grupo de archivos dentro de un directorio (y también cualquier subdirectorio), y desafortunadamente la capacidad predeterminada de Tkinter para seleccionar múltiples archivos en un diálogo de archivo está quebrada en Windows 7 (http://bugs.python.org/issue8010).

Por lo tanto, estoy tratando de representar una estructura de directorio por un método alternativo (aún usando Tkinter): construir un facsímil de la estructura de directorios, hecho de casillas marcadas e indentadas (organizadas en un árbol). Así, un directorio de la siguiente manera:

\SomeRootDirectory 
    \foo.txt 
    \bar.txt 
    \Stories 
     \Horror 
      \rickscott.txt 
      \Trash 
       \notscary.txt 
     \Cyberpunk 
    \Poems 
     \doyoureadme.txt 

se verá algo como esto (donde # representa un botón de verificación):

SomeRootDirectory 
    # foo.txt 
    # bar.txt 
    Stories 
     Horror 
      # rickscott.txt 
      Trash 
       # notscary.txt 
     Cyberpunk 
    Poems 
     # doyoureadme.txt 

Construyendo el diccionario original de la estructura de directorios es fácil usando una cierta receta que encontré en ActiveState (ver a continuación), pero toco una pared cuando trato de iterar sobre el diccionario muy bien anidado que me queda. Y creo que necesito iterar sobre eso para llenar un cuadro Tkinter con una representación cuadriculada bonita del árbol. Luego espero cargar los diversos archivos de texto seleccionados por el usuario, interpretando qué casillas de verificación son verdaderas o falsas. Todo parece bastante fácil, excepto iterar sobre el diccionario sin para fijar la profundidad.

En términos más abstractos

Para hacer estos diccionarios anidados que estoy usando una receta ActiveState - http://code.activestate.com/recipes/577879/. Implementa os.walk para hacer diccionarios como este:

a={ 
    'SomeRootDirectory': { 
     'foo.txt': None, 
     'bar.txt': None, 
     'Stories': { 
      'Horror': { 
       'rickscott.txt' : None, 
       'Trash' : { 
        'notscary.txt' : None, 
        }, 
       }, 
      'Cyberpunk' : None 
      }, 
     'Poems' : { 
      'doyoureadme.txt' : None 
     } 
    } 
} 

Después de lo cual estoy perplejo. Soy un novato de Python y un mero especialista en humanidades ... que en mi caso significa que la recursividad es muy confusa para mí. Así que miré y busqué recetas e intenté todo tipo de modificaciones de respuestas similares, pero fue en vano. Necesito poder iterar sobre este diccionario para completar otra representación de él, y también para reconstruir las referencias a los archivos después de hacer eso (es decir, después de que el usuario haya seleccionado qué archivos).

¡Mis disculpas si esto es demasiado detallado! ¡Gracias por tu ayuda!

solución adaptada de la respuesta de spicavigo

#distinguish between directory and file 
dirtab = "/===" 
filetab = "|---" 

Parents={-1:"Root"} 
def add_dir(level, parent, index, k): 
    print (dirtab*level)+k 
def add_file(level, parent, index, k): 
    #Currently an empty folder gets passed to add_file; here's a quick treatment. 
    if "." in k: 
     print (filetab*level)+k 
    else: 
     print (dirtab*level)+k 
def addemup(level=0, parent=-1, index=0, di={}): 
    for k in di: 
     index +=1 
     if di[k]: 
      Parents[index]=k 
      add_dir(level, parent, index, k) 
      addemup(level+1, index, index, di[k]) 
     else: 
      add_file(level, parent, index, k) 

addemup(di=a) #dictionary from above 

Esto produce algo que creo que va a ser muy fácil de revisar en una representación Tkinter:

SomeRootDirectory 
/===Poems 
|---|---doyoureadme.txt 
/===Stories 
/===/===Horror 
|---|---|---rickscott.txt 
/===/===/===Trash 
|---|---|---|---notscary.txt 
/===/===Cyberpunk 
|---foo.txt 
|---bar.txt 

Gracias, esta comunidad es increíble.

+5

cuando iterar sobre el diccionario pares de valores clave (dentro de una función que toma como argumento el diccionario), se puede comprobar si el valor es un tipo de diccionario, si es sí, entonces llame a su función de nuevo, es decir, usa recursividad aquí y pasa el valor como diccionario a la función, de lo contrario procesa el valor ... esto debería resolver el problema de iteración de profundidad variable – avasal

Respuesta

4

Este es un código preliminar. Repase y dígame dónde enfrenta problemas.

Parents={-1:"Root"} 
def add_dir(level, parent, index, k): 
    print "Directory" 
    print "Level=%d, Parent=%s, Index=%d, value=%s" % (level, Parents[parent], index, k) 
def add_file(parent, index, k): 
    print "File" 
    print "Parent=%s, Index=%d, value=%s" % (Parents[parent], index, k) 
def f(level=0, parent=-1, index=0, di={}): 
    for k in di: 
     index +=1 
     if di[k]: 
      Parents[index]=k 
      add_dir(level, parent, index, k) 
      f(level+1, index, index, di[k]) 
     else: 
      add_file(parent, index, k) 

a={ 
    'SomeRootDirectory': { 
     'foo.txt': None, 
     'bar.txt': None, 
     'Stories': { 
      'Horror': { 
       'rickscott.txt' : None, 
       'Trash' : { 
        'notscary.txt' : None, 
        }, 
       }, 
      'Cyberpunk' : None 
      }, 
     'Poems' : { 
      'doyoureadme.txt' : None 
     } 
    } 
} 

f(di=a) 
+0

Guau, eso funciona maravillosamente. Actualicé mi pregunta para incluir mi solución (una pequeña adaptación suya que, básicamente, está lista para Tkinter, creo). – hangtwenty

9

Aquí hay una función que imprime todos los nombres de sus archivos. Pasa por todas las claves del diccionario y, si se asignan a elementos que no son diccionarios (en su caso, el nombre del archivo), imprimimos el nombre. De lo contrario, llamamos a la función en el diccionario que está mapeado.

def print_all_files(directory): 

    for filename in directory.keys(): 
     if not isinstance(directory[filename], dict): 
      print filename 
     else: 
      print_all_files(directory[filename]) 

lo que este código puede ser modificado para hacer lo que quiera, pero es sólo un ejemplo de cómo se puede evitar la fijación de la profundidad a través de uso de la recursividad.

La clave para entender es que cada vez que se llama a print_all_files, no tiene conocimiento de cuán profundo es en el árbol. Simplemente mira los archivos que están justo ahí e imprime los nombres. Si hay directores, simplemente se ejecuta en ellos.

2

Me doy cuenta de que esta es una vieja pregunta, pero solo estaba buscando una forma simple y limpia de caminar dicts anidados y esto es lo más parecido a mi búsqueda limitada. La respuesta de oadams no es lo suficientemente útil si quieres algo más que nombres de archivos y la respuesta de spicavigo parece complicada.

Acabo de hacer mi propio truco que actúa de manera similar a cómo os.walk trata a los directores, excepto que devuelve toda la información clave/valor.

devuelve un iterador y para cada directorio en el "árbol" de dicts anidados, el iterador devuelve (trayectoria, sub-Dicts, valores) donde:

  • ruta es la ruta a la dict
  • sub-dicts es una tupla de (, dict clave) pares para cada sub-dict en este dict
  • valores es una tupla de (clave, valor) pares para cada uno (no dict) objeto en esta dict


def walk(d): 
    ''' 
    Walk a tree (nested dicts). 

    For each 'path', or dict, in the tree, returns a 3-tuple containing: 
    (path, sub-dicts, values) 

    where: 
    * path is the path to the dict 
    * sub-dicts is a tuple of (key,dict) pairs for each sub-dict in this dict 
    * values is a tuple of (key,value) pairs for each (non-dict) item in this dict 
    ''' 
    # nested dict keys 
    nested_keys = tuple(k for k in d.keys() if isinstance(d[k],dict)) 
    # key/value pairs for non-dicts 
    items = tuple((k,d[k]) for k in d.keys() if k not in nested_keys) 

    # return path, key/sub-dict pairs, and key/value pairs 
    yield ('/', [(k,d[k]) for k in nested_keys], items) 

    # recurse each subdict 
    for k in nested_keys: 
     for res in walk(d[k]): 
      # for each result, stick key in path and pass on 
      res = ('/%s' % k + res[0], res[1], res[2]) 
      yield res 

Aquí está el código que utiliza para probar que, aunque no tiene pareja aa otras cosas no relacionado (pero limpio) en ella:

import simplejson as json 
from collections import defaultdict 

# see https://gist.github.com/2012250 
tree = lambda: defaultdict(tree) 

def walk(d): 
    ''' 
    Walk a tree (nested dicts). 

    For each 'path', or dict, in the tree, returns a 3-tuple containing: 
    (path, sub-dicts, values) 

    where: 
    * path is the path to the dict 
    * sub-dicts is a tuple of (key,dict) pairs for each sub-dict in this dict 
    * values is a tuple of (key,value) pairs for each (non-dict) item in this dict 
    ''' 
    # nested dict keys 
    nested_keys = tuple(k for k in d.keys() if isinstance(d[k],dict)) 
    # key/value pairs for non-dicts 
    items = tuple((k,d[k]) for k in d.keys() if k not in nested_keys) 

    # return path, key/sub-dict pairs, and key/value pairs 
    yield ('/', [(k,d[k]) for k in nested_keys], items) 

    # recurse each subdict 
    for k in nested_keys: 
     for res in walk(d[k]): 
      # for each result, stick key in path and pass on 
      res = ('/%s' % k + res[0], res[1], res[2]) 
      yield res 

# use fancy tree to store arbitrary nested paths/values 
mem = tree() 

root = mem['SomeRootDirectory'] 
root['foo.txt'] = None 
root['bar.txt'] = None 
root['Stories']['Horror']['rickscott.txt'] = None 
root['Stories']['Horror']['Trash']['notscary.txt'] = None 
root['Stories']['Cyberpunk'] 
root['Poems']['doyoureadme.txt'] = None 

# convert to json string 
s = json.dumps(mem, indent=2) 

#print mem 
print s 
print 

# json.loads converts to nested dicts, need to walk them 
for (path, dicts, items) in walk(json.loads(s)): 
    # this will print every path 
    print '[%s]' % path 
    for key,val in items: 
     # this will print every key,value pair (skips empty paths) 
     print '%s = %s' % (path+key,val) 
    print 

La salida será similar a:

{ 
    "SomeRootDirectory": { 
    "foo.txt": null, 
    "Stories": { 
     "Horror": { 
     "rickscott.txt": null, 
     "Trash": { 
      "notscary.txt": null 
     } 
     }, 
     "Cyberpunk": {} 
    }, 
    "Poems": { 
     "doyoureadme.txt": null 
    }, 
    "bar.txt": null 
    } 
} 

[/] 

[/SomeRootDirectory/] 
/SomeRootDirectory/foo.txt = None 
/SomeRootDirectory/bar.txt = None 

[/SomeRootDirectory/Stories/] 

[/SomeRootDirectory/Stories/Horror/] 
/SomeRootDirectory/Stories/Horror/rickscott.txt = None 

[/SomeRootDirectory/Stories/Horror/Trash/] 
/SomeRootDirectory/Stories/Horror/Trash/notscary.txt = None 

[/SomeRootDirectory/Stories/Cyberpunk/] 

[/SomeRootDirectory/Poems/] 
/SomeRootDirectory/Poems/doyoureadme.txt = None 
0

Puede recorrer un diccionario anidado mediante la recursión

def walk_dict(dictionary): 
    for key in dictionary: 
     if isinstance(dictionary[key], dict): 
      walk_dict(dictionary[key]) 
     else: 
      #do something with dictionary[k] 
      pass 

Espero que ayude :)

0
a={ 
    'SomeRootDirectory': { 
     'foo.txt': None, 
     'bar.txt': None, 
     'Stories': { 
      'Horror': { 
       'rickscott.txt' : None, 
       'Trash' : { 
        'notscary.txt' : None, 
        }, 
       }, 
      'Cyberpunk' : None 
      }, 
     'Poems' : { 
      'doyoureadme.txt' : None 
     } 
    } 
} 

def dict_paths(dictionary, level=0, parents=[], paths=[]): 
    for key in dictionary: 
    parents = parents[0:level] 
    paths.append(parents + [key]) 
    if dictionary[key]: 
     parents.append(key) 
     dict_paths(dictionary[key], level+1, parents, paths) 
    return paths 

dp = dict_paths(a) 
for p in dp: 
    print '/'.join(p) 
+0

¡Una explicación sería agradable! – gsamaras

Cuestiones relacionadas