我应该通过元素的属性将seq<a>
拆分为seq<seq<a>>
。如果此属性等于给定值,则必须在该点处“拆分”。我怎么能在 FSharp?
如果必须在该项目中拆分或者不拆分,那么将“函数”传递给它会返回一个bool应该不错。
样品:
输入序列:seq: {1,2,3,4,1,5,6,7,1,9}
当它等于1时,应该在每个项目上进行拆分,因此结果应为:
seq
{
seq{1,2,3,4}
seq{1,5,6,7}
seq{1,9}
}
答案 0 :(得分:10)
您所做的只是分组 - 每次遇到值时都会创建一个新组。
let splitBy f input =
let i = ref 0
input
|> Seq.map (fun x ->
if f x then incr i
!i, x)
|> Seq.groupBy fst
|> Seq.map (fun (_, b) -> Seq.map snd b)
let items = seq [1;2;3;4;1;5;6;7;1;9]
items |> splitBy ((=) 1)
再次,更短,斯蒂芬的改进很好:
let splitBy f input =
let i = ref 0
input
|> Seq.groupBy (fun x ->
if f x then incr i
!i)
|> Seq.map snd
答案 1 :(得分:4)
不幸的是,编写使用序列(seq<'T>
类型)的函数有点困难。它们不能很好地处理列表上的模式匹配等功能概念。相反,您必须使用GetEnumerator
方法和生成的IEnumerator<'T>
类型。这通常会使代码变得非常迫切。在这种情况下,我会写下以下内容:
let splitUsing special (input:seq<_>) = seq {
use en = input.GetEnumerator()
let finished = ref false
let start = ref true
let rec taking () = seq {
if not (en.MoveNext()) then finished := true
elif en.Current = special then start := true
else
yield en.Current
yield! taking() }
yield taking()
while not (!finished) do
yield Seq.concat [ Seq.singleton special; taking()] }
我不建议使用函数样式(例如使用Seq.skip
和Seq.head
),因为这是非常低效的 - 它创建了一系列序列,从其他序列获取值并返回它(因此通常有O(N ^ 2)复杂度。
或者,您可以使用计算构建器来编写此代码以使用IEnumerator<'T>
,但这不是标准的。你可以find it here,如果你想玩它。
答案 2 :(得分:4)
以下是一个不纯的实现,但是懒得产生不可变的序列:
let unflatten f s = seq {
let buffer = ResizeArray()
let flush() = seq {
if buffer.Count > 0 then
yield Seq.readonly (buffer.ToArray())
buffer.Clear() }
for item in s do
if f item then yield! flush()
buffer.Add(item)
yield! flush() }
f
是用于测试元素是否应该是分裂点的函数:
[1;2;3;4;1;5;6;7;1;9] |> unflatten (fun item -> item = 1)
答案 3 :(得分:2)
可能没有最有效的解决方案,但这有效:
let takeAndSkipWhile f s = Seq.takeWhile f s, Seq.skipWhile f s
let takeAndSkipUntil f = takeAndSkipWhile (f >> not)
let rec splitOn f s =
if Seq.isEmpty s then
Seq.empty
else
let pre, post =
if f (Seq.head s) then
takeAndSkipUntil f (Seq.skip 1 s)
|> fun (a, b) ->
Seq.append [Seq.head s] a, b
else
takeAndSkipUntil f s
if Seq.isEmpty pre then
Seq.singleton post
else
Seq.append [pre] (splitOn f post)
splitOn ((=) 1) [1;2;3;4;1;5;6;7;1;9] // int list is compatible with seq<int>
splitOn的类型是('a - &gt; bool) - &gt; SEQ&LT;'一&GT; - &GT; SEQ取代。我没有在许多输入上测试它,但它似乎有效。
答案 4 :(得分:0)
如果你正在寻找一些实际上像split一样工作的东西,就像字符串拆分一样(即谓词中没有包含该项,则谓词返回true)下面是我想出的...试图尽可能的功能:)
let fromEnum (input : 'a IEnumerator) =
seq {
while input.MoveNext() do
yield input.Current
}
let getMore (input : 'a IEnumerator) =
if input.MoveNext() = false then None
else Some ((input |> fromEnum) |> Seq.append [input.Current])
let splitBy (f : 'a -> bool) (input : 'a seq) =
use s = input.GetEnumerator()
let rec loop (acc : 'a seq seq) =
match s |> getMore with
| None -> acc
| Some x ->[x |> Seq.takeWhile (f >> not) |> Seq.toList |> List.toSeq]
|> Seq.append acc
|> loop
loop Seq.empty |> Seq.filter (Seq.isEmpty >> not)
seq [1;2;3;4;1;5;6;7;1;9;5;5;1]
|> splitBy ( (=) 1) |> printfn "%A"