Преобразуйте список диктов, комбинируя элементы списка на основе ключа - PullRequest
1 голос
/ 01 ноября 2019

Имеется список словарей, таких как:

history = [
  {
    "actions": [{"action": "baz", "people": ["a"]}, {"action": "qux", "people": ["d", "e"]}],
    "events": ["foo"]
  },
  {
    "actions": [{"action": "baz", "people": ["a", "b", "c"]}],
    "events": ["foo", "bar"]
  },
]

Какой самый эффективный (пока еще читаемый) способ получить список диктов, где каждый диктователь является уникальным event и списокдействия для этого события были объединены на основе клавиши action. Например, для приведенного выше списка желаемый вывод:

output = [
    {
      "event": "foo", 
      "actions": [
        {"action": "baz", "people": ["a", "b", "c"]}, 
        {"action": "qux", "people": ["d", "e"]}
      ]
    },
    {
      "event": "bar", 
      "actions": [
        {"action": "baz", "people": ["a", "b", "c"]}
      ]
    },
]

Я не могу изменить структуру вывода, так как он потребляется чем-то внешним. Я написал следующий код, который работает, но очень многословен и плохо читается.

from collections import defaultdict

def transform(history):
    d = defaultdict(list)
    for item in history:
        for event in item["events"]:
            d[event] = d[event] + item["actions"]
    transformed = []
    for event, actions in d.items():
        merged_actions = {}
        for action in actions:
            name = action["action"]
            if merged_actions.get(name):
                merged_actions[name]["people"] = list(set(action["people"]) | set(merged_actions[name]["people"]))
            else:
                merged_actions[name] = {
                    "action": action["action"],
                    "people": action["people"]
                }
        transformed.append({
            "event": event,
            "actions": list(merged_actions.values())
        })
    return transformed

Я нацеливаюсь только на python3.6 +

Ответы [ 2 ]

2 голосов
/ 01 ноября 2019

Вы можете использовать collections.defaultdict с itertools.groupby:

from collections import defaultdict
from itertools import groupby as gb
d = defaultdict(list)
for i in history:
  for b in i['events']:
    d[b].extend(i['actions'])

new_d = {a:[(j, list(k)) for j, k in gb(sorted(b, key=lambda x:x['action']), key=lambda x:x['action'])] for a, b in d.items()}
result = [{'event':a, 'actions':[{'action':c, 'people':list(set([i for k in b for i in k['people']]))} for c, b in d]} for a, d in new_d.items()]

Выход:

[
 {'event': 'foo', 
  'actions': [
     {'action': 'baz', 'people': ['b', 'a', 'c']}, 
     {'action': 'qux', 'people': ['d', 'e']}
    ]
  }, 
 {'event': 'bar', 
   'actions': [{'action': 'baz', 'people': ['b', 'a', 'c']}]
  }
 ]
0 голосов
/ 01 ноября 2019

Это не менее подробный ответ, но, возможно, немного лучше читаемый. Также он не зависит ни от чего и является просто стандартным питоном.

tmp_dict = {}
for d in history:
    for event in d["events"]:
        if event not in tmp_dict:
            tmp_dict[event] = {}
            for actions in d["actions"]:
                tmp_dict[event][actions["action"]] = actions["people"]
        else:
            for actions in d["actions"]:
                if actions["action"] in tmp_dict[event]:
                    tmp_dict[event][actions["action"]].extend(actions["people"])
                else:
                    tmp_dict[event][actions["action"]] = actions["people"]

output = [{"event": event, "actions": [{"action": ac, "people": list(set(peop))} for ac, peop in tmp_dict[event].items()]} for event in tmp_dict]

print (output)

Вывод:

[
   {'event': 'foo',
    'actions': [
                {'action': 'qux', 'people': ['e', 'd']},
                {'action': 'baz', 'people': ['a', 'c', 'b']}
               ]
   },
   {'event': 'bar',
    'actions': [{'action': 'baz', 'people': ['a', 'c', 'b']}]
   }
]
...