2012-05-22 2 views
3

밀리 초 시간대의 기록 틱 데이터 시퀀스를 처리해야합니다. 능력은 특정 시간대 (시간, 분 등)의 오프닝 틱을 걸러 내야합니다. 연속열은 스팬보다 큰 ​​간격을 가질 수 있으므로 이러한 간격 이후의 첫 번째 틱은 첫 번째 틈으로 선택해야합니다. 그렇지 않으면 시작 틱은 해당 타임 라인의 달력 시작 부분에 가장 가까운 틱입니다.상태 정보를 전달하는 다른 관용적 방법이 있습니까?

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 

를 실행 한 다음, 내 마음에 오는

우선 호출 사이 전달하는 폐쇄로 틱 각각의 갭을 열거 나 간격 개방의 timespanId을 캡처 다음 상태 필터링 기능 opensTimespan:Timespan->(Timestamp->bool)입니다 다음과 같이 적용 할 수 있습니다

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

이 잘 작동하지만 opensTimespan 측면 효과를 갖는 것은 확실히 관용적 없습니다.

하나의 대안은 다음과 필터링 기능 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 %) 성능 저하와 동등한 결과를 얻을 수 있습니다.

순수한 기능적 방식으로이 작업에 접근하는 다른 방법에는 어떤 것이 있습니까?

감사합니다.

+2

나는 부작용이 opensTimespan 함수 내 국한되어있는 경우, 그것은 '관용적'하지 않을 수 있음을 말하고 싶지만, 그러나 성능을 확실하게 허용 . 핵심 F # 함수는 동일합니다. – Benjol

답변

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 

, 나는 다른 순수 솔루션에 대해 완전히 확실하지 않다 - 두 개의 인접 요소를 비교하기 때문에 첫 번째 요소와 정확히 똑같지는 않습니다 (그러나 데이터를 테스트 할 필요가 없습니다). (아마도 첫 번째 요소에서 건너 뛰기 여러 항목이 있습니까?)

+0

Tomas, 내 솔루션 (및 귀하와 Stephen 's도 마찬가지 임)은 결과가 현명하고 내 단위 테스트와 상반됩니다. 귀하의 시간은 매우 효과적입니다. 상태 기반 광산에 4 % 만 추가됩니다. 한편 슬라이딩 창에서 'Seq.pairwise'를 통해 진드기 튜플을 비교하여 광산을 개선했지만 여전히 1135 %의 시간을 보냈습니다 패널티. 내 유일한 관심사는 공간 고려 사항입니다. 테스트 데이터는 ~ 8mil의 가치가있는 틱으로 시간당 507을 산출합니다. 몇 년 동안의 진드기 데이터에 적용될 경우 중간 목록과 역순환이 귀하의 제안에 좋지 않은 영향을 미칠지 알아 보는 것은 흥미로운 일입니다. –

5

을 피할 수있게 해주는 Seq.scan을 사용하는 경우를 제외하고 Tomas의 솔루션 (실제로 필자는 그의 출발점, 의견 및 모두를 사용했습니다. (예를 들어 무한 틱 스트림을 처리 할 수 ​​있습니다).

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) 
+0

스티븐,'Seq.scan'의 아주 아름다운 응용 프로그램! 그러나 상당한 성능 저하를 낳습니다. 비교를 위해 나는 'Seq.toList'를 덧붙였다. (원래의 테이크는 모두 sequence-based이다.) Tomas의 폴드 기반의 4 %에 비해서 무려 28 % 나 증가했다. –

+0

@GeneBelitski - 감사합니다. 성능 향상을위한 아이디어로 내 대답에 대한 업데이트를 추가했습니다. –

+0

네, 시간이 개선되어 이제는 원래의 무국적 ~ 15 % 시간 벌금과 비슷한 수준에 머물 렀습니다. (이후 4 %의 벌금을 면도 한 이후 'Seq.windowed 2'에서 'Seq.pairwise'로 옮겼습니다. 아마도당신은 지금 당연히 더 적은 gen0 GC를 볼 수 있기 때문에 여분의 boxing/unboxing 원인에 대해 맞습니다. 감사합니다. –

관련 문제