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

发布于 2024-12-07 11:59:26 字数 986 浏览 2 评论 0原文

我对函数式编程还很陌生,因此 F# 和我很难为这个问题找到正确的解决方案。

我有一系列记录类型,例如:

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

现在我想按给定标准对发票序列进行分组或聚合,即对它们的价格求和。不符合条件的发票不应分组,而应按原样返回。

标准函数可能如下所示:

/// 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函数,以便更好地表明它可能比按一个或多个字段分组更复杂一些。

I am pretty new to functional programming and therefore F# and I have serious trouble to come up with the right solution for this problem.

I have got a sequence of record types, say like:

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

Now I want to group or aggregate the sequence of Invoices by a given criteria and i.e. sum their prices. Invoices that does not match the criteria should not be grouped and just returned as they are.

A criteria function might look like this:

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

The aggregation or grouping could look like this:

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

The problem looks kind of simple but I am currently not experienced enough in functional programming to connect the dots.

Edit:

I just modified the criteria function in order to better show that it might be a bit more complex than grouping by one or multiple fields.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

臻嫒无言 2024-12-14 11:59:26

除非我遗漏了什么,否则涉及两个步骤:分组和减少。最简单的分组方法是Seq.groupBy。由于您想使用自定义相等性,因此需要将 [] 属性应用于您的类型并覆盖 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)

Unless I'm missing something, there are two steps involved: group and reduce. The easiest way to group is Seq.groupBy. Since you want to use custom equality you need to either apply the [<CustomEquality>] attribute to your type and override Equals and GetHashCode, or roll your own key generating function that uses your concept of equality. Here's an example of the latter.

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

Usage

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)
心安伴我暖 2024-12-14 11:59:26

像这样的 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<_>())

该解决方案既没有功能,也可能非常慢,但它应该适用于任意复杂的组合函数

Something like this where Defaultinvoice is some sort of '0' invoice

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

EDIT - For more complicated combine function.

So if your combine function is more complicated, the best approach is probably to use recursion and I think it is going to be hard to avoid a O(n^2) solution. I would go with something like

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<_>())

This solution is both highly non-functional and probably very slow, but it should work for arbitrarily complicated combination functions

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文