我需要处理一系列毫秒时间帧的历史刻度数据。需要能够过滤某些时间盘(小时,分钟等)的开口刻度。序列可能有比间距更大的间隙,因此必须将此间隙后的第一个间距选为开口间隔,否则开口间距是最接近对应时间跨度的日历开始的间隔。
我想到的第一件事是以下有状态过滤函数opensTimespan:Timespan->(Timestamp->bool)
,它将每个间隙开放或间隔开放的时间跨度捕获到一个闭包中,以便在调用之间传递:
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
有副作用绝对不是惯用的。
一种替代方案可能是使用这样一个事实:刻度线上的决定是打开一个或不需要只需要自我和前一个时间戳的一对来提出以下无状态过滤功能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%)性能损失。
以纯粹的功能方式接近这项任务的其他方式我可能会失踪?
谢谢。
答案 0 :(得分: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
除此之外,我不完全确定你的其他纯粹的解决方案 - 我不认为它与第一个完全相同(但我没有要测试的数据),因为你只是比较两个相邻的元素(也许,在第一个元素中,你可以跳过多个项目?)
答案 1 :(得分:5)
像Tomas&#39;解决方案(事实上,我使用他作为我的起点,评论和所有),除了使用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)