2010-07-24 19 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' ),但使用反射來創建這些功能,以便您不必輸入任何樣板。