我对函数式编程很陌生,因此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
函数,以便更好地表明它可能比通过一个或多个字段分组更复杂。
答案 0 :(得分:2)
除非我遗漏了某些内容,否则涉及两个步骤:分组和减少。最简单的分组方式是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)
答案 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<_>())
这个解决方案非常缺乏功能,可能非常慢,但它适用于任意复杂的组合功能