F# 遍历集合和构建列表
F# iterate over collection and build list
我是一名 .Net 开发人员,但对 F# 和一般的函数式编程不熟悉。有人可以为我指出以下问题的正确方向吗:
我正在尝试遍历我从 CSV 中读取的一系列数据并构建一种摘要列表。伪代码是
type record = { Name:string; Time:DateTime;}
type summary = {Name:String; Start:DateTime; End:DateTime}
示例数据:
(命名时间)
- 一个10:01
- 一个10:02
- 一个10:03
- B 11:15
- B 11:25
- B11:30
- C 12:00
- 一个13:01
- 一个13:05
我正在尝试遍历序列并构建第二个序列:
Seq<Summary>
(名称开始结束)
- 一个10:0110:03
- B 11:15 11:30
- C 12:00 12:00
- 一个13:0113: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#:
sequenceOfRecords
|> Seq.groupBy (fun r -> r.Name)
|> Seq.map (fun (name, records) ->
let times = Seq.map snd records
{ Name = name; Start = Seq.min times; End = Seq.max times })
如果你愿意,你也可以 return (name, min, max) 的元组。
除了声明性之外,函数式编程还包含抽象具体类型的基本要求的能力,即 泛型 编程。只要满足要求(F# 称之为约束),您的算法将以通用方式适用,与具体数据结构无关。
如您的问题描述所述,您可能拥有任何事物的序列(有序对象集合的最通用数据结构),可以从中提取密钥。应对这些键进行不平等测试,因此存在平等约束。依靠序列的顺序,任何东西都是不受约束的。表示为 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。当密钥由 (fun r -> r.Name)
获取时,它在您的 seq<record>
上同样有效。
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"))]
对于这种你必须在迭代时保持某种状态的集合进行迭代的问题,通常的方法是使用折叠或递归。不过,我这里倾向于使用unfold。
open System
type Record = { Name : string; Time : DateTime }
type Summary = { Name : String; Start : DateTime; End : DateTime }
let records = [ { Name = "A"; Time = DateTime(2015, 7, 24, 10, 1, 0) }
{ Name = "A"; Time = DateTime(2015, 7, 24, 10, 2, 0) }
{ Name = "A"; Time = DateTime(2015, 7, 24, 10, 3, 0) }
{ Name = "B"; Time = DateTime(2015, 7, 24, 11, 15, 0) }
{ Name = "B"; Time = DateTime(2015, 7, 24, 11, 25, 0) }
{ Name = "B"; Time = DateTime(2015, 7, 24, 11, 30, 0) }
{ Name = "C"; Time = DateTime(2015, 7, 24, 12, 0, 0) }
{ Name = "A"; Time = DateTime(2015, 7, 24, 13, 1, 0) }
{ Name = "A"; Time = DateTime(2015, 7, 24, 13, 5, 0) } ]
let createSummary records =
let times = records |> Seq.map (fun r -> r.Time)
{ Name = (Seq.head records).Name
Start = Seq.min times
End = Seq.max times }
let summarize records =
records
|> Seq.unfold (fun (restOfRecords : seq<Record>) ->
if Seq.isEmpty restOfRecords then None
else
let firstRecord = Seq.head restOfRecords
let belongsToSameGroup (r : Record) = firstRecord.Name = r.Name
let thisGroup = restOfRecords |> Seq.takeWhile belongsToSameGroup
let newRest = restOfRecords |> Seq.skipWhile belongsToSameGroup
Some (createSummary thisGroup, newRest) )
summarize records
|> Seq.iter (fun s -> printfn "Name: %s, Start: %s, End: %s" s.Name (s.Start.ToString()) (s.End.ToString()))
我是一名 .Net 开发人员,但对 F# 和一般的函数式编程不熟悉。有人可以为我指出以下问题的正确方向吗:
我正在尝试遍历我从 CSV 中读取的一系列数据并构建一种摘要列表。伪代码是
type record = { Name:string; Time:DateTime;}
type summary = {Name:String; Start:DateTime; End:DateTime}
示例数据: (命名时间)
- 一个10:01
- 一个10:02
- 一个10:03
- B 11:15
- B 11:25
- B11:30
- C 12:00
- 一个13:01
- 一个13:05
我正在尝试遍历序列并构建第二个序列:
Seq<Summary>
(名称开始结束)
- 一个10:0110:03
- B 11:15 11:30
- C 12:00 12:00
- 一个13:0113: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#:
sequenceOfRecords
|> Seq.groupBy (fun r -> r.Name)
|> Seq.map (fun (name, records) ->
let times = Seq.map snd records
{ Name = name; Start = Seq.min times; End = Seq.max times })
如果你愿意,你也可以 return (name, min, max) 的元组。
除了声明性之外,函数式编程还包含抽象具体类型的基本要求的能力,即 泛型 编程。只要满足要求(F# 称之为约束),您的算法将以通用方式适用,与具体数据结构无关。
如您的问题描述所述,您可能拥有任何事物的序列(有序对象集合的最通用数据结构),可以从中提取密钥。应对这些键进行不平等测试,因此存在平等约束。依靠序列的顺序,任何东西都是不受约束的。表示为 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。当密钥由 (fun r -> r.Name)
获取时,它在您的 seq<record>
上同样有效。
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"))]
对于这种你必须在迭代时保持某种状态的集合进行迭代的问题,通常的方法是使用折叠或递归。不过,我这里倾向于使用unfold。
open System
type Record = { Name : string; Time : DateTime }
type Summary = { Name : String; Start : DateTime; End : DateTime }
let records = [ { Name = "A"; Time = DateTime(2015, 7, 24, 10, 1, 0) }
{ Name = "A"; Time = DateTime(2015, 7, 24, 10, 2, 0) }
{ Name = "A"; Time = DateTime(2015, 7, 24, 10, 3, 0) }
{ Name = "B"; Time = DateTime(2015, 7, 24, 11, 15, 0) }
{ Name = "B"; Time = DateTime(2015, 7, 24, 11, 25, 0) }
{ Name = "B"; Time = DateTime(2015, 7, 24, 11, 30, 0) }
{ Name = "C"; Time = DateTime(2015, 7, 24, 12, 0, 0) }
{ Name = "A"; Time = DateTime(2015, 7, 24, 13, 1, 0) }
{ Name = "A"; Time = DateTime(2015, 7, 24, 13, 5, 0) } ]
let createSummary records =
let times = records |> Seq.map (fun r -> r.Time)
{ Name = (Seq.head records).Name
Start = Seq.min times
End = Seq.max times }
let summarize records =
records
|> Seq.unfold (fun (restOfRecords : seq<Record>) ->
if Seq.isEmpty restOfRecords then None
else
let firstRecord = Seq.head restOfRecords
let belongsToSameGroup (r : Record) = firstRecord.Name = r.Name
let thisGroup = restOfRecords |> Seq.takeWhile belongsToSameGroup
let newRest = restOfRecords |> Seq.skipWhile belongsToSameGroup
Some (createSummary thisGroup, newRest) )
summarize records
|> Seq.iter (fun s -> printfn "Name: %s, Start: %s, End: %s" s.Name (s.Start.ToString()) (s.End.ToString()))