【问题标题】:Fastest way to merge lists that have a common field?合并具有共同字段的列表的最快方法?
【发布时间】:2011-09-25 10:32:43
【问题描述】:

我正在学习 F#,并且正在使用赔率比较服务 (ala www.bestbetting.com) 将理论付诸实践。 到目前为止,我有以下数据结构:

type price = {    Bookie : string;    Odds : float32;    }

type selection = {
    Prices : list<price>;
    Name : string;
    }

type event = {    Name : string;    Hour : DateTime;    Sport : string;    Selections : list<selection>;    }

所以,我有几个来自多个来源的“事件”。而且我需要一种非常快速的方法来合并具有相同名称和时间的事件,并且一旦完成合并具有相同名称的不同选择的价格。

我考虑过获取第一个列表,然后对其他列表进行逐一搜索,当指定的字段匹配时返回一个包含两个列表合并的新列表。

我想知道是否有更快的方法来执行此操作,因为性能很重要。我已经看到了这个Merge multiple lists of data together by common ID in F# ...虽然这很有帮助,但我要求最好的性能解决方案。也许使用任何其他结构,它不是列表或其他合并它们的方式......所以任何建议将不胜感激。

谢谢!

【问题讨论】:

  • 我将块引用更改为代码,以便突出显示语法。
  • 顺便说一句,我在发布之前已经看到了这个stackoverflow.com/questions/4787226/…...虽然这很有帮助,但我要求最好的性能解决方案。谢谢。
  • @Jacobo:我认为您应该编辑问题以表明..
  • @Dan Abramov 非常感谢!你是怎么做到的?我尝试了但无法完成,所以我只是认为不支持 F# 之类的突出显示...再次感谢您。
  • 为什么您引用的解决方案不可接受?性能需要提高多少?理想的解决方案将涉及一个哈希表,groupBy 会为您完成。

标签: f#


【解决方案1】:

正如 Daniel 在评论中提到的,关键问题是,与基于标准 Seq.groupBy 函数的解决方案相比,性能需要提高多少?如果您有大量数据要处理,那么实际上使用一些数据库来实现此目的可能更容易。

如果您只需要大约 1.7 倍的速度(或者可能更多,具体取决于内核数量 :-)),那么您可以尝试将 Seq.groupBy 替换为基于 F# PowerPack 中可用的并行 LINQ 的并行版本。使用 PSeq.groupBy(和其他 PSeq 函数),您可以编写如下内容:

#r "FSharp.PowerPack.Parallel.Seq.dll"
open Microsoft.FSharp.Collections

// Takes a collection of events and merges prices of events with the same name/hour
let mergeEvents (events:seq<event>) = 
  events 
  |> PSeq.groupBy (fun evt -> evt.Name, evt.Hour)
  |> PSeq.map (fun ((name, hour), events) ->
      // Merge prices of all events in the group with the same Selections.Name
      let selections = 
        events 
        |> PSeq.collect (fun evt -> evt.Selections)
        |> PSeq.groupBy (fun sel -> sel.Name)
        |> PSeq.map (fun (name, sels) ->
            { Name = name
              Prices = sels |> Seq.collect (fun s -> s.Prices) |> List.ofSeq } )
        |> PSeq.toList
      // Build new Event as the result - since we're grouping just using 
      // name & hour, I'm using the first available 'Sport' value 
      // (which may not make sense)
      { Name = name
        Hour = hour
        Sport = (Seq.head events).Sport
        Selections = selections })   
  |> PSeq.toList

我没有测试这个版本的性能,但我相信它应该更快。您也不需要引用整个程序集 - 您可以从 PowerPack source code 复制几个相关函数的源代码。上次检查时,函数标记为inline时性能更好,而当前源代码中并非如此,因此您可能也想检查一下。

【讨论】:

  • 您好,很抱歉未能早点回答;这是非常紧张的日子。我知道我应该提到它......我想要一种更快的方法,但也非常重视代码的可读性,所以你的答案似乎非常合适。由于我是 F# 的新手,我还没有使用过 F# Power Pack 库,尽管我看到很多地方都引用了它。非常感谢您花时间将示例代码放入其中,因为它是学习过程中非常有价值的见解。
  • 顺便说一句,Tomas,虽然不是这个地方,但感谢这本关于 F# 的好书。你的和 Don Syme 的是我正在使用的,因为我发现它们同样很棒,但风格不同。很高兴能读到你的书,我只是希望我现在能有更多的时间专注于这项任务。干杯。
  • @Jacobo - 感谢您对这本书的好话!我希望答案是有用的,但是(与并行编程一样)您需要测量性能以查看它是否真的有帮助(在您的特定情况下)。
【解决方案2】:

我还没有测试过,但我认为这会起作用。

let events = List.init 10 (fun _ -> Unchecked.defaultof<event>) //TODO: initialize to something meaningful

for ((name, hour), evts) in (events |> Seq.groupBy (fun e -> e.Name, e.Hour)) do
  printfn "Name: %s, Hour: %A" name hour
  let prices = 
    seq {
      for e in evts do
        for s in e.Selections do
          for p in s.Prices do
            yield s.Name, p 
    }
    |> Seq.groupBy fst

  for (selectionName, p) in prices do
    printfn "  Selection Name: %s" selectionName
    for (_, price) in p do
      printfn "    %A" price

【讨论】:

  • 您好 daniel,感谢您抽出宝贵时间编写示例代码。由于我是这种语言的新手,我可能会遗漏一些东西,但是,有什么理由比上面代码的并行 PSeq.GroupBy 更快吗?我想我遵循代码的作用,但看不出它为什么会更快。有什么我可能不知道的吗?不过,我会尝试并进行一些比较来满足我的好奇心。再次感谢您的帮助。
  • @Jacobo:不,没有理由说这应该比并行版本更快,尽管并行性是有代价的,在某些情况下,这可能会抵消好处。正如托马斯所说,唯一确定的方法就是测量。
猜你喜欢
  • 1970-01-01
  • 2021-03-15
  • 2017-10-17
  • 1970-01-01
  • 2021-02-09
  • 2010-11-25
  • 2021-07-06
  • 2022-01-25
  • 2012-04-03
相关资源
最近更新 更多