简体   繁体   中英

Is it possible to write a recursive grouping function like this in f#

Lets say you had a requirement to group a sequence into a sequence of tuples. Each tuple is a key*seq. So in a sense, the result is a sequence of sequences.

All pretty standard so far.

What if you wanted to further group each sub sequence by some other key? It would be easy enough to map another groupby function onto each element of your sequence of sequences. You would then have a sequence of sequences of sequences.

Starting to get slightly hairy.

What if you wanted to group it even further?

Would it be possible to write a function that can take in a key generating function and an arbitrary sequence, and recursively unwraps the layers and then adds another layer of grouping using the keyFunction?

I suspect the answer is no, because the recursive function would not have a well defined type.

My attempt at this, to further illustrate the idea:

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

EDIT:

Lets add an example of how this might work, it it were possible

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"

Lets say we have a whole bunch of fruit records in a sequence. We want to group them by type of fruit.

One way would be to say

let group1 = fruitRecs |> Seq.groupBy key1

Using our recursive function, this would be

let group1 = recursiveGrouper key1 fruitRecs

Next, lets say we want to group each of the items in the groups of group1 by source farm.

We could say

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

Using our recursive function it would be

let group2 = recursiveGrouper key2 group1

And we could go further and group by Grade by saying

let group3 = recursiveGrouper key3 group2

I don't think you could write it as a recursive function with the sort of constraints you put on yourself - that is:

  1. A tuple 'key * seq<'value> representing the grouping,
  2. A heterogeneous key function (or a collection thereof) - this is what I understand by "group each sub sequence by some other key".

You could make some leeway if you would represent the grouping as an actual tree type (rather than an ad-hoc tree built from tuples) - that way you'd have a well-defined recursive result type to go with your recursive function.

If at that point you would be able to also compromise on the key function to make it homogeneous (worst case - producing a hashcode), you should be able to express what you want within the type system.

You certainly could have a non-recursive grouping function that takes a grouped sequence and puts another level of grouping on top of it - like the one below:

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
        }

Using your FruitRecord example:

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

Actually there are some ways to make that recursive function work, using static constraints. Here's a small example:

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

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM