在F#中匹配两个数组的元素

时间:2013-09-06 16:50:50

标签: arrays f# pattern-matching tuples sequence

我有两个库存数据序列,我正在尝试排列日期并合并数据,以便我可以将其传递给其他将运行一些统计数据的函数。基本上,我想传递两个(或更多)看起来像这样的序列:

sequenceA = [(float,DateTime)]
sequenceB = [(float,DateTime)]

到函数,并让它返回一个序列,其中所有数据都由DateTime正确对齐。类似的东西:

return = [(float,float,DateTime)]

其中浮点数是该DateTime的两个序列的收盘价。

我已经尝试过使用嵌套的for循环,我相当确定它应该可以工作(尽管我遇到了一些麻烦),但似乎F#的匹配表达式也应该能够处理这个问题。我查了一些匹配表达式的文档和示例,但是我遇到了许多我无法解决的问题。

这是我最近尝试完成我想要完成的简化版本。如您所见,我只是想看看序列'x'的第一个元素是否具有日期“1/11/2011”。问题是1)它总是返回“是”,2)我无法弄清楚如何从这里到整个序列,然后最终2+序列。

let x = seq[(1.0,System.DateTime.Parse("1/8/2011"));(2.0,System.DateTime.Parse("1/9/2011"))]
type t = seq<float*DateTime>    

let align (a:t) =
    let testDate = System.DateTime.Parse("1/11/2011")
    let b = Seq.head(a)
    match snd b with
        | testDate -> printfn "Yes"
        | _ -> printfn "No"

align x

我对F#比较新,但我很确定这应该可以通过匹配表达式实现。任何帮助将不胜感激!

3 个答案:

答案 0 :(得分:2)

您的问题分为两部分:

对于模式匹配,在上面的模式中,testDate是一个名称,它将绑定到元组b中的第二个项目。两种模式都匹配任何日期,但自第一种模式匹配以来,您的示例始终打印“是”。

如果您想匹配特定的日期值,可以在模式中使用'when'关键字:

let dateValue = DateTime.Today

match dateValue with
| someDate when someDate = DateTime.Today -> "Today"
| _ -> "Not Today"

如果我必须实现对齐功能,我可能不会尝试使用模式匹配。您可以使用Seq.groupBy收集具有相同日期的所有条目。

///Groups two sequences together by key
let align a b =

    let simplifyEntry (key, values) =
        let prices = [for value in values -> snd value]
        key, prices

    a 
    |> Seq.append b
    |> Seq.groupBy fst
    |> Seq.map simplifyEntry
    |> Seq.toList

//Demonstrate alignment of two sequences
let s1 = [DateTime.Today, 1.0]
let s2 = [
    DateTime.Today, 2.0 
    DateTime.Today.AddDays(2.0), 10.0]

let pricesByDate = align s1 s2

for day, prices in pricesByDate do
    let pricesText =
        prices
        |> Seq.map string
        |> String.concat ", "
    printfn "%A %s" day pricesText

答案 1 :(得分:0)

我碰巧正在处理一个用于处理时间序列数据的库,并且它具有执行此操作的功能 - 它实际上更通用,因为它返回DateTime * float option * float option来表示一个系列具有的情况指定日期的值,但另一个不是。

该函数假定两个系列已经排序 - 这意味着它只需要遍历它们一次(对于未排序的序列,您需要进行多次迭代或构建一些临时表)。

另请注意,参数的交换比示例中的要多。你需要给它DateTime * float。该函数不是特别好 - 它在IEnumerable中工作,这意味着它需要使用可变的枚举器(和一般的丑陋的命令式东西)。一般来说,模式匹配对序列不起作用 - 你可以得到 head ,但你不能得到 tail - 因为这样效率很低。你可以为F#列表编写更好的文件......

open System.Collections.Generic

let alignWithOrdering (seq1:seq<'T * 'TAddress>) (seq2:seq<'T * 'TAddress>) (comparer:IComparer<_>) = seq {
  let withIndex seq = Seq.mapi (fun i v -> i, v) seq
  use en1 = seq1.GetEnumerator()
  use en2 = seq2.GetEnumerator()
  let en1HasNext = ref (en1.MoveNext())
  let en2HasNext = ref (en2.MoveNext())
  let returnAll (en:IEnumerator<_>) hasNext f = seq { 
    if hasNext then
      yield f en.Current
      while en.MoveNext() do yield f en.Current }

  let rec next () = seq {
    if not en1HasNext.Value then yield! returnAll en2 en2HasNext.Value (fun (k, i) -> k, None, Some i)
    elif not en2HasNext.Value then yield! returnAll en1 en1HasNext.Value (fun (k, i) -> k, Some i, None)
    else
      let en1Val, en2Val = fst en1.Current, fst en2.Current
      let comparison = comparer.Compare(en1Val, en2Val)
      if comparison = 0 then 
        yield en1Val, Some(snd en1.Current), Some(snd en2.Current)
        en1HasNext := en1.MoveNext()
        en2HasNext := en2.MoveNext()
        yield! next()
      elif comparison < 0 then
        yield en1Val, Some(snd en1.Current), None
        en1HasNext := en1.MoveNext()
        yield! next ()
      else 
        yield en2Val, None, Some(snd en2.Current)
        en2HasNext := en2.MoveNext() 
        yield! next () }
  yield! next () }

假设我们想要使用字符串作为键(而不是你的DateTime),你可以像这样调用它:

alignWithOrdering 
    [ ("b", 0); ("c", 1); ("d", 2) ] 
    [ ("a", 0); ("b", 1); ("c", 2) ] (Comparer<string>.Default) |> List.ofSeq

// Returns 
[ ("a", None, Some 0); ("b", Some 0, Some 1); 
  ("c", Some 1, Some 2); ("d", Some 2, None) ]

如果您有兴趣使用F#中的时间序列库存数据,您可能有兴趣加入F# Foundation F#for Data and Machine Learning 工作组。我们目前正在开发一个支持时间序列的开源库,这使得这个更好:-)。如果你有兴趣看看&amp;有助于早期预览,那么你可以通过这个工作组来做到这一点。

答案 2 :(得分:0)

open System
let x = seq[(1.0,System.DateTime.Parse("1/8/2011"));(2.0,DateTime.Parse("1/9/2011"))]
//type t = seq<float*DateTime>    
let (|EqualDate|_|) str dt=
  DateTime.TryParse str|>function
   |true,x when x=dt->Some()
   |_->None
let align a =
    //let testDate = System.DateTime.Parse("1/11/2011")
    let b = Seq.head(a)
    match b with
        |_,EqualDate "1/9/2011" -> printfn "Yes"
        | _ -> printfn "No"

align x
x|>Seq.skip 1|>align