список именованных корней; как вычислить сумму отдельных элементов

#python #namedtuple

#python #namedtuple

Вопрос:

Скажем, у меня есть namedtuple:

 Trade = namedtuple('Trade', ['Ticker', 'Date', 'QTY', 'Sell', 'Buy', 'Profit'])
 

Есть ли какой-либо «питонический» способ сделать однострочную сумму каждого «суммируемого» или «выбранного» (КОЛ-во, продажа, покупка, прибыль) элементов в списке?

 listofstuff = []
Trade1 = Trade(Ticker='foo', Date='{2020:12:24}', QTY=100, Sell=500.0, Buy=100.0, Profit=400.0)
Trade2 = Trade(Ticker='foo', Date='{2020:12:24}', QTY=50, Sell=50.0, Buy=500.0, Profit=-450.0)

listofstuff.append(Trade1)
listofstuff.append(Trade2)
 

Ожидаемый результат:

 Trade(Ticker='do not care', Date='do not care', QTY=150.0, Sell = 550.0, Buy = 600.0,0 Profit=-50.0)
 

Я знаю, что могу сделать следующее, что занимает 4 строки кода:

 tot_QTY = sum(i.QTY for i in listofstuff)
tot_Sell = sum(i.Sell for i in listofstuff)
tot_Buy = sum(i.Buy for i in listofstuff)
tot_Profit = sum(i.Profit for i in listofstuff)

x = Trade(Ticker=listofstuff[0].Ticker, Date=listofstuff[0].Date, QTY=tot_QTY,
          Sell=tot_Sell, Buy=tot_Buy, Profit=tot_Profit)
 

Но хотелось бы заменить суммы чем-то более общим, что занимает всего 1 строку кода 🙂

 total = sum(listofstuff) # most likely cannot calc sums of 'ticker' nor ' date' but I do not care since I can use original [0] items for those..
 

а затем создайте ‘x’, используя сумму отдельных элементов в списке

 x = Trade(Ticker=listofstuff[0].Ticker, Date=listofstuff[0].Date, QTY=total.QTY,
          Sell=total.Sell, Buy=total.Buy, Profit=total.Profit)
 

Ответ №1:

Мы можем посмотреть на это так: sum это просто reduce часть входного списка с операцией сложения в качестве функции. Итак, мы можем просто определить нашу собственную операцию:

 def add_trades(x, y):
    return Trade(x.Ticker, x.Date, x.QTY   y.QTY, x.Sell   y.Sell, x.Buy   y.Buy, x.Profit   y.Profit)
 

И используйте это с reduce :

 from functools import reduce

x = reduce(add_trades, listofstuff)
print(x)
# Gives: Trade(Ticker='foo', Date='{2020:12:24}', QTY=150, Sell=550.0, Buy=600.0, Profit=-50.0)
 

Чтобы сделать add_trades функцию более универсальной, вы можете выполнить итерацию _fields кортежа и try добавить их:

 def add_trades(x, y):
    new_fields = []
    for field in x._fields:
        try:
            new_fields.append(getattr(x, field)   getattr(y, field))
        except TypeError:
            new_fields.append(getattr(x, field))
    return Trade._make(new_fields)
 

Комментарии:

1. спасибо, но это выглядит не намного / не проще, чем оригинальное решение. Можно ли с уверенностью предположить, что нет какого-либо встроенного метода для совместного вычисления отдельных элементов? Если бы у меня было 2 разных именованных корня (разные элементы), могла бы одна функция «add» добавлять элементы, несмотря на реальные элементы кортежа? Что-то вроде «шаблона» в C … Я мог бы добавить также «Ticker» и «Data» в «add_trades», несмотря на целесообразность или полезность, поскольку оригиналы также доступны после вызова…

2. @okkraj Я не уверен, что понимаю. Вы имеете в виду, что хотите добавить все поля, которые не вызывают ошибку? Посмотрите, соответствует ли мое редактирование тому, что вы имеете в виду

3. да, это именно то, о чем я спрашивал в комментарии к вашему сообщению. Хотя это не «однострочный», но выглядит как общий! Решение @Alain T выглядит тогда еще более «питоническим» способом, если вы знаете типы полей, но ваши выглядят более «читабельными» для новичка в python :).

Ответ №2:

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

 R = Trade(*((max,sum)[isinstance(v[0],(int,float))](v) 
            for v in zip(*listofstuff)))

print(R)
Trade(Ticker='foo', Date='{2020:12:24}', QTY=150, Sell=550.0,
      Buy=600.0, Profit=-50.0)
 

в качестве альтернативы, вы можете поместить значение None в поля, отличные от total:

 R = Trade(*( sum(v) if isinstance(v[0],(int,float)) else None 
             for v in zip(*listofstuff)))

print(R)
Trade(Ticker=None, Date=None, QTY=150, Sell=550.0, Buy=600.0, Profit=-50.0)
 

Если тип агрегации варьируется в зависимости от каждого поля (например, min для одних полей, sum для других и т. Д.), Вы можете подготовить словарь функций агрегации и использовать его в понимании:

 aggregate = {'QTY':sum, 'Sell':min, 'Buy':max, 'Profit':lambda v:sum(v)/len(v)}

R = Trade(*(aggregate.get(f,lambda _:None)(v) 
            for f,v in zip(Trade._fields,zip(*listofstuff))))

print(R)
Trade(Ticker=None, Date=None, QTY=150, Sell=50.0, Buy=500.0,  Profit=-25.0)
 

Комментарии:

1. большое спасибо, это было то, что я искал, но не смог найти никакого «готового ответа из Интернета». Пометил это как «принятый ответ», так как я запросил «питонический» способ, а ваш довольно простой однострочный! Дополнительный бонус за счет дальнейшего ответа, показывая, что что-то можно сделать «довольно легко» для разных элементов по отдельности.

Ответ №3:

Вы можете сжать список и уменьшить, используя » «, что близко к тому, что вы просите, чтобы суммировать числа. » » объединяет строку, это может сработать:

 from functools import reduce
import operator

sums = [reduce(operator.add,i) for i in zip(*listofstuff)]
sums[:2] = listofstuff[0].Ticker,listofstuff[0].Date
Trade(*sums)

Trade(Ticker='foo', Date='{2020:12:24}', QTY=150, Sell=550.0, Buy=600.0, Profit=-50.0)
 

Комментарии:

1. Это сработало бы, но, похоже, требует, чтобы все элементы поддерживали операцию , поэтому datetime выдает ошибку в моем случае «Ошибка типа: неподдерживаемые типы операндов для : ‘datetime.date’ и ‘datetime.date'»