Найти - Пользователи
Полная версия: Как свернуть список словарей
Начало » Python для новичков » Как свернуть список словарей
1
diagon
Есть структура данных:
[
{’name’: ‘Foo’, ‘size’: 2, ‘count’: 1}
{’name’: ‘Bar’, ‘size’: 1, ‘count’: 3}
{’name’: ‘Bar’, ‘size’: 1, ‘count’: 2}
]

На выходе должно получиться:
[
{’name’: ‘Foo’, ‘size’: 2, ‘count’: 1}
{’name’: ‘Bar’, ‘size’: 1, ‘count’: 5}
]

Как сделать это на Python?
Понимаю, что в Django ORM это сделать просто, на у меня сложная вычисляемая структура данных и, кажется, проще это обработать оперативке, тем более список не особо большой. В голову приходит reduce, но не понимаю как там можно использовать составные ключи, также на уме библиотека для работы со структурами данных, но особо с ней не работал. Ключей в реальной задаче больше, чем 2, и суммируемых полей тоже.
py.user.next
Добавил ещё поле одно.
  
>>> def get_uniq_fields(lst, field_name):
...     return tuple({i[field_name]:None for i in lst})
... 
>>> def combine_fields(lst, field_name):
...     return sum(i[field_name] for i in lst)
... 
>>> def join_objects(lst):
...     out = []
...     names = get_uniq_fields(lst, 'name')
...     for name in names:
...         filtered_by_name = tuple(i for i in lst if i['name'] == name)
...         template = filtered_by_name[0]
...         count_combined = combine_fields(filtered_by_name, 'count')
...         some_combined = combine_fields(filtered_by_name, 'some')
...         template['count'] = count_combined
...         template['some'] = some_combined
...         out.append(template)
...     return out
... 
>>> lst = [{'name': 'Foo', 'size': 2, 'count': 1, 'some': 10},
...        {'name': 'Bar', 'size': 1, 'count': 3, 'some': 20},
...        {'name': 'Bar', 'size': 1, 'count': 2, 'some': 30}]
>>> 
>>> out = join_objects(lst)
>>> out
[{'name': 'Foo', 'size': 2, 'count': 1, 'some': 10}, {'name': 'Bar', 'size': 1, 'count': 5, 'some': 50}]
>>>
FishHook
   
from typing import Dict, Tuple, Generator
  
class Summarizer:
  
    fields_to_sum = ["count"]
    unique_fields = ["name", "size"]
  
    def __init__(self):
        self._result: Dict[Tuple[str, ...], Tuple[int, ...]] = {}
  
    def add(self, record: Dict):
  
        uniques = tuple(record[i] for i in self.unique_fields)
        sums = tuple(record[i] for i in self.fields_to_sum)
        if sub_sums := self._result.get(uniques):
            sums = tuple(sum(i) for i in zip(sums, sub_sums))
        self._result[uniques] = sums
  
    @property
    def result(self) -> Generator[Dict, None, None]:
        for uniques, sums in self._result.items():
            _u = {k: v for k, v in zip(self.unique_fields, uniques)}
            _s = {k: v for k, v in zip(self.fields_to_sum, sums)}
            yield {**_u, **_s}
  
summarizer = Summarizer()
l = [{"name": "Foo", "size": 2, "count": 1},
     {"name": "Bar", "size": 1, "count": 3},
     {"name": "Bar", "size": 1, "count": 2}
     ]
  
for i in l:
    summarizer.add(i)
  
print(list(summarizer.result))
xam1816
  
lst = [
    {'name': 'Foo', 'size': 2, 'count': 1},
    {'name': 'Bar', 'size': 1, 'count': 3},
    {'name': 'Bar', 'size': 1, 'count': 2}
]
new_dict = {}
for d in lst:
    k = (d['name'], d['size'])
    new_dict.setdefault(k, 0)
    new_dict[k] += d['count']
out = [{'name': k[0], 'size': k[1], 'count': v} for k, v in new_dict.items()]
print(out)

 
[{'name': 'Foo', 'size': 2, 'count': 1}, {'name': 'Bar', 'size': 1, 'count': 5}]
FishHook
xam1816
diagon
Ключей в реальной задаче больше, чем 2, и суммируемых полей тоже.
Мы не знаем насколько больше, можем предположить, что ключей десятки. Ваше решение имеет право на жизнь, вопросов нет, но мне кажется что в данном случае задачу надо решать в общем виде. Мне кажется хорошим критерием считать, что если мы изменим структуру данных добавив/удалив поле, то лучшее решение то, которое не влечет за собой никаких изменений кроме декларативных, слабее - если нам придется изменять алгоритм. Хотя, конечно, этим можно пожертвовать ради простоты и/или производительности.
diagon
Всем спасибо большое за интересные предложения.
Вспомнил еще про pandas

 import pandas as pd
data = [
{'name': 'Foo', 'size': 2, 'count': 1},
{'name': 'Bar', 'size': 1, 'count': 3},
{'name': 'Bar', 'size': 1, 'count': 2}
]
df = pandas.DataFrame(data)
out = df.groupby(['name', 'size'], as_index=False).sum('count').to_dict('records')
print(print)

 [
{'name': 'Bar', 'size': 1, 'count': 5},
{'name': 'Foo', 'size': 2, 'count': 1}
]

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

В случае необходимости обрабтки в памяти думаю лучше использовать pandas, код выходит более читабельным.
Конечно можно написать свою функцию “сворачивания” и использовать ее, но что-то мне подсказывает, что в pandas хорошо оптимизирована работа с данными и сильно быстрее собственный код работать не будет.
Интересно услышать рассуждения других по поводу подобных задач.
FishHook
diagon
Интересно услышать рассуждения других по поводу подобных задач.
Мне кажется, тут все очень индивидуально и зависит от конкретной задачи, объема данных, их структуры, структуры команды и т.д. Несколько соображений:
- если данные табличные, данных много и запросы большие и сложные, то есть смысл по полной использовать возможности СУБД, это будет быстрее
- если данных относительно мало, а скорость обработки критична, то pandas не факт что даст преимущества, вы потеряете время на создании датафреймов, они не тривиально внутри устроены.
- нужна ли вам лишняя зависимость (тяжелая зависимость) в проекте ради одной строки кода? Надо ли вам увеличивать порог вхождения членов команды ради этого?
- pandas, насколько я понимаю, это инструмент биг-дата, он хорошо работает в связке с Hadoop и колоночными данными. Мне видится, что постобработка данных извлеченных из реляционной БД пандой, это какой-то странный оверхед и где-то тут есть ошибка архитектуры.
This is a "lo-fi" version of our main content. To view the full version with more information, formatting and images, please click here.
Powered by DjangoBB