我想获得一些关于如何使用F#/函数式编程来搜索列表中重复值的指针和/或示例代码。
考虑以下用例: 给定每个日期的列表/数组/日期序列和最高温度,我想提取出连续n天的临时值大于给定阈值的初始日期。
这种类型的查询的另一个例子是在股票价格历史表/列表中搜索超过给定阈值的价格,该阈值在指定的时间间隔内保持(例如至少30天)。 在这种情况下,我正在寻找首次超过阈值的初始日期。
TIA
答案 0 :(得分:1)
我可能会从一个效率较低但优雅的功能解决方案开始,该解决方案使用Seq.windowed
函数(将序列转换为指定大小的连续组的序列):
source
// Create groups of specified size
|> Seq.windowed requiredLength
// Add starting indices to the sequence
|> Seq.mapi (fun i v -> i, v)
// Find all groups that contain only numbers larger than treshold
|> Seq.filter (fun (i, v) -> v |> Seq.forall ((<) treshold))
// Get indices of such groups
|> Seq.map fst
这会返回所有这些组的索引,因此如果有多个重叠组(即匹配条件的较大序列),那么您将获得所有起始索引。您可能只是从结果中过滤连续数字以仅获取组的第一个索引(使用Seq.fold
)。
要获得更高效的版本,您需要编写一个遍历数组或列表的递归函数。当您在阈值上找到最后一个值时,您可能需要记住(在函数参数中)。 (这与命令式循环基本相同,除了你使用递归函数并在参数中保持状态)。
答案 1 :(得分:1)
虽然我喜欢Tomas代码的简洁性,但我不禁认为他所暗示的更有效的版本在这里是非常强制的,特别是如果实际比较逻辑比简单的整数比较更昂贵。我提交以下抽象:
let findWindowBeginnings predicate minWindowSize data =
if minWindowSize < 2 then
invalidArg "minWindowSize" "minWindowSize must be greater than 1"
((None, []), data)
||> Seq.fold (fun (window, acc) x ->
if predicate x then
match window with
| Some (start, size) -> let size' = size + 1
let acc' = if size' = minWindowSize
then start::acc
else acc
Some (start, size'), acc'
| _ -> Some (x, 1), acc
else None, acc)
|> snd
|> List.rev
日期+温度元组序列的用例如下:
let findHeatwaveBeginnings tempThreshold consecutiveDays data =
(consecutiveDays, data)
||> findWindowBeginnings (snd >> (<) tempThreshold)
// alternatively, if you're not a fan of point-free style code:
// findWindowBeginnings (fun (_, maxTemp) -> maxTemp > tempThreshold)
|> List.map fst
因为findWindowBeginnings
由Seq.fold
驱动,所以它当然会与数组和列表一起使用。此外,findWindowBeginnings
对于正在检查的数据类型是完全不可知的,因为您传入的谓词执行数据自省,谓词当然可以处理您喜欢的任何数据类型(元组,记录,适当的类/结构,等等。)。唯一的要求是输入数据按逻辑排序。
F#Snippets链接:http://fssnip.net/3u