Замените ключи вложенного словаря значением другого словаря (где ключи двух словарей равны)), значением для ключа может быть список словарей

У меня есть два dict, dict1 и dict2, я хочу создать новый dict (или манипулировать dict1) с парами ключ-значение как (значение dict2: значение dict1 [где ключ dict one и ключ dict2 одинаковы]), значение ключа может быть списком диктов (как вы увидите в примере ввода)

ВВОД

dict1 = {"key1":{"key3":"value1","key2":"value2","key4":{"key5":"value3","key6":{"key7":"value4","key8":{"key9":"value5","key10":"value6","key55":"value7"}},"key11":{"key12":"value8","key13":"value9"},"key14":[{"key15":"value10","key16":"value11","key17":"value12"},{"key15":"value13","key16":"value14","key17":"value15"}]}}}

dict2 = {"key1":"ab","key2":"bc","key3":"cd","key4":"de","key5":"ef","key6":"fg","key7":"gh","key8":"hi","key9":"ij","key10":"jk","key55":"kl","key11":"lm","key12":"mn","key13":"no","key14":"op","key15":"pq","key16":"qr","key17":"qs"}

Моя функция

def walk(dict1, dict2):
    output = {}
    for key, value in dict1.iteritems():

        if isinstance(value, dict):
            output[dict2[key]] = walk(value, dict2)
        elif isinstance(value, list):
            output[dict2[key]] = walk_list(value, dict2)
        else:
            output[dict2[key]] = value
    return output

def walk_list(sublist, dict2):
    output = []
    for i in sublist:

        if isinstance(i, dict):
            output = walk(i, dict2)
        elif isinstance(value, list):
            output = walk_list(i, dict2)
        else:
            output.append((key, value))
    return output

output = walk(dict1, dict2)
output = json.dumps(output)
print output

ВЫХОД Я ПОЛУЧИЛ

 {"ab": {"de": {"lm": {"mn": "value8", "no": "value9"}, "ef": "value3", "fg": {"hi": {"ij": "value5", "jk": "value6", "kl": "value7"}, "gh": "value4"}, "op": {"pq": "value13", "qs": "value15", "qr": "value14"}}, "bc": "value2", "cd": "value1"}}

ОЖИДАЕМЫЙ РЕЗУЛЬТАТ

 {"ab":{"cd":"value1","bc":"value2","de":{"ef":"value3","fg":{"gh":"value4","hi":{"ij":"value5","jk":"value6","kl":"value7"}},"lm":{"mn":"value8","no":"value9"},"op":[{"pq":"value10","qr":"value11","qs":"value12"},{"pq":"value13","qr":"value14","qs":"value15"}]}}}

Пожалуйста, исправьте мой код.


person Ritzor    schedule 10.01.2016    source источник


Ответы (3)


Очень простое решение, которое выполняет рекурсивный шаг очень рано и поэтому имеет очень простую логику:

def translateKeys (obj, keyNames):
    if isinstance(obj, dict):
        return {keyNames.get(k, k): translateKeys(v, keyNames) for k, v in obj.items()}
    elif isinstance(obj, list):
        return [translateKeys(v, keyNames) for v in obj]
    else:
        return obj

Вместо того, чтобы ожидать определенного типа, он просто принимает что угодно (словарь, список или что-то еще) и работает с его элементами, вызывая себя для каждого значения. Это позволяет избежать повторения самого obj и проверки значения каждого элемента в цикле.

Используется в вашем примере данных:

>>> dict1 = {"key1":{"key3":"value1","key2":"value2","key4":{"key5":"value3","key6":{"key7":"value4","key8":{"key9":"value5","key10":"value6","key55":"value7"}},"key11":{"key12":"value8","key13":"value9"},"key14":[{"key15":"value10","key16":"value11","key17":"value12"},{"key15":"value13","key16":"value14","key17":"value15"}]}}}
>>> dict2 = {"key1":"ab","key2":"bc","key3":"cd","key4":"de","key5":"ef","key6":"fg","key7":"gh","key8":"hi","key9":"ij","key10":"jk","key55":"kl","key11":"lm","key12":"mn","key13":"no","key14":"op","key15":"pq","key16":"qr","key17":"qs"}
>>> expected =  {"ab":{"cd":"value1","bc":"value2","de":{"ef":"value3","fg":{"gh":"value4","hi":{"ij":"value5","jk":"value6","kl":"value7"}},"lm":{"mn":"value8","no":"value9"},"op":[{"pq":"value10","qr":"value11","qs":"value12"},{"pq":"value13","qr":"value14","qs":"value15"}]}}}
>>> result = translateKeys(dict1, dict2)
>>> result
{'ab': {'de': {'fg': {'gh': 'value4', 'hi': {'ij': 'value5', 'jk': 'value6', 'kl': 'value7'}}, 'op': [{'qr': 'value11', 'pq': 'value10', 'qs': 'value12'}, {'qr': 'value14', 'pq': 'value13', 'qs': 'value15'}], 'ef': 'value3', 'lm': {'no': 'value9', 'mn': 'value8'}}, 'cd': 'value1', 'bc': 'value2'}}
>>> result == expected
True

