我是一名 .Net 开发人员,但对 F# 和函数式编程很陌生。有人可以为我指出以下问题的正确方向:
我正在尝试遍历从 CSV 读取的一系列数据并构建一种汇总列表。伪代码是
type record = { Name:string; Time:DateTime;}
type summary = {Name:String; Start:DateTime; End:DateTime}
示例数据: (姓名时间)
- 10:01
- 10:02
- 10:03
- 早 11:15
- 早 11:25
- 早 11:30
- 中点 12:00
- 13:01
- 13:05
我正在尝试遍历序列并构建第二个序列:
Seq<Summary>
(名称开始结束)
- 10:01 10:03
- 早 11:15 11:30
- 中 12:00 12:00
- 13:01 13:05
我应该给 seq<record>
配管吗?到一个在 foreach
中迭代的函数风格,还是有更好的方法?我已经在 F# 中对数据进行了排序,因此数据按时间顺序排列。我不必担心它们会出现故障。
如果是 C#,我可能会执行类似(伪代码)的操作:
List<Summary> summaryData
foreach(var r in records)
{
Summary last = summaryData.LastOrDefault()
if(last == null)
{
summaryData.add( new Summary from r)
}
else
{
if(last.Name = r.Name)
{
last.End = r.Time
}
else
{
summaryData.add( new Summary from r)
}
}
非常感谢任何帮助!
最佳答案
除了声明性之外,函数式编程还包括抽象具体类型的基本要求的能力,即泛型编程。只要满足要求(F# 将它们称为约束),您的算法就会以通用方式适用,而与具体的数据结构无关。
正如您的问题描述中所述,您可能有一个序列(对象有序集合的最通用数据结构),可以从中提取 key 。这些键应进行不等式测试,因此存在等式约束。依靠序列的顺序,任何东西都不受约束。表示为 F# 签名,您的输入数据由 source:seq<'T>
描述和关键投影函数为projection:('T -> 'Key) when 'Key : equality
.
作为一个完整的函数签名,我建议projection:('T -> 'Key) -> source:seq<'T> -> seq<'T * 'T> when 'Key : equality
.返回一系列对避免引入额外的类型参数。它匹配输入,除了一些选择性的重新排列。这是此功能的可能实现,没有要求效率甚至正确性。请注意,'Key
上的等式约束是推断出来的,从未明确说明过。
let whenKeyChanges (projection : 'T -> 'Key) (source : seq<'T>) =
// Wrap in option to mark start and end of sequence
// and compute value of every key once
seq{ yield None
yield! Seq.map (fun x -> Some(x, projection x)) source
yield None }
// Create tuples of adjacent elements in order to
// test their keys for inequality
|> Seq.pairwise
// Project to singleton in case of the first and the
// last element of the sequence, or to a two-element
// sequence if keys are not equal; concatenate the
// results to obtain a flat sequence again
|> Seq.collect (function
| None, Some x | Some x, None -> [x]
| Some(_, kx as x), Some(_, ky as y)
when kx <> ky -> [x; y]
| _ -> [] )
// Create tuples of adjacent elements a second time.
|> Seq.pairwise
// Only the first and then every other pair will contain
// indentical keys
|> Seq.choose (fun ((x, kx), (y, ky)) ->
if kx = ky then Some(x, y) else None )
混凝土上的示例应用程序(X * string) list
,关键是 X。它在您的 seq<record>
上也同样有效。当 (fun r -> r.Name)
获取 key 时.
type X = A | B | C
[ A, "10:01"
A, "10:02"
A, "10:03"
B, "11:15"
B, "11:25"
B, "11:30"
C, "12:00"
A, "13:01"
A, "13:05" ] |> whenKeyChanges fst
// val it : seq<(X * string) * (X * string)> =
// seq
// [((A, "10:01"), (A, "10:03")); ((B, "11:15"), (B, "11:30"));
// ((C, "12:00"), (C, "12:00")); ((A, "13:01"), (A, "13:05"))]
关于F# 遍历集合并构建列表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/31595184/