F#按给定标准对记录序列/集合进行分组或聚合

时间:2011-09-30 07:53:05

标签: f# functional-programming grouping aggregate

我对函数式编程很陌生,因此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 个答案:

答案 0 :(得分:2)

除非我遗漏了某些内容,否则涉及两个步骤:分组和减少。最简单的分组方式是Seq.groupBy。由于您要使用自定义相等性,因此您需要将[<CustomEquality>]属性应用于您的类型并覆盖EqualsGetHashCode,或者使用您自己的密钥生成函数来使用您的相等概念。以下是后者的一个例子。

//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)

答案 1 :(得分:0)

类似这样的地方,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)

编辑 - 用于更复杂的组合功能。

因此,如果你的组合函数更复杂,最好的方法可能是使用递归,我认为很难避免使用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<_>())

这个解决方案非常缺乏功能,可能非常慢,但它适用于任意复杂的组合功能