Сравнение 2 списка словарей и возвращение измененных значений в виде вложенных слов - PullRequest
0 голосов
/ 28 марта 2020

У меня есть 2 списка диктовок следующим образом

last_data = [{'country': 'USA', 'cases': 10425, 'deaths': 1704, 'recovered': 2525, 'active': 100027},
            {'country': 'Australia', 'cases': 3045, 'deaths': 1704, 'recovered': 2525, 'active': 100027},
            {'country': 'Germany', 'cases': 6025, 'deaths': 1704, 'recovered': 2525, 'active': 100027}]

current_data = [{'country': 'USA', 'cases': 10425, 'deaths': 1704, 'recovered': 2525, 'active': 100027},
                {'country': 'Australia', 'cases': 3046, 'deaths': 1704, 'recovered': 2525, 'active': 100028},
                {'country': 'Germany', 'cases': 6026, 'deaths': 1706, 'recovered': 2525, 'active': 100026}]

Я пытаюсь добиться ниже, сравнивая эти 2

expected_output = [{'Australia':{'last_cases_value': 3045,'updated_cases_value':3046,'change':1,
                                {'last_active_value':100027,'updated_active_value':10028,'change':1}
                   {'Germany':{'last_cases_value': 6025,'updated_cases_value':6026,'change':1},
                              {'last_death_value':1704,'updated_death_value':1706,'change':2},
                              {'last_active_value':100027,'updated_active_value':10026,'change':-1}]

Я пробовал много вещей, но тот, который получил меня тип закрытия равен

pairs = zip(last_data, current_data)
print([(x, y) for x, y in pairs if x != y])

вывод приведенного выше кода выглядит следующим образом

[({'country': 'Australia', 'cases': 3045, 'deaths': 1704, 'recovered': 2525, 'active': 100027}, {'country': 'Australia', 'cases': 3046, 'deaths': 1704, 'recovered': 2525, 'active': 100028}), ({'country': 'Germany', 'cases': 6025, 'deaths': 1704, 'recovered': 2525, 'active': 100027}, {'country': 'Germany', 'cases': 6026, 'deaths': 1704, 'recovered': 2525, 'active': 100027})]

Все еще не могу понять, как преобразовать в ожидаемый результат

Я использую последние версия python3 .8

Любая помощь будет принята с благодарностью

Ответы [ 3 ]

2 голосов
/ 28 марта 2020

Ваш ожидаемый результат не действителен. Я бы, наверное, go для вложенного словаря, подобного этому:

{
    "Germany": {
        "cases": {
            "last_cases_value": 6025,
            "updated_cases_value": 6026,
            "change": 1
        },
        "active": {
            "last_active_value": 100027,
            "updated_active_value": 100026,
            "change": -1
        },
        "deaths": {
            "last_deaths_value": 1704,
            "updated_deaths_value": 1706,
            "change": 2
        }
    },
    "Australia": {
        "cases": {
            "last_cases_value": 3045,
            "updated_cases_value": 3046,
            "change": 1
        },
        "active": {
            "last_active_value": 100027,
            "updated_active_value": 100028,
            "change": 1
        }
    }
}

Чтобы получить вышеизложенное, я сначала преобразовал бы ваш список словарей во вложенные словари, где 'country' - это ключ:

last_data = [{'country': 'USA', 'cases': 10425, 'deaths': 1704, 'recovered': 2525, 'active': 100027},
             {'country': 'Australia', 'cases': 3045, 'deaths': 1704, 'recovered': 2525, 'active': 100027},
             {'country': 'Germany', 'cases': 6025, 'deaths': 1704, 'recovered': 2525, 'active': 100027}]

current_data = [{'country': 'USA', 'cases': 10425, 'deaths': 1704, 'recovered': 2525, 'active': 100027},
                {'country': 'Australia', 'cases': 3046, 'deaths': 1704, 'recovered': 2525, 'active': 100028},
                {'country': 'Germany', 'cases': 6026, 'deaths': 1706, 'recovered': 2525, 'active': 100026}]

def list_dicts_to_nested_dict(key, lst):
    return {dic[key]: {k: v for k, v in dic.items() if k != key} for dic in lst}

last_data_dict = list_dicts_to_nested_dict('country', last_data)
# {'USA': {'cases': 10425, 'deaths': 1704, 'recovered': 2525, 'active': 100027}, 'Australia': {'cases': 3045, 'deaths': 1704, 'recovered': 2525, 'active': 100027}, 'Germany': {'cases': 6025, 'deaths': 1704, 'recovered': 2525, 'active': 100027}}

current_data_dict = list_dicts_to_nested_dict('country', current_data)
# {'USA': {'cases': 10425, 'deaths': 1704, 'recovered': 2525, 'active': 100027}, 'Australia': {'cases': 3046, 'deaths': 1704, 'recovered': 2525, 'active': 100028}, 'Germany': {'cases': 6026, 'deaths': 1706, 'recovered': 2525, 'active': 100026}}

Что также является хорошей идеей, потому что поиск определенных данных c стран будет O (1) вместо O (N) при сканировании всего толковый словарь. Это также облегчает пересечение стран в будущем, что я покажу ниже.

Затем добавьте измененные данные во вложенную двухстороннюю collections.defaultdict из dict, поскольку она обрабатывает инициализацию новых ключей для вы. Вы можете взглянуть на этот Nested defaultdict of defaultdict answer для получения дополнительной информации и других способов сделать это.

result = defaultdict(lambda: defaultdict(dict))

# Get the intersecting keys.
# Avoids Key Errors in the future, if both dictionaries don't have the same key
for country in last_data_dict.keys() & current_data_dict.keys():

    # Only deal with dictionaries that have changed
    if last_data_dict[country] != current_data_dict[country]:

        # Get intersecting keys between both dictionaries
        for key in last_data_dict[country].keys() & current_data_dict[country].keys():

            # Calculate the change between updated and previous data
            change = current_data_dict[country][key] - last_data_dict[country][key]

            # We only care about data that has changed
            # Insert data into dictionary
            if change != 0:
                result[country][key][f"last_{key}_value"] = last_data_dict[country][key]
                result[country][key][f"updated_{key}_value"] = current_data_dict[country][key]
                result[country][key]["change"] = change

Затем вы можете сериализовать и вывести вышеуказанные данные в виде строки JSON с json.dumps, поскольку таким способом проще вывести вложенный defaultdict вместо преобразование всей структуры данных в dict рекурсивно или каким-либо другим способом. В любом случае defaultdict является подклассом dict, поэтому его можно рассматривать как обычный словарь.

print(dumps(result, indent=4))

Кроме того, если вы не заботитесь о выводе, то печать defaultdict напрямую также является простым вариантом:

print(result)
# defaultdict(<function <lambda> at 0x000002355BC3AA60>, {'Australia': defaultdict(<class 'dict'>, {'cases': {'last_cases_value': 3045, 'updated_cases_value': 3046, 'change': 1}, 'active': {'last_active_value': 100027, 'updated_active_value': 100028, 'change': 1}}), 'Germany': defaultdict(<class 'dict'>, {'deaths': {'last_deaths_value': 1704, 'updated_deaths_value': 1706, 'change': 2}, 'cases': {'last_cases_value': 6025, 'updated_cases_value': 6026, 'change': 1}, 'active': {'last_active_value': 100027, 'updated_active_value': 100026, 'change': -1}})})

В качестве дополнительной опции, но не нужно шаг, как было отмечено выше, мы можем создать рекурсивную функцию для преобразования вложенного defaultdict в обычный словарь с подуровнями типа dict:

def defaultdict_to_dict(df):
    result = {}

    for k, v in df.items():
        if isinstance(v, defaultdict):
            result[k] = dict(v)
            defaultdict_to_dict(v)

    return dict(result)

pprint(defaultdict_to_dict(result))

, который работает по назначению:

{'Australia': {'active': {'change': 1,
                          'last_active_value': 100027,
                          'updated_active_value': 100028},
               'cases': {'change': 1,
                         'last_cases_value': 3045,
                         'updated_cases_value': 3046}},
 'Germany': {'active': {'change': -1,
                        'last_active_value': 100027,
                        'updated_active_value': 100026},
             'cases': {'change': 1,
                       'last_cases_value': 6025,
                       'updated_cases_value': 6026},
             'deaths': {'change': 2,
                        'last_deaths_value': 1704,
                        'updated_deaths_value': 1706}}}

Вы можете посмотреть полную реализацию на ideone.com .

2 голосов
/ 28 марта 2020

вы можете использовать список и словарь:

l = {l['country']: {'v': l['cases'], 'a': l['active']} for l in last_data}
c = {l['country']: {'v': l['cases'], 'a': l['active']} for l in current_data}

result = [{k: [{'last_cases_value': l[k]['v'], 
               'updated_cases_value': c[k]['v'],
               'change': c[k]['v'] - l[k]['v']},
               {'last_active_value': l[k]['a'], 
               'updated_active_value': c[k]['a'],
               'change': c[k]['a'] - l[k]['a']}]} for k in c.keys()]

вывод:

[{'USA': [{'last_cases_value': 10425,
    'updated_cases_value': 10425,
    'change': 0},
   {'last_active_value': 100027,
    'updated_active_value': 100027,
    'change': 0}]},
 {'Australia': [{'last_cases_value': 3045,
    'updated_cases_value': 3046,
    'change': 1},
   {'last_active_value': 100027,
    'updated_active_value': 100028,
    'change': 1}]},
 {'Germany': [{'last_cases_value': 6025,
    'updated_cases_value': 6026,
    'change': 1},
   {'last_active_value': 100027,
    'updated_active_value': 100026,
    'change': -1}]}]

, если вы хотите сохранить в результате только те страны, которые изменили свою статистику :

result = [{k: [{'last_cases_value': l[k]['v'], 
               'updated_cases_value': c[k]['v'],
               'change': c[k]['v'] - l[k]['v']},
               {'last_active_value': l[k]['a'], 
               'updated_active_value': c[k]['a'],
               'change': c[k]['a'] - l[k]['a']}]}
          for k in c.keys() if  c[k]['a'] - l[k]['a'] and c[k]['v'] - l[k]['v']]

вывод:

[{'Australia': [{'last_cases_value': 3045,
    'updated_cases_value': 3046,
    'change': 1},
   {'last_active_value': 100027,
    'updated_active_value': 100028,
    'change': 1}]},
 {'Germany': [{'last_cases_value': 6025,
    'updated_cases_value': 6026,
    'change': 1},
   {'last_active_value': 100027,
    'updated_active_value': 100026,
    'change': -1}]}]
0 голосов
/ 28 марта 2020

вы можете использовать pandas:

import pandas as pd

curr_df=pd.DataFrame(current_data)
last_df=pd.DataFrame(last_data)

df=curr_df.merge(last_df, on="country", suffixes=["_updated", "_last"])
res=df[sorted(df.columns)].rename(columns={c: "_".join(c.split("_")[::-1])+"_value" for c in df.columns if c!="country"}).set_index('country').to_dict('index')

res=[dict([(k, [dict(p) for p in list(zip(v.items(), list(v.items())[1:]))[::2]])]) for k, v in res.items()]

Выход:

[{'USA': [{'last_active_value': 100027, 'updated_active_value': 100027}, {'last_cases_value': 10425, 'updated_cases_value': 10425}, {'last_deaths_value': 1704, 'updated_deaths_value': 1704}, {'last_recovered_value': 2525, 'updated_recovered_value': 2525}]}, {'Australia': [{'last_active_value': 100027, 'updated_active_value': 100028}, {'last_cases_value': 3045, 'updated_cases_value': 3046}, {'last_deaths_value': 1704, 'updated_deaths_value': 1704}, {'last_recovered_value': 2525, 'updated_recovered_value': 2525}]}, {'Germany': [{'last_active_value': 100027, 'updated_active_value': 100026}, {'last_cases_value': 6025, 'updated_cases_value': 6026}, {'last_deaths_value': 1704, 'updated_deaths_value': 1706}, {'last_recovered_value': 2525, 'updated_recovered_value': 2525}]}]

Примечание - merge по умолчанию выполняет внутреннее соединение, если вы хотите полный внешний / левый внешний посмотрите на аргумент how:

https://pandas.pydata.org/pandas-docs/stable/reference/api/pandas.DataFrame.merge.html

zip формула - для изменения списка попарно - была взята отсюда : { ссылка }

...