让我们假设您需要将序列分组为一系列元组。每个元组都是一个关键* 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
答案 0 :(得分:4)
我不认为你可以将它写成递归函数,并且你会对自己施加一些限制 - 即:
'key * seq<'value>
,如果您将分组表示为实际的树类型(而不是从元组构建的特殊树),您可以有一些余地 - 这样您就可以使用定义良好的递归结果类型你的递归函数。
如果在那时你也能够在关键函数上做出妥协以使其同质化(最坏的情况 - 产生一个哈希码),你应该能够在类型系统中表达你想要的东西。
你当然可以拥有一个非递归分组函数,该函数采用分组序列并在其上放置另一级别的分组 - 如下所示:
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