直到改变序列为止

我有一个大的Excel文件,我用F#中的Excel提供程序读取。 行应该按某个列分组。 处理崩溃与OutOfMemoryException 。 不知道是否Seq.groupBy调用是有罪的或Exceltypes的提供者。 为了简化它,我使用3D Point作为一行。

 type Point = { x : float; y: float; z: float; } let points = seq { for x in 1 .. 1000 do for y in 1 .. 1000 do for z in 1 .. 1000 -> {x = float x; y = float y; z = float z} } let groups = points |> Seq.groupBy (fun point -> point.x) 

行已经按分组列sorting,例如x = 10的10个点,然后20个x = 20的点,等等。 而不是分组他们,我只需要拆分块中的行,直到更改。 有一种方法来枚举序列只是一次,并获得行序列分裂,不分组,由一些列值或某些f(行)值?

如果行已经sorting,那么这个chunkify函数将返回一个seq <'list>。 每个列表将包含具有相同x值的所有点。

 let chunkify pred s = seq { let values = ref [] for x in s do match !values with |h::t -> if pred hx then values := x::!values else yield !values values := [x] |[] -> values := [x] yield !values } let chunked = points |> chunkify (fun xy -> xx = yx) 

这里chunked有一种types

 seq<Point list> 

另一个解决scheme,跟凯文一样

 module Seq = let chunkBy f src = seq { let chunk = ResizeArray() let mutable key = Unchecked.defaultof<_> for x in src do let newKey = fx if (chunk.Count <> 0) && (newKey <> key) then yield chunk.ToArray() chunk.Clear() key <- newKey chunk.Add(x) } // returns 2 arrays, each with 1000 elements points |> Seq.chunkBy (fun pt -> pt.y) |> Seq.take 2 

这是一个纯粹的function性的方法,肯定是慢的,而且很难理解。

 module Seq = let chunkByFold f src = src |> Seq.scan (fun (chunk, (key, carry)) x -> let chunk = defaultArg carry chunk let newKey = fx if List.isEmpty chunk then [x], (newKey, None) elif newKey = key then x :: chunk, (key, None) else chunk, (newKey, Some([x]))) ([], (Unchecked.defaultof<_>, None)) |> Seq.filter (snd >> snd >> Option.isSome) |> Seq.map fst 

让我们从input开始

 let count = 1000 type Point = { x : float; y: float; z: float; } let points = seq { for x in 1 .. count do for y in 1 .. count do for z in 1 .. count -> {x = float x; y = float y; z = float z} } val count : int = 1000 type Point = {x: float; y: float; z: float;} val points : seq<Point> 

如果我们试图评估点,那么我们得到一个OutOfMemoryException:

 points |> Seq.toList System.OutOfMemoryException: Exception of type 'System.OutOfMemoryException' was thrown. at Microsoft.FSharp.Collections.FSharpList`1.Cons(T head, FSharpList`1 tail) at Microsoft.FSharp.Collections.SeqModule.ToList[T](IEnumerable`1 source) at <StartupCode$FSI_0011>.$FSI_0011.main@() Stopped due to error 

这可能是由于groupBy失败的原因,但我不确定。 但它告诉我们,我们必须使用seq和yield来返回组。 所以我们得到这个实现:

 let group groupBy points = let mutable lst = [ ] seq { for p in points do match lst with | [] -> lst <- [p] | p'::lst' when groupBy p' p -> lst <- p::lst | lst' -> lst <- [p]; yield lst' } val group : groupBy:('a -> 'a -> bool) -> points:seq<'a> -> seq<'a list> 

这不是最容易阅读的代码。 它将从点序列中获取每个点,并将其前置到累加器列表中,同时满足groupBy函数。 如果groupBy函数不满足,则生成新的累加器列表,并生成旧的累加器列表。 请注意,累加器列表的顺序是相反的。

testingfunction:

 for g in group (fun p' p -> p'.x = px ) points do printfn "%f %i" g.[0].x g.Length 

终止很好(一段时间后)。

其他实现错误修复和更好的格式。

 let group (groupBy : 'a -> 'b when 'b : equality) points = let mutable lst = [] seq { yield! seq { for p in points do match lst with | [] -> lst <- [ p ] | p' :: lst' when (groupBy p') = (groupBy p) -> lst <- p :: lst | lst' -> lst <- [ p ] yield (groupBy lst'.Head, lst') } yield (groupBy lst.Head, lst) } 

似乎没有一行纯function解决scheme或已经定义的Seq方法,我已经监督。

因此,作为一个替代scheme,我自己的必要解决scheme 比较@凯文的答案,但实际上满足更多的我的需要。 参考单元包含:

  • 组密钥,每行只计算一次
  • 当前块列表(可以是seq与Seq.groupBy一致),其中包含input顺序中f(x)等于存储组键(需要相等)的元素。

 let splitByChanged f xs = let acc = ref (None,[]) seq { for x in xs do match !acc with | None,_ -> acc := Some (fx),[x] | Some key, chunk when key = fx -> acc := Some key, x::chunk | Some key, chunk -> let group = chunk |> Seq.toList |> List.rev yield key, group acc := Some (fx),[x] match !acc with | None,_ -> () | Some key,chunk -> let group = chunk |> Seq.toList |> List.rev yield key, group } points |> splitByChanged (fun point -> point.x) 

该function具有以下签名:

  val splitByChanged : f:('a -> 'b) -> xs:seq<'a> -> seq<'b * 'a list> when 'b : equality 

我们欢迎您提供更正确的解决scheme