代码之家  ›  专栏  ›  技术社区  ›  Peanut

F#-对列表执行非确定性分组

  •  1
  • Peanut  · 技术社区  · 14 年前

    我正在处理一个我知道可以用C#解决的问题。我想向我的老板证明,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
    

    但是,我不能保证数据的分组,也就是说,每次运行程序时,确定分组的规则都可能发生变化,因此,例如,我可能必须按以下方式分组:

    • 交易日期,BrokerId
    • 仅限交易日期
    • 交易日期,经纪人,账号

    ... 等等。

    一旦我有了不同的组,就很容易(我认为)应用一个规则(比如总交易额大于10000)。

    任何关于创建一个面向功能的解决方案来解决这个问题的帮助/建议都是非常受欢迎的。

    非常感谢。

    2 回复  |  直到 14 年前
        1
  •  9
  •   Tomas Petricek    14 年前

    如果我正确地理解了问题,那么您基本上需要调用 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
    

    序列groupBy

    trades |> Seq.groupBy func
    

    在F#中可能还有其他方法可以做到这一点,但我认为这是一个相对简单的方法,可以说服你的老板:-)。顺便说一句,您可以在C#3.0中编写基本相同的东西,不过由于语法更重,它看起来更难看一些。。。

    编辑1 :这种方法的一个优点是不需要使用任何反射。所有的东西都以编译代码的形式运行,所以应该非常高效。组合函数只调用其他几个函数(.NET方法)并将返回的值装箱。。。

    编辑2 :关于顺序-这种方法会起作用(在比较元组时,首先比较第一个元素),但我不完全确定在使用时聚合项的顺序 Seq.reduce

        2
  •  4
  •   kvb    14 年前

    像这样的怎么样?

    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)
    

    这避免了每次通过提前创建函数(如Tomas的解决方案)调用groupByProps函数时都使用反射,而是使用反射来创建这些函数,这样就不必输入任何样板文件。