F # - выполнение недетерминированных группировок по спискам - PullRequest
1 голос
/ 24 июля 2010

Я работаю над проблемой, которую знаю, что могу решить с помощью C #.Я хотел бы доказать своему боссу, что F # сможет решить более лаконично.Однако мое понимание функционального программирования все еще довольно незрелое.

Проблема:

Я работаю со списком классов 'Trade'.Определение класса следующее:

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»).

Любая помощь / указатели с созданием функционально ориентированного решения этой проблемы будут приветствоваться.

Большое спасибо.

Ответы [ 2 ]

9 голосов
/ 24 июля 2010

Если я правильно понимаю проблему, то вы, по сути, хотите вызвать функцию Seq.groupBy. Проблема в том, что вы не совсем знаете лямбда-функцию, которую хотите передать в качестве аргумента при написании кода, потому что функция может варьироваться в зависимости от выбора ключей, которые следует использовать для группировки. Вот один из относительно простых способов сделать это ...

Мы создадим словарь функций, который даст нам функцию для чтения указанного свойства 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, хотя это выглядело бы немного уродливее из-за более тяжелого синтаксиса ...

EDIT 1 : Хорошая особенность этого подхода в том, что вам не нужно использовать какие-либо отражения. Все работает как скомпилированный код, поэтому он должен быть довольно эффективным. Составленная функция просто вызывает несколько других функций (методы .NET) и упаковывает возвращаемые значения ...

РЕДАКТИРОВАТЬ 2 : Что касается порядка - этот подход будет работать (при сравнении кортежей сначала сравниваются первые элементы), но я не совсем уверен, в каком порядке агрегируются элементы при использовании Seq.reduce, так что, может быть, этот пример работает наоборот ...

4 голосов
/ 24 июля 2010

Как насчет чего-то подобного?

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, путем создания функций заранее (как решение Томаса), но использует отражение для созданияэти функции, так что вам не нужно вводить в любой шаблон.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...