#python #json #nested
Вопрос:
Я хотел бы извлечь путь ключей, значение которого-Уолли, из программы
arr = []
sub_arr = []
def extract(obj, sub_arr, val):
if isinstance(obj, dict):
for k, v in obj.items():
if isinstance(v, (dict, list)):
sub_arr.append(k)
extract(v, sub_arr, val)
elif v == val:
sub_arr.append(k)
arr.append(sub_arr)
sub_arr = []
elif isinstance(obj, list):
for item in obj:
if isinstance(item, (dict, list)):
sub_arr.append(obj.index(item))
extract(item, sub_arr, val)
elif item == val:
sub_arr.append(obj.index(item))
arr.append(sub_arr)
sub_arr = []
return arr
obj = {
"News": [
{
"Title": "NewsA",
"Tags": ["Gossiping"],
"Date": "2021/06/26",
"Detail": {
"Author": "Wally",
"Content": "Hello World"
}
},
{
"Title": "NewsB",
"Tags": ["Social", "Wally"],
"Date": "2021/06/27",
"Detail": {
"Author": "Andy",
"Content": "Taiwan NO.1"
}
}
]
}
print(extract(obj, sub_arr, "Wally"))
Это лучший результат, который у меня пока есть
[
['News', 0, 'Tags', 'Detail', 'Author', 1, 'Tags', 1, 'Detail']
, ['News', 0, 'Tags', 'Detail', 'Author', 1, 'Tags', 1, 'Detail']
]
Моя желаемая ценность была бы такой
[['News', 0, 'Detail', 'Author'], ['News', 1, 'Tags', 1]]
Довольно сильно застрял прямо здесь. Есть ли что-то, что я пропустил? Был бы признателен за небольшую помощь
Комментарии:
1. Ценность-это Уолли — Какая ценность ? Там есть Автор и теги с Уолли в качестве значения
2. Но мне нужен корень ключа, например [«Новости», 0, «Подробности», «Автор»] и [«Новости», 1, «Теги», 1]
Ответ №1:
Списки изменчивы и передаются для каждой итерации вашей функции извлечения. В вашем случае sub_arr добавляется бесконечно, что объясняет полученный вами ответ. Поэтому всегда будьте осторожны при использовании списков таким образом.
Решением является создание нового списка для каждого вызова функции извлечения, например:
arr = []
sub_arr = []
def extract(obj, sub_arr, val):
if isinstance(obj, dict):
for k, v in obj.items():
found_arr = [*sub_arr, k]
if isinstance(v, (dict, list)):
extract(v, found_arr, val)
elif v == val:
arr.append(found_arr)
elif isinstance(obj, list):
for item in obj:
found_arr = [*sub_arr, obj.index(item)]
if isinstance(item, (dict, list)):
extract(item, found_arr, val)
elif item == val:
arr.append(found_arr)
return arr
obj = {
"News": [
{
"Title": "NewsA",
"Tags": ["Gossiping"],
"Date": "2021/06/26",
"Detail": {
"Author": "Wally",
"Content": "Hello World"
}
},
{
"Title": "NewsB",
"Tags": ["Social", "Wally"],
"Date": "2021/06/27",
"Detail": {
"Author": "Andy",
"Content": "Taiwan NO.1"
}
}
]
}
print(extract(obj, sub_arr, "Wally"))
Что дает желаемый ответ:
[['News', 0, 'Detail', 'Author'], ['News', 1, 'Tags', 1]]
Комментарии:
1. Приятно, мне это легче понять, спасибо за большую помощь!
Ответ №2:
Самый большой недостаток-это повторное использование sub_arr
. Любой рекурсивный механизм для структурирования этой программы должен каким-то образом интернировать или копировать частичные ключевые пути по мере ее спуска. Используя один и тот же объект, вы получите примерно одинаковый результат для каждой части пути (как вы видите в своем текущем решении, где оба ключевых пути идентичны).
В подобных программах мне также нравится создавать разные значения для своих входных данных как можно раньше, а не разветвлять поведение программы на основе типа входных данных. Я нахожу, что это легче понять и изменить. В частности, обратите внимание , как расширяется функция верхнего уровня вашего решения isinstance(obj, ...)
, в то время как в моем решении мы можем превратить как списки, так и диктанты в своего рода повторяющиеся пары вещей for k, v in items(obj)
.
def items(obj):
if isinstance(obj, dict):
return obj.items()
if isinstance(obj, list):
return enumerate(obj)
def _extract(obj, target):
# base case
if not items(obj):
if obj == target:
yield []
# recurse
else:
for k, v in items(obj):
for L in _extract(v, target):
# could use `yield [k, L]` here and
# list(map(unpack, _extract(obj, target))) below
# to avoid quadratic copying for long key paths
yield [k, *L]
def extract(obj, target):
return list(_extract(obj, target))
# if you opt for `yield [k, L]` solution
def unpack(L):
result = []
while L:
k, L = L
result.append(k)
return result
Как бы в стороне, это может быть полезной идеей при изучении неизвестных ответов API и тому подобного. Реструктурируйте его, чтобы использовать предикат соответствующей функции, а не просто какое-то значение, которое вы ищете, и оно станет намного более универсальным.
def _extract(obj, matches):
if not items(obj):
if matches(obj):
yield []
...
# existing behavior
extract(obj, lambda x: x=='Wally')
# just a utility function
def false_on_error(f):
def _f(*a, **k):
try:
return f(*a, **k)
except:
return False
return _f
# find the keys giving values that kind of look like an email
import re
prog = re.compile(r'@.*?.')
extract(obj, false_on_error(lambda x: prog.search(x)))
# you saw a value on the site and want to know if anything
# kind of looks like it
#
# bad practice to depend on `or` short-circuiting like this
# to prevent errors...
extract(obj, false_on_error(lambda x: x == 98.6 or '98.6' in x))
Комментарии:
1. Отлично, это очень помогает! Премного благодарен
Ответ №3:
В вашем коде
if isinstance(v, (dict, list)):
arr.append(k)
вы добавляли k
каждый раз, когда получали что-то новое dict
или list
результат был неправильным.
Кроме того, прохождение sub_arr
через множество слоев усложнило управление вашим кодом.
У меня есть некоторые изменения логики в соответствии с вашей базой кода.
def extract(obj, val):
result = []
if isinstance(obj, dict):
for k, v in obj.items():
if isinstance(v, str):
if v == val:
result.append([k])
continue
exts = extract(v, val)
for e in exts:
result.append([k] e)
if isinstance(obj, list):
count = 0
for item in obj:
if isinstance(item, str):
if item == val:
result.append([count])
continue
exts = extract(item, val)
for e in exts:
temp = [count]
temp.extend(e)
result.append(temp)
count =1
return result
Комментарии:
1. Фантастика, большое спасибо за большую помощь!