Рекурсивный поиск путей в словаре, содержащем вложенную строку

#python #recursion #re

#python #рекурсия #python-re

Вопрос:

Я пытаюсь определить самый быстрый способ поиска во вложенном словаре с помощью регулярного выражения и возврата путей к каждому вхождению этой строки. Меня интересуют только значения, которые являются строками, а не другие значения, которые могут явно не быть строками. Рекурсия не является моей сильной стороной. Вот пример JSON, допустим, я ищу все абсолютные пути, которые содержат ‘blah’.

 d = {'id': 'abcde',
 'key1': 'blah',
 'key2': 'blah blah',
 'nestedlist': [{'id': 'qwerty',
   'nestednestedlist': [{'id': 'xyz', 'keyA': 'blah blah blah'},
    {'id': 'fghi', 'keyZ': 'blah blah blah'}],
   'anothernestednestedlist': [{'id': 'asdf', 'keyQ': 'blah blah'},
    {'id': 'yuiop', 'keyW': 'blah'}]}]}
  

Я нашел следующий фрагмент кода, но мне не удалось заставить его возвращать пути, а не просто печатать их. Помимо этого, не должно быть слишком сложно добавить «ЕСЛИ значение является строкой и содержит re.search(), ТО добавьте путь к списку».

 def search_dict(v, prefix=''):
    
    if isinstance(v, dict):
        for k, v2 in v.items():
            p2 = "{}['{}']".format(prefix, k)
            search_dict(v2, p2)
    elif isinstance(v, list):
        for i, v2 in enumerate(v):
            p2 = "{}[{}]".format(prefix, i)
            search_dict(v2, p2)
    else:
        print('{} = {}'.format(prefix, repr(v)))
  

Ответ №1:

Оба ответа здесь с готовностью вычисляют результат, исчерпывая весь входной dict, прежде чем возвращать первый (если таковой имеется) доступный результат. Мы можем использовать yield from для кодирования более питоновской программы —

 def search_substr(t = {}, q = ""):
  def loop(t, path):
    if isinstance(t, dict):
      for k, v in t.items():
        yield from loop(v, (*path, k))  # <- recur
    elif isinstance(t, list):
      for k, v in enumerate(t):
        yield from loop(v, (*path, k))  # <- recur
    elif isinstance(t, str):
      if q in t:
        yield path, t                   # <- output a match
  yield from loop(t, ())                # <- init

for (path, value) in search_substr(d, "blah"):
  print(path, value)
  

Результат —

 ('key1',) blah
('key2',) blah blah
('nestedlist', 0, 'nestednestedlist', 0, 'keyA') blah blah blah
('nestedlist', 0, 'nestednestedlist', 1, 'keyZ') blah blah blah
('nestedlist', 0, 'anothernestednestedlist', 0, 'keyQ') blah blah
('nestedlist', 0, 'anothernestednestedlist', 1, 'keyW') blah
  

Обратите внимание, мы проверяем наличие подстроки q в target t с помощью q in t . Если вы действительно хотите использовать regexp для этого —

 from re import compile

def search_re(t = {}, q = ""):
  def loop(t, re, path):                      # <- add re
    if isinstance(t, dict):
      for k, v in t.items():
        yield from loop(v, re, (*path, k))    # <- carry re
    elif isinstance(t, list):
      for k, v in enumerate(t):
        yield from loop(v, re, (*path, k))    # <- carry re
    elif isinstance(t, str):
      if re.search(t):                        # <- re.search
        yield path, t
  yield from loop(t, compile(q), ())          # <- compile q
  

Теперь мы можем выполнять поиск с помощью регулярного выражения —

 for (path, value) in search_re(d, r"[abhl]{4}"):
  print(path, value)
  

Результат —

 ('key1',) blah
('key2',) blah blah
('nestedlist', 0, 'nestednestedlist', 0, 'keyA') blah blah blah
('nestedlist', 0, 'nestednestedlist', 1, 'keyZ') blah blah blah
('nestedlist', 0, 'anothernestednestedlist', 0, 'keyQ') blah blah
('nestedlist', 0, 'anothernestednestedlist', 1, 'keyW') blah
  

Давайте попробуем другой поиск, используя другой запрос —

 for (path, value) in search_re(d, r"[dfs]{3}"):
  print(path, value)
  
 ('nestedlist', 0, 'anothernestednestedlist', 0, 'id') asdf
  

Наконец, search_substr и search_re ничего не выдают, когда запрос не соответствует —

 print(list(search_re(d, r"zzz")))
# []
  

Ответ №2:

Вам просто нужно инициализировать выходной список, append элементы, найденные в текущем вызове, и extend его по результатам, возвращаемым рекурсивными вызовами.

попробуйте это:

 def search_dict(v, prefix=''):
    result = []
    if isinstance(v, dict):
        for k, v2 in v.items():
            p2 = "{}['{}']".format(prefix, k)
            result.extend(search_dict(v2, p2))
    elif isinstance(v, list):
        for i, v2 in enumerate(v):
            p2 = "{}[{}]".format(prefix, i)
            result.extend(search_dict(v2, p2))
    else:
        result.append('{} = {}'.format(prefix, repr(v)))
    return result
  

Комментарии:

1. бум, детка. вот и все.

Ответ №3:

Adam.Er8 точен, я просто хотел дать более четкий ответ на вопрос:

 def search_dict(v, re_term, prefix=''):

    re_term = re.compile(re_term)
    result = []
    if isinstance(v, dict):
        for k, v2 in v.items():
            p2 = "{}['{}']".format(prefix, k)
            result.extend(search_dict(v2, re_term, prefix = p2))
    elif isinstance(v, list):
        for i, v2 in enumerate(v):
            p2 = "{}[{}]".format(prefix, i)
            result.extend(search_dict(v2, re_term, prefix = p2))
    elif isinstance(v, str) and re.search(re_term,v):
        result.append(prefix)
    return result