F # повторять последовательность объектов и условно агрегировать на основе свойства - PullRequest
0 голосов
/ 26 ноября 2018

Мне удалось выполнить это упражнение в C #, но у меня возникли проблемы с воспроизведением этого в F #.У меня есть последовательность следующего типа TransactionFs:

    type TransactionFs(Debitor: string, Activity:string, Spend:float, Creditor:string)  = 
        member this.Debitor = Debitor
        member this.Activity = Activity
        member this.Spend = Spend
        member this.Creditor = Creditor

последовательность:

    [FSI_0003+TransactionFs {Activity = "someActivity1";
                         Creditor = "alessio";
                         Debitor = "luca";
                         Spend = 10.0;};
 FSI_0003+TransactionFs {Activity = "someActivity2";
                         Creditor = "alessio";
                         Debitor = "giulia";
                         Spend = 12.0;};
 FSI_0003+TransactionFs {Activity = "someActivity3";
                         Creditor = "luca";
                         Debitor = "alessio";
                         Spend = 7.0;};

Я пытаюсь получить последовательность TransactionFs по следующему правилу,Для каждой транзакции отметьте Debitor и Creditor;посмотрите в последовательности все соответствующие транзакции, где Debitor и Creditor поменялись местами, и верните единственное TransactionFs со свойством Spend, которое представляет собой общую задолженность владельца наибольшего Spend (вычитая или суммируяSpend соответственно).Это Spend будет представлять общий долг от Debitor до Creditor.

Например, результат для пары Creditor и Debitor alessio и luca долженbe:

TransactionFs {Activity = "_aggregate_";
                     Creditor = "alessio";
                     Debitor = "luca";
                     Spend = 3.0;};

Конечно, один из способов сделать это - использовать вложенные циклы, но, поскольку я изучаю F #, я хотел бы знать, каков будет правильный функциональный способ сделать это.

1 Ответ

0 голосов
/ 26 ноября 2018

В качестве первого шага, я бы, вероятно, использовал Seq.groupBy, чтобы сгруппировать предметы в единицы с той же парой людей, что и кредитором или должником, в любом порядке.Таким образом, вы получите список списков транзакций, но все это делается за один шаг O (N).Т.е.,

let grouped = transactions |> Seq.groupBy (fun t ->
    let c, d = t.Creditor, t.Debitor
    if c < d then c, d else d, c
)

Теперь у вас есть последовательность, которая выглядит примерно так (в псевдокодовой смеси кода и английского):

[
    (("alessio", "luca"), [luca gave alessio 10; alessio gave luca 7])
    (("alessio", "giulia"), [alessio gave giulia 12])
]

Вывод Seq.groupBy равенпоследовательность из 2-х кортежей;формат каждого 2-го кортежа (группа, элементы).Здесь сама группа представляет собой 2-кортеж (name1, name2), поэтому вложенная структура данных ((name1, name2), транзакции).

Теперь для каждого списка переходов выЯ захочу сложить сумму, при этом некоторые транзакции будут считаться «положительными», а некоторые - «отрицательными» в зависимости от того, совпадают ли они с порядком (name1, name2) или наоборот.То есть в первом списке транзакций те, в которых Алессио заплатил Лука, будут считаться положительными, а те, в которых Лука заплатил Алессио, будут считаться отрицательными.Сложите все эти значения, и если разница будет положительной, то отношение дебитор-кредитор будет следующим: «имя1 должно деньги имени2», в противном случае все наоборот.Например:

let result = grouped |> Seq.map (fun ((name1, name2), transactions) ->
    let spendTotal = transactions |> Seq.sumBy (fun t ->
        let mult = if t.Debitor = name1 then +1.0 else -1.0
        t.Spend * mult
    )
    let c, d = if spendTotal > 0.0 then name1, name2 else name2, name1
    { Activity = "_aggregate_"
      Creditor = c
      Debitor = d
      Spend = spendTotal }
)   

Теперь ваша последовательность выглядит примерно так:

[
    (("alessio", "luca"), luca gave alessio 3 net)
    (("alessio", "giulia"), alessio gave giulia 12 net)
]

Теперь мы хотим отбросить имена групп (пары (name1, name2)) и взять тольковторая часть каждого кортежа в последовательности.(Помните, что всеобъемлющей структурой последовательности является (group, transactions). F # имеет вспомогательную функцию, называемую snd, для получения второго элемента 2-кортежа. Поэтому следующий шаг в цепочке просто:

let finalResult = result |> Seq.map snd

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

let finalResult =
    transactions
    |> Seq.groupBy (fun t ->
        let c, d = t.Creditor, t.Debitor
        if c < d then c, d else d, c )
    |> Seq.map (fun ((name1, name2), transactions) ->
        let spendTotal = transactions |> Seq.sumBy (fun t ->
            let mult = if t.Debitor = name1 then +1.0 else -1.0
            t.Spend * mult
        )
        let c, d = if spendTotal > 0.0 then name2, name1 else name1, name2
        { Activity = "_aggregate_"
          Creditor = c
          Debitor = d
          Spend = spendTotal }
   |> Seq.map snd

ПРИМЕЧАНИЕ. Так как вы запросили «правильный функциональный способ сделать это»,Я написал это, используя синтаксис записи F # для ваших объектов данных. Записи F # предоставляют множество полезных функциональных возможностей по умолчанию, которые вы не получаете с классами, например, с уже написанными для вас функциями сравнения и хэш-кода. Плюс записи неизменяемыпосле создания, вам никогда не придется беспокоиться о параллелизме в многопоточной среде: если у вас есть ссылка на запись, никакой другой код не изменит ее из-под вас без предупреждения. Однако, если вы используете классы, тогдасинтаксис для создания класса будет другим.

ПРИМЕЧАНИЕ 2. Я толькооколо 90% уверены, что я получил правильный порядок кредитора / дебитора по всему коду.Протестируйте этот код, и если окажется, что я поменял их местами, поменяйте местами соответствующие части (например, строку let c, d = ...) моего кода.

Надеюсь, что это пошаговое построение решения поможетВы лучше понимаете, что делает код, и как делать вещи в правильном функциональном стиле.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...