Если вы хотите инвертировать этот перевод, вы можете просто инвертировать keyNames и вместо этого выполнить перевод результата:

>>> result = translateKeys(dict1, dict2)
>>> invertedKeyNames = {v: k for k, v in dict2.items()}
>>> original = translateKeys(result, invertedKeyNames)
>>> original == dict1
True
person poke    schedule 10.01.2016
comment
это обобщенно? означает, что это будет работать для любого JSON? подобное значение ключа может быть простым значением, списком, диктовкой, любой другой вложенной допустимой комбинацией списков и диктора в соответствии со структурой JSON? - person Ritzor; 10.01.2016
comment
Да, это должно работать с obj любым десериализованным объектом JSON. Поскольку объектами JSON могут быть только объекты (=словари), массивы (=списки) или значения (числа, строки, true, false или null), и мы обрабатываем рекурсию объектов и массивов (поскольку это единственные типы, которые могут содержать другие объекты JSON, которым может потребоваться перевод ключа), он должен работать с любым допустимым объектом, полученным из JSON. . - person poke; 10.01.2016
comment
Можете ли вы также предоставить другую функцию для извлечения dict1 с помощью dict2 и результата? - person Ritzor; 10.01.2016
comment
Конечно, вам просто нужно инвертировать словарь dict2, чтобы сопоставление было в другом направлении, а затем применить его к предыдущему результату. Смотрите мой отредактированный ответ. - person poke; 10.01.2016

Я думаю, что это связано с вашей функцией walk_list, когда выходная переменная назначается, а не добавляется. Это моя версия:

dict1 = {"key1":{"key3":"value1","key2":"value2","key4":{"key5":"value3","key6":{"key7":"value4","key8":{"key9":"value5","key10":"value6","key55":"value7"}},"key11":{"key12":"value8","key13":"value9"},"key14":[{"key15":"value10","key16":"value11","key17":"value12"},{"key15":"value13","key16":"value14","key17":"value15"}]}}}
dict2 = {"key1":"ab","key2":"bc","key3":"cd","key4":"de","key5":"ef","key6":"fg","key7":"gh","key8":"hi","key9":"ij","key10":"jk","key55":"kl","key11":"lm","key12":"mn","key13":"no","key14":"op","key15":"pq","key16":"qr","key17":"qs"}

def walk(dict1, dict2):
    output = {}
    for key, value in dict1.iteritems():

        if isinstance(value, dict):
            outVal = walk(value, dict2)
        elif isinstance(value, list):
            outVal = walk_list(value, dict2)
        else:
            outVal = value
        output[dict2[key]] = outVal

    return output

def walk_list(sublist, dict2):
    output = []
    for i in sublist:
        if isinstance(i, dict):
            outVal = walk(i, dict2)
        elif isinstance(i, list):
            outVal = walk_list(i, dict2)
        else:
            outVal = i
        output.append(outVal)

    return output            

mine = walk(dict1, dict2)
expecting = {"ab":{"cd":"value1","bc":"value2","de":{"ef":"value3","fg":{"gh":"value4","hi":{"ij":"value5","jk":"value6","kl":"value7"}},"lm":{"mn":"value8","no":"value9"},"op":[{"pq":"value10","qr":"value11","qs":"value12"},{"pq":"value13","qr":"value14","qs":"value15"}]}}}

