Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

F# - GroupBy and apply function to each property inside second tuple item

Tags:

group-by

map

f#

seq

I have a an F# list of classes for which I am using properties to access data (i'm using a library developed in C#). I would like to group by one property then apply a separate function to each property in the second item of the resulting tuple.

Example:

let grouped = list  |> Seq.groupBy (fun x -> x.Year) //group by the year property. Results in Seq<int * seq<myClass>>
                    |> Seq.map (fun (a, b) -> (a, //How to map generic functions to each remaining property in the second tuple?  

Hopefully this will make sense to someone. My second tuple item is a seq resulting from the groupBy. Each remaining property in MyClass needs to have a different function applying to it. In the past to sum a property i have just done something like:

|> Seq.map (fun (a, b) -> (a, b |> Seq.SumBy (fun x -> x.myProperty)))

I'd like to do something like this using Seq.map for several properties.

Many Thanks for any help at all, Richard

like image 388
Richard Todd Avatar asked Mar 11 '13 14:03

Richard Todd


1 Answers

You need to somehow specify the properties that you want to work with - the simplest way is to create a list of functions that read the properties. Assuming your type is MyType, you can write something like this:

let properties = [ (fun (x:MyType) -> x.MyProperty) ]

After you construct groups, you can then iterate over all properties in properties (using List.map or F# list comprehension) and caculate values |> Seq.sumBy prop where values is the group and prop is the current property:

let grouped = 
  list  
  |> Seq.groupBy (fun x -> x.Year) 
  |> Seq.map (fun (key, values) -> 
       (key, [for prop in properties -> values |> Seq.sumBy prop ])

If you need to use other aggregation functions than Seq.sumBy, then you can build a list of aggregating operations that you need to run (instead of a list of properties).

let properties = [ "MyPropSum", Seq.sumBy (fun (x:MyType) -> x.MyProperty);
                   "MyProp2Avg", Seq.averageBy (fun (x:MyType) -> x.MyProperty2) ]

To make further processing easier, I would probably build a dictionary with the results - this can be easily done by passing the list with name-value pairs to the dict function:

let grouped = 
  list  
  |> Seq.groupBy (fun x -> x.Year) 
  |> Seq.map (fun (key, values) -> 
       (key, dict [for name, aggregate in properties -> name, aggregate values ])
like image 76
Tomas Petricek Avatar answered Oct 01 '22 03:10

Tomas Petricek