2010-07-24 36 views
1

我正在处理一个我知道可以用C#解决的问题。我想向我的老板证明F#能够以更简洁的方式解决问题。然而,我对函数式编程的理解还很不成熟。F# - 在列表上执行非确定性分组

问题:

我用的“贸易”类的列表的工作。所述类的定义如下:

type Trade(brokerId : string, productId : string, marketId : string, buySideId : string, tradeDate : string, ruleId : int) = class 

    member this.BrokerId = brokerId 
    member this.ProductId = productId 
    member this.MarketId = marketId 
    member this.BuySideId = buySideId 
    member this.TradeDate = tradeDate 
end 

我需要能够组的交易,然后应用规则以每个所得​​的数据组的。

但是我不能保证该数据的分组,即确定分组将有可能改变每次程序运行时的规则 - 所以比如我可能要组:

  • TradeDate,BrokerId
  • TradeDate只有
  • TradeDate,BrokerId,ACCOUNTID

...等等。

一旦我有了不同的群体,应用规则(比如'总TradeAmount大于10,000')就很容易(我认为)。

任何帮助/指针与创建一个功能导向的解决方案,这个问题将非常受欢迎。

非常感谢。

+0

在程序的任何运行过程中,你如何知道要分组?你几乎可以肯定地用'Seq.groupBy'来做你想要的... – kvb 2010-07-24 03:51:57

+0

这些分组将在数据库中进行配置。所以我会返回一个属性名称列表 - 这将不得不按照给定的顺序应用。 – Peanut 2010-07-24 03:54:25

回答

9

如果我正确地理解了这个问题,那么你基本上想要调用Seq.groupBy函数。问题在于,在编写代码时,您并不完全知道要将其作为参数传递给它的lambda函数,因为函数可能因应用于分组的键的选择而异。这是一个相对简单的方法...

我们将创建一个函数字典,它为我们提供了一个读取Trade的指定属性的函数(原则上这可以自动构造,但它可能更容易只写):

let keyfunctions : IDictionary<string, Trade -> obj> = 
    dict [ "TradeDate", (fun t -> box t.TradeDate); 
     "BrokerId", (fun t -> box t.BrokerId); 
     "MarketId", (fun t -> box t.MarketId); ] 

现在,如果我们想用多个按键,我们需要一种方法来合并两个功能,让我们的关键部分成一个单一的功能。我们可以写一个组合子,它有两个功能,并返回一个一个产生盒装元组的关键:

let combine f1 f2 = (fun t -> box (f1 t, f2 t)) 

如果你有一个字符串列表,指定你的钥匙,那么你只需要在挑选功能字典每个键的使用combine它们组合成一个单一的功能:

let grouping = [ "TradeDate"; "MarketId" ] 
let func = grouping |> Seq.map (fun n -> keyfunctions.[n]) |> Seq.reduce combine 

现在你有一个可以作为参数传递给Seq.groupBy功能:

trades |> Seq.groupBy func 

在F#中可能还有其他方法可以做到这一点,但我认为这是一个相对简单的方法,可以说服你的老板:-)。作为一个侧面说明,你基本上可以写C#3.0中同样的事情,尽管它看起来有点丑陋由于更重语法...

编辑1:关于这种方法的好处是,你不需要使用任何反射。一切都以编译代码的形式运行,所以它应该非常高效。将所组成的功能只是调用其他几项功能(.NET方法)和箱子返回值...

编辑2:关于订单 - 这种方法将工作(元组进行比较时,第一要素首先比较) ,但我不完全确定在使用Seq.reduce时物品在哪个订单中汇总的顺序,所以也许这个示例可以用另一种方式运行...

+0

嗨Tomas - 非常感谢。我会试试看,并会告诉你它是如何发生的。 – Peanut 2010-07-24 04:26:52

+0

完美地工作 - 谢谢。 – Peanut 2010-07-24 05:31:38

4

这样的事情呢?

open System.Reflection 

let getProp obj prop = 
    obj.GetType().GetProperty(prop).GetValue(obj,null) 

let groupByProps props = 
    Seq.groupBy (fun obj -> List.map (getProp obj) props) 

然后,你可以做trades |> groupByProps ["BrokerId"; "RuleId"]

编辑

对于略少简明而更好的性能解决方案,您可以改为尝试这个办法:

open System.Reflection 
open System.Linq.Expressions 

let propReader<'t> (prop:PropertyInfo) = 
    let param = Expression.Parameter(typeof<'t>, "x") 
    Expression.Lambda<System.Converter<'t,obj>>(Expression.Convert(Expression.Property(param, prop),typeof<obj>), [| param |]).Compile() 
    |> Microsoft.FSharp.Core.FuncConvert.ToFSharpFunc 

let propMap<'t>() = 
    typeof<'t>.GetProperties() 
    |> Seq.map (fun prop -> prop.Name, propReader<'t> prop) 
    |> dict 

let tradeMap = propMap<Trade>() 

let groupByProps = 
    fun props -> Seq.groupBy (fun obj -> List.map (fun prop -> tradeMap.[prop] obj) props) 

这每次通过提前创建函数来调用groupByProps函数时都避免使用反射(如Tomas' ),但使用反射来创建这些功能,以便您不必输入任何样板。