是否可以在f#中编写这样的递归分组函数

时间:2017-02-14 08:52:19

标签: function recursion types f#

让我们假设您需要将序列分组为一系列元组。每个元组都是一个关键* seq。所以从某种意义上说,结果是一系列序列。

到目前为止一切都很标准。

如果您想通过其他键进一步对每个子序列进行分组,该怎么办?将另一个groupby函数映射到序列序列的每个元素上会很容易。然后,您将拥有一系列序列序列。

开始变得有点毛茸茸。

如果你想进一步分组怎么办?

是否可以编写一个可以接受密钥生成函数和任意序列的函数,并递归地展开图层,然后使用keyFunction添加另一层分组?

我怀疑答案是否定的,因为递归函数没有明确定义的类型。

我对此的尝试,进一步说明了这个想法:

let rec recursiveGrouper keyFunction aSeq =
            let first = Seq.head aSeq
            match first with
                | ((a:'a), _) -> Seq.map (fun (b,(c:seq<'c>)) -> (b, recursiveGrouper keyFunction c)) aSeq
                | _ -> Seq.groupBy keyFunction aSeq

编辑:

让我们添加一个如何工作的例子,它可能是

type FruitRecord = {Fruit:string; Number:int; SourceFarm:string; Grade:float}

let key1 fr =
    fr.Fruit

let key2 fr =
    fr.SourceFarm

let key3 fr =
    match fr.Grade with
    |f when f > 5.0 -> "Very Good"
    |f when f > 2.5 -> "Not bad"
    |_ -> "Garbage"

假设我们在序列中有一大堆水果记录。我们想按照水果的类型对它们进行分组。

一种方法是说

let group1 = fruitRecs |> Seq.groupBy key1

使用我们的递归函数,这将是

let group1 = recursiveGrouper key1 fruitRecs

接下来,假设我们要按源群组对group1组中的每个项目进行分组。

我们可以说

let group2 =
    group1
    |> Seq.map (fun (f, s) -> (f, Seq.groupBy key2 s))

使用我们的递归函数,它将是

let group2 = recursiveGrouper key2 group1

我们可以通过说

进一步分组
let group3 = recursiveGrouper key3 group2

2 个答案:

答案 0 :(得分:4)

我不认为你可以将它写成递归函数,并且你会对自己施加一些限制 - 即:

  1. 表示分组的元组'key * seq<'value>
  2. 异构关键功能(或其集合) - 我理解这是通过&#34;将每个子序列按一些其他键分组&#34;。
  3. 如果您将分组表示为实际的树类型(而不是从元组构建的特殊树),您可以有一些余地 - 这样您就可以使用定义良好的递归结果类型你的递归函数。

    如果在那时你也能够在关键函数上做出妥协以使其同质化(最坏的情况 - 产生一个哈希码),你应该能够在类型系统中表达你想要的东西。

    你当然可以拥有一个非递归分组函数,该函数采用分组序列并在其上放置另一级别的分组 - 如下所示:

    module Seq = 
        let andGroupBy (projection: 't -> 'newKey) (source: seq<'oldKey * seq<'t>>) = 
            seq { 
                for key, sub in source do
                    let grouped = Seq.groupBy projection sub
                    for nkey, sub in grouped do 
                        yield (key, nkey), sub
            }
    

    使用您的FruitRecord示例:

    values 
    |> Seq.groupBy key1
    |> Seq.andGroupBy key2
    |> Seq.andGroupBy key3
    

答案 1 :(得分:4)

实际上有一些方法可以使用静态约束使递归函数工作。这是一个小例子:

// If using F# lower than 4.0, use this definition of groupBy
module List =
    let groupBy a b = Seq.groupBy a (List.toSeq b) |> Seq.map (fun (a, b) -> a, Seq.toList b) |> Seq.toList

type A = class end // Dummy type
type B = class end // Dummy type
type C =
    inherit B    
    static member        ($) (_:C, _:A ) = fun keyFunction -> ()  // Dummy overload
    static member        ($) (_:C, _:B ) = fun keyFunction -> ()  // Dummy overload
    static member        ($) (_:B, aSeq) = fun keyFunction -> List.groupBy keyFunction aSeq // Ground case overload
    static member inline ($) (_:C, aSeq) = fun keyFunction -> List.map (fun (b, c) -> b, (Unchecked.defaultof<C> $ c) keyFunction) aSeq    

let inline recursiveGrouper keyFunction aSeq = (Unchecked.defaultof<C> $ aSeq) keyFunction

// Test code
type FruitRecord = {Fruit:string; Number:int; SourceFarm:string; Grade:float}

let key1 fr = fr.Fruit

let key2 fr = fr.SourceFarm

let key3 fr =
    match fr.Grade with
    |f when f > 5.0 -> "Very Good"
    |f when f > 2.5 -> "Not bad"
    |_ -> "Garbage"

let fruitRecs = [
    {Fruit = "apple" ; Number = 8; SourceFarm = "F"; Grade = 5.5}
    {Fruit = "apple" ; Number = 5; SourceFarm = "F"; Grade = 4.5}
    {Fruit = "orange"; Number = 8; SourceFarm = "F"; Grade = 5.5}
    ]

let group1 = recursiveGrouper key1 fruitRecs
let group2 = recursiveGrouper key2 group1
let group3 = recursiveGrouper key3 group2