Słownik: Jak wymienić każdą ścieżkę klucza, który zawiera pewną wartość?

głosy
2

Powiedzmy mam słownika w postaci:

d={'geo': {'bgcolor': 'white','lakecolor': 'white','caxis': {'gridcolor': 'white', 'linecolor': 'white',}},
    'title': {'x': 0.05},
    'yaxis': {'automargin': True,'linecolor': 'white','ticks': '','zerolinecolor': 'white','zerolinewidth': 2}
  }

Jak można pracować na swój sposób przez tego dict i zrobić listę każdej pełnej ścieżki klucz zawierający wartość 'white'? Korzystanie z funkcji zdefiniowanej przez użytkownika w JFS postu poszukiwanie wartości w słowniku zagnieżdżonych Pythona pozwala sprawdzić, czy nie 'white'występuje co najmniej jeden raz, a także zwraca ścieżkę:

# in:
def getpath(nested_dict, value, prepath=()):
    for k, v in nested_dict.items():
        path = prepath + (k,)
        if v == value: # found value
            return path
        elif hasattr(v, 'items'): # v is a dict
            p = getpath(v, value, path) # recursive call
            if p is not None:
                return p
getpath(d,'white')

# out:
('geo', 'bgcolor')

Ale „biały” występuje inne miejsca też, tak jak w:

1. d['geo']['lakecolor']

2: d['geo']['caxis']['gridcolor']

3: d['yaxis']['linecolor']

Jak mogę się upewnić, że funkcja wyszukuje wszystkie ścieżki?

Próbowałem stosując funkcję wyżej, aż powróci noneeliminując jednocześnie odnaleziono ścieżki jeden po drugim, ale szybko okazało się brzydkiego bałagan.

Dziękuję za wszelkie sugestie!

Utwórz 02/12/2019 o 23:54
źródło użytkownik
W innych językach...                            


3 odpowiedzi

głosy
1

Wracając to, co sprawia, że wynik niekompletne. Zamiast wracać, użyj osobną listę do śledzenia ścieżki. Używam listę cur_listtutaj i odsyłając go na samym końcu pętli:

d = {
  'geo': {'bgcolor': 'white',
         'caxis': {'gridcolor': 'white', 'linecolor': 'white'},
         'lakecolor': 'white'},
  'title': {'x': 0.05},
  'yaxis': {'automargin': True,
           'linecolor': 'white',
           'ticks': '',
           'zerolinecolor': 'white',
           'zerolinewidth': 2}
}

cur_list = []

def getpath(nested_dict, value, prepath=()):
    for k, v in nested_dict.items():
        path = prepath + (k,)
        if v == value: # found value
            cur_list.append(path)
        elif isinstance(v, dict): # v is a dict
            p = getpath(v, value, path, cur_list) # recursive call
            if p is not None:
                cur_list.append(p)

getpath(d,'white')
print(cur_list)


# RESULT:
# [('geo', 'bgcolor'), ('geo', 'caxis', 'gridcolor'), ('geo', 'caxis', 'linecolor'), ('geo', 'lakecolor'), ('yaxis', 'linecolor'), ('yaxis', 'zerolinecolor')]
Odpowiedział 03/12/2019 o 00:00
źródło użytkownik

głosy
1

tylko przekształcić funkcję tak to zwraca listi nie zrobić return, gdy coś zostanie znaleziony. Wystarczy dodać do / rozszerzenie listy

def getpath(nested_dict, value, prepath=()):
    p = []
    for k, v in nested_dict.items():
        path = prepath + (k,)
        if v == value: # found value
            p.append(path)
        elif hasattr(v, 'items'): # v is a dict
            p += getpath(v, value, path) # recursive call
    return p

z danych wejściowych, to produkuje (kolejność może się różnić w zależności od wersji Pythona, gdzie słowniki są nieuporządkowane):

[('yaxis', 'linecolor'), ('yaxis', 'zerolinecolor'), ('geo', 'lakecolor'), 
('geo', 'caxis', 'linecolor'), ('geo', 'caxis', 'gridcolor'), ('geo', 'bgcolor')]
Odpowiedział 03/12/2019 o 00:00
źródło użytkownik

głosy
5

Jest to idealny przypadek użycia napisać generator:

def find_paths(haystack, needle):
    if haystack == needle:
        yield ()
    if not isinstance(haystack, dict):
        return
    for key, val in haystack.items():
        for subpath in find_paths(val, needle):
            yield (key, *subpath)

Można go używać w następujący sposób:

d = {
    'geo': {'bgcolor': 'white','lakecolor': 'white','caxis': {'gridcolor': 'white', 'linecolor': 'white',}},
    'title': {'x': 0.05},
    'yaxis': {'automargin': True,'linecolor': 'white','ticks': '','zerolinecolor': 'white','zerolinewidth': 2}
}

# you can iterate over the paths directly...
for path in find_paths(d, 'white'):
    print('found at path: ', path)

# ...or you can collect them into a list:
paths = list(find_paths(d, 'white'))
print('found at paths: ' + repr(paths))

Podejście generator ma tę zaletę, że nie potrzeba do utworzenia obiektu, aby wszystkie ścieżki w pamięci na raz; można je obróbce jeden po drugim, a natychmiast odrzucane. W tym przypadku oszczędności pamięci byłoby raczej skromne, ale w innych mogą być znaczące. Ponadto, jeśli iteracji pętli nad generatora jest zakończona wcześniej, generator nie zamierza zachować poszukiwania większej liczby ścieżek, które byłyby później wyrzucone w każdym razie.

Odpowiedział 03/12/2019 o 00:18
źródło użytkownik

Cookies help us deliver our services. By using our services, you agree to our use of cookies. Learn more