2017-02-14 77 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

相關問題