У вас есть ориентированный граф с ребрами, образованными id -> id
связями. Вы пытаетесь перечислить все пути через этот график. Это на самом деле намного проще на не с использованием связанных списков.
Обратите внимание, что ваша реализация связанного списка на самом деле не связывает узлы;Ваши значения next
должны ссылаться на другие Node
экземпляры, а не на id
.
Поскольку ваши пути не могут иметь циклов, график называется ациклическим ,Ваши пути также очень просты, так как вы говорите, что никогда не может быть более одного идентификатора получателя на идентификатор отправителя.
Создайте новое представление в вашем фрейме данных с индексом sender_id вместе с идентификатором и количеством получателя. колонны;это позволит очень легко найти следующие элементы пути. Затем вы можете перебирать эти столбцы, обходить их пути и тривиально суммировать их суммы. В следующем коде используются уже найденные пути , чтобы избежать необходимости повторного обхода этих путей:
# receiver and amount rows, indexed by sender
edges = df[['sender_id', 'receiver_id', 'amount']].set_index('sender_id')
paths = {} # sender -> [sender, receiver, receiver, receiver, ...]
totals = {} # sender -> total amount
for sender, next_, amount in edges.itertuples():
path = paths[sender] = [sender, next_]
totals[sender] = amount
while True:
if next_ in paths:
# re-use already found path
path += paths[next_]
totals[sender] += totals[next_]
break
try:
next_, amount = edges.loc[next_]
except KeyError:
break # path complete
path.append(next_)
totals[sender] += amount
Код можно сделать еще более эффективным, обновляя каждый встреченный подпуть, поэтому, когда выобработайте 3-ю строку для идентификатора отправителя 125
, этот путь уже обработан, потому что вам пришлось пройти его по пути, начинающемуся с 002
для первой строки:
for sender, next_, amount in edges.itertuples():
if sender in paths:
# already handled as part of a longer path
continue
paths[sender], totals[sender] = [sender, next_], amount
senders = [sender] # all sender ids along the path
while True:
if next_ in paths:
# re-use already found path
for sender in senders:
paths[sender] += paths[next_]
totals[sender] += totals[next_]
break
if next_ not in edges.index:
break # path complete
# start a new path from this sender id
paths[next_], totals[next_] = [next_], 0
senders.append(next_)
next_, amount = edges.loc[next_]
for sender in senders:
paths[sender].append(next_)
totals[sender] += amount
В любом случае, вы сейчасиметь полные пути и итоги для всех ваших транзакций. Вы можете превратить их обратно в дополнительные столбцы:
df['path'], df['total'] = df.sender_id.map(paths), df.sender_id.map(totals)
Для вашего входного фрейма данных, который выдает:
transaction_id sender_id receiver_id amount path total
0 213234 002 125 10 [002, 125, 689, 233] 85
1 223322 017 354 90 [017, 354, 456] 100
2 343443 125 689 70 [125, 689, 233] 75
3 324433 689 233 5 [689, 233] 5
4 328909 354 456 10 [354, 456] 10
В качестве альтернативы, вы можете объединить пути и итоги, зацикливая либословарь:
for id, path in paths.items():
print(id, path, totals[id])
, который, опять же для вашего конкретного примера, выдает:
002 ['002', '125', '689', '233'] 85
125 ['125', '689', '233'] 75
689 ['689', '233'] 5
017 ['017', '354', '456'] 100
354 ['354', '456'] 10