F # Группировать или агрегировать последовательность записей / коллекцию по заданным критериям - PullRequest
0 голосов
/ 30 сентября 2011

Я довольно новичок в функциональном программировании, и поэтому F #, и у меня есть серьезные проблемы, чтобы найти правильное решение для этой проблемы.

У меня есть последовательность типов записей, скажем, как:

type Invoice = {
    GrpText : string
    GrpRef : int
    Article : int
    Wkz : int
    Text : string
    Price : decimal
    InvoiceRef : int
}

Теперь я хочу сгруппировать или агрегировать последовательность Invoices по заданным критериям и т.е. суммировать их цены.Invoices, который не соответствует критериям, не следует группировать и просто возвращать такими, какие они есть.

Функция критериев может выглядеть следующим образом:

/// determines whether to group the two given Invoice items or not
let criteria item toCompareWith =
    (item.GrpRef > 0 && item.Article = toCompareWith.Article
        && item.InvoiceRef = toCompareWith.InvoiceRef) ||
    (item.Wkz <> 0 && item.Text = toCompareWith.Text)

Агрегирование или группировка могут выглядеть следующим образомэто:

/// aggregate the given Invoice items
let combineInvoices item1 item2 =
    {item1 with Price = item1.Price + item2.Price; Wkz = 0}

Проблема выглядит довольно простой, но я в настоящее время недостаточно опытен в функциональном программировании для соединения точек.

Редактировать:

Я только что изменил функцию criteria, чтобы лучше показать, что она может быть немного сложнее, чем группировка по одному или нескольким полям.

Ответы [ 2 ]

2 голосов
/ 30 сентября 2011

Если я что-то не упустил, есть два шага: группировка и сокращение. Самый простой способ группировки - Seq.groupBy. Поскольку вы хотите использовать пользовательское равенство, вам нужно либо применить атрибут [<CustomEquality>] к вашему типу и переопределить Equals и GetHashCode, либо выполнить собственную функцию генерации ключей, которая использует вашу концепцию равенства. Вот пример последнего.

//custom key generator
let genKeyWith compare =
  let lookup = ResizeArray()
  fun item ->
    match Seq.tryFindIndex (compare item) lookup with
    | Some idx -> idx
    | None ->
      lookup.Add(item)
      lookup.Count - 1

Использование

let getKey = genKeyWith criteria

let invoices = Seq.init 10 (fun _ -> Unchecked.defaultof<Invoice>)

invoices 
|> Seq.groupBy getKey
|> Seq.map (fun (_, items) -> Seq.reduce combineInvoices items)
0 голосов
/ 30 сентября 2011

Примерно так: Defaultinvoice - это своего рода счет-фактура '0'

input 
|> Seq.groupBy (fun t -> t.Article) 
|> Seq.map (fun (a,b) -> a, (b |> List.fold (fun (c,d) -> combineInvoices c d) Defaultinvoice)

EDIT - для более сложной функции объединения.

Так что, если ваша функция объединения более сложна, лучше всегоПодход, вероятно, заключается в использовании рекурсии, и я думаю, что будет трудно избежать решения O (n ^ 2).Я хотел бы пойти с чем-то вроде

let rec overallfunc input =
    let rec func input (valid:ResizeArray<_>) =
        match input with
        |[] -> valid //return list
        |h::t -> 
            match valid.tryfindIndex (fun elem -> criteria h elem) with //see if we can combine with something
            |Some(index) -> valid.[index] <- combineInvoices (valid.[index]) h //very non-functional here
            |None -> valid.Add(h)
            func t valid //recurse here
    func input (ResizeArray<_>())

Это решение является как крайне нефункциональным, так и, вероятно, очень медленным, но оно должно работать для произвольно сложных комбинационных функций

...