print mine == expecting
person TimSC    schedule 10.01.2016
comment
это обобщенно? означает, что это будет работать для любого JSON? подобное значение ключа может быть простым значением, списком, диктовкой, любой другой вложенной допустимой комбинацией списков и диктора в соответствии со структурой JSON? - person Ritzor; 10.01.2016
comment
Да, я так думаю. :) - person TimSC; 10.01.2016
comment
Кстати, спасибо за ваш вклад, можете ли вы также предоставить другую функцию для извлечения dict1 с помощью dict2 и результата? - person Ritzor; 10.01.2016

Я думаю, что это решает загадку, используя только одну функцию

def walk(dict1, dict2):
    res = dict()
    for k,v in dict1.items():
        if isinstance(v,list):
            newv = [walk(x, dict2) for x in v]
        elif isinstance(v,dict):
            newv = walk(v, dict2)
        else:
            newv = v
        res[dict2.get(k, k)] = newv # keep the same key if not present in dict2
    return res

expected = {"ab":{"cd":"value1","bc":"value2","de":{"ef":"value3","fg":{"gh":"value4","hi":{"ij":"value5","jk":"value6","kl":"value7"}},"lm":{"mn":"value8","no":"value9"},"op":[{"pq":"value10","qr":"value11","qs":"value12"},{"pq":"value13","qr":"value14","qs":"value15"}]}}}

output = walk(dict1, dict2)
print(output)
print(output == expected)

как он производит

{'ab': {'de': {'lm': {'no': 'value9', 'mn': 'value8'}, 'ef': 'value3', 'fg': {'hi': {'ij': 'value5', 'kl': 'value7', 'jk': 'value6'}, 'gh': 'value4'}, 'op': [{'qr': 'value11', 'qs': 'value12', 'pq': 'value10'}, {'qr': 'value14', 'qs': 'value15', 'pq': 'value13'}]}, 'cd': 'value1', 'bc': 'value2'}}
True

В основном он проверяет каждое значение в словаре:

  • если это список значений, он применяется к каждому элементу в списке
  • если это словарь, то он применяется к нему
  • если это буквальное значение, то оно используется

РЕДАКТИРОВАТЬ:

в случае, если входные словари в конце концов являются не только словарями, но могут быть любым приемлемым элементом json (например, list, dict, value), который может стать более обобщенным

def walk(obj, keys):
    if isinstance(obj,list):
        return [walk(x, keys) for x in obj]
    elif isinstance(obj,dict):
        return {keys.get(k, k): walk(v, keys) for k,v in obj.items()}
    else:
        return obj

это именно то, что @Poke ответил с самого начала, спасибо ему.

РЕДАКТИРОВАТЬ2:

на случай, если вы захотите вернуться к исходному словарю dict1, при условии, что все значения не пересекаются (т. е. сопоставление dict2 представляет собой биективная функция), вы можете сделать

back2dict1 = walk(output, {v:k for k,v in dict2.items()})
print(back2dict1)
print(back2dict1 == dict1)

который производит

{'key1': {'key3': 'value1', 'key2': 'value2', 'key4': {'key5': 'value3', 'key11': {'key12': 'value8', 'key13': 'value9'}, 'key14': [{'key15': 'value10', 'key16': 'value11', 'key17': 'value12'}, {'key15': 'value13', 'key16': 'value14', 'key17': 'value15'}], 'key6': {'key7': 'value4', 'key8': {'key10': 'value6', 'key55': 'value7', 'key9': 'value5'}}}}}
True
person Pynchia    schedule 10.01.2016
comment
res[dict2[k]] = newv должно быть res[dict2.get(k, k)] = newv для обработки случая, когда k not in dict2. - person Dan D.; 10.01.2016
comment
@ДанД. да, спасибо, что указали на это. Я думал об этом, но не записал в конце - person Pynchia; 10.01.2016
comment
это обобщенно? означает, что это будет работать для любого JSON? подобное значение ключа может быть простым значением, списком, диктовкой, любой другой вложенной допустимой комбинацией списков и диктора в соответствии со структурой JSON? - person Ritzor; 10.01.2016
comment
Кстати, вы можете обновить заголовок и описание вашего вопроса, чтобы более точно отразить ваши требования. Это поможет будущим читателям с той же проблемой - person Pynchia; 10.01.2016
comment
Кстати, спасибо за ваш вклад, можете ли вы также предоставить другую функцию для извлечения dict1 с помощью dict2 и результата? - person Ritzor; 10.01.2016