3

ミリ秒の時間枠の一連の履歴ティックデータを処理する必要があります。特定の期間(毎時、毎分など)の開始ティックをフィルタリングする機能が必要です。シーケンスにはスパンよりも大きなギャップがある可能性があるため、そのようなギャップの後の最初のティックを開始ティックとして選択する必要があります。そうでない場合、開始ティックは、対応するタイムスパンのカレンダー開始のパスに最も近いものです。

最初に頭に浮かぶのは、次のステートフルフィルタリング関数opensTimespan:Timespan->(Timestamp->bool)です。これは、各ギャップオープニングまたはインターバルオープニングティックのtimespanIdを、呼び出し間を通過するためのクロージャーにキャプチャします。

let opensTimespan (interval: Timespan)=
    let lastTakenId = ref -1L  // Timestamps are positive
    fun (tickAt: Timestamp) -> 
        let tickId = tickAt / interval in
            if tickId <> !lastTakenId then lastTakenId := tickId; true
            else false

そしてこのように適用することができます:

let hourlyTicks = readTicks @"EURUSD-history.zip" "EURUSD-2012-04.csv"
                  |> Seq.filter (opensTimespan HOUR) |> Seq.toList

これは問題なく機能しますがopensTimespan、副作用があることは間違いなく慣用的ではありません。

代替案の1つは、ティックの決定が1つを開くかどうかの決定に、自己と前のタイムスタンプのペアだけで次のステートレスフィルタリング関数を作成する必要があるという事実を使用することopensTimespanF:Timespan->Timestamp*Timestamp->boolです。

let opensTimespanF interval (ticksPair: Timestamp*Timestamp) =
    fst ticksPair/ interval <> snd ticksPair/ interval

それは次のように適用できます:

let hourlyTicks= 
    seq {
        yield 0L;
        yield! readTicks @"EURUSD-history.zip" "EURUSD-2012-04.csv"
    }
    |> Seq.pairwise |> Seq.filter (opensTimespanF HOUR)
    |> Seq.map snd
    |> Seq.toList

純粋に機能的なこのアプローチでは、わずかな(〜11%)パフォーマンスの低下だけで同等の結果が得られます。

私が見逃しているかもしれない純粋な機能的な方法でこのタスクに取り組む他の方法は何ですか?

ありがとうございました。

4

2 に答える 2

5

純粋関数型の解決策は、fold関数を使用することです。このfold関数は、シーケンス(またはリスト)を処理し、いくつかの状態を蓄積するために使用されます。この例では、状態は、lastTakenId返される要素のリストであるため、次のタイプの状態を使用できますTimestamp * (Timestamp list)

let hourlyTicks = 
  readTicks @"EURUSD-history.zip" "EURUSD-2012-04.csv" 
  |> Seq.fold (fun (lastTakenId, res) tickAt ->
      // Similar to the body of your stateful function - 'lastTakenId' is the last
      // state and 'tickAt' is the current value. The 'res' list stores 
      // all returned elements
      let tickId = tickAt / HOUR 
      if tickId <> lastTakenId then  
        // We return new state for 'lastTakenId' and append current element to result
        (tickId, tickAt::res)
      else 
        // Here, we skip element, so we return the original state and original list
        (lastTakenId, res) ) (-1L, []) // Initial state: -1 and empty list of results

  // Take the second part of the state (the result list) and
  // reverse it, because it was accumulated in the opposite order
  |> snd |> List.rev

余談ですが、他の純粋なソリューションについては完全にはわかりません-隣接する2つだけを比較しているため、最初のソリューションとまったく同じことはできないと思います(ただし、テストするデータはありません)。要素(おそらく、最初の要素では、複数の項目をスキップできますか?)

于 2012-05-22T16:01:28.847 に答える
5

Tomasのソリューション(実際、私は彼を出発点、コメントなどすべてとして使用しました)と同様ですが、Seq.scanを使用すると、オンデマンドで結果を回避List.revして生成できます(たとえば、無限のティックストリームを処理できます)。

let hourlyTicks = 
  readTicks @"EURUSD-history.zip" "EURUSD-2012-04.csv" 
  |> Seq.scan (fun (lastTakenId,_) tickAt ->
      // Similar to the body of your stateful function - 'lastTakenId' is the last state
      // and 'tickAt' is the current value.
      let tickId = tickAt / HOUR 
      if tickId <> lastTakenId then  
        // We return new state for 'lastTakenId' and yield current 
        // element to the "scan stream"
        (tickId, Some(tickAt))
      else 
        // Here, we skip element, so we return the original tick id and 
        // yield None to the "scan stream"
        (lastTakenId, None) ) (-1L, None) // Initial state: -1 and None

  //yield all the snd elements of the "scan stream" where Option.isSome
  |> Seq.choose snd

(免責事項:あなたの質問で想定されているすべての依存関係を持っていないので、私はこれをテストしませんでした)。

コメントに応じて更新

あなたが見ているパフォーマンスのペナルティは、アキュムレータの値のボックス化/ボックス化解除によるものではないかと思います。以下が改善を示しているかどうかを知りたいと思います。

open System
open System.Collections.Generic
let hourlyTicks3 = 
  readTicks @"EURUSD-history.zip" "EURUSD-2012-04.csv" 
  |> Seq.scan (fun (kvp:KeyValuePair<_,_>) tickAt ->
      let lastTakenId = kvp.Key
      // Similar to the body of your stateful function - 'lastTakenId' is the last state
      // and 'tickAt' is the current value.
      let tickId = tickAt / HOUR 
      if tickId <> lastTakenId then  
        // We return new state for 'lastTakenId' and yield current 
        // element to the "scan stream"
        KeyValuePair<_,_>(tickId, Nullable<_>(tickAt))
      else 
        // Here, we skip element, so we return the original tick id and 
        // yield "null" to the "scan stream"
        KeyValuePair<_,_>(lastTakenId, Nullable<_>()) ) (KeyValuePair<_,_>(-1L, Nullable<_>())) // Initial state: -1 and "null"
  //yield all Values of KeyValuePair.Value elements of the "scan stream" where Nullable.HasValue
  |> Seq.filter (fun kvp -> kvp.Value.HasValue)
  |> Seq.map (fun kvp -> kvp.Value.Value)
于 2012-05-22T16:25:19.913 に答える