2017-02-14 78 views
3

可以说你需要将一个序列分组为一个元组序列。每个元组都是一个密钥* seq。所以从某种意义上说,结果是一系列的序列。是否有可能在f#中编写像这样的递归分组函数?

迄今为止所有漂亮的标准。

如果你想进一步按其他一些密钥对每个子序列进行分组怎么办?将另一个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 

接下来,让我们说我们通过希望将每1组的组项目源农场。

我们可以说

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

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

let group2 = recursiveGrouper key2 group1 

而且我们可以进一步和组去了甲级说

let group3 = recursiveGrouper key3 group2 
+1

你能添加至少两个给定输入的期望输出的例子吗? – Gustavo

+0

你并不是真的想'recursiveGrouper'是递归的,然后检查编辑我的答案。 – scrwtp

回答

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 
+0

这似乎不适用于我的机器。我认为List.groupBy需要更改为Seq.groupBy,即使如此,我在recursiveGrouper的第二个应用程序上出现错误:类型'FruitRecord'与类型'string * seq ' –

+0

'不匹配是的,我使用了列表而不是seqs。如果你想我可以改变它seqs。但我发布的代码应该适合你,不是吗? – Gustavo

+0

对我而言,List没有groupBy。但我正在使用f#3.1,或许这在f#4上有所不同? –

4

我不认为你可以把它写成一个递归函数,用你自己的约束 - 即

  1. 元组'key * seq<'value>表示分组,
  2. 异构键功能(或集合物) - 这是我通过了解“基团通过一些其他键的每个子序列”。

你可以做一些回旋余地,如果你将代表分组作为一个实际的树类型(而不是从元组建立一个特设的树) - 这样你就会有一个明确的递归结果类型去与您的递归功能。

如果在那时你可以在关键函数上做出妥协来使它变得均匀(最坏的情况 - 生成一个哈希码),你应该能够在类型系统中表达你想要的。

你当然可以有一个非递归分组功能,需要一个分组序列,并把在它上面分组的另一个层面 - 像下面这样:

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 
+0

我认为有可能用静态约束来破解某些东西。我可以试试看。 – Gustavo

+1

@Gustavo:当然,这很有趣。这是我通常不会穿越的河流。你应该检查编辑的问题,虽然;) – scrwtp

+0

完成,看看有什么在河的另一边;) – Gustavo

相关问题