我有一些Seq<'T>我需要一个功能:
Seq.block : int -> Seq<'T> -> Seq<Seq<'T>>
其中结果序列是原始序列,以固定长度的块分割。
所以,例如Seq.block 2 {1; 2; 3; 4; 5}必须返回{{1; 2}; {3; 4}; {5}}
问题在于,它必须同时适用于有限和无限序列。我有各种各样的尝试,最后一个是工作,但我希望这可以做得更清洁。我已经离开了我在这里的各种非工作尝试,希望这对某人也有帮助。
我的第一次尝试是:
let block size = Seq.mapi (fun i x -> (i/size,x))
>> Seq.groupBy fst
>> Seq.map (fun (_,res) -> Seq.map snd res)
这适用于有限序列,但不适用于无限序列(groupBy不起作用,回想起来很明显)。
作为我试过的第二次尝试(希望折叠会懒散地运作)
let block size = Seq.mapi (fun i x -> (i/size,x))
>> Seq.fold (fun (accIx,fullSeq,buildSeq) (ix,el) ->
if accIx = ix then (accIx,fullSeq, Seq.append buildSeq (Seq.singleton el))
else (accIx+1,Seq.append fullSeq (seq {yield buildSeq}),Seq.singleton el))
(0, Seq.empty,Seq.empty)
>> fun (_,full,rem) -> Seq.append full (seq { yield rem})
但这甚至更糟糕(在第一次尝试有效的测试示例中引发了stackoverflow,尽管我的版本可能已经失去了改进的空间)。
对于尝试#3,我做了:
let block size x = Seq.initInfinite (fun i -> x |> Seq.skip (i*size)
|> Seq.take size)
这实际上适用于无限序列,但是a。它不适用于有限序列和b。当你在序列的尾部进一步走出时,可能会遇到性能问题。我已经知道的递归尝试#4注定要失败,但我想我还是试一试:
let rec block size x =
Seq.append (seq { yield Seq.take size x})
(block size (Seq.skip size x))
尝试#5几乎有效:
let block size = Seq.windowed size
>> Seq.mapi (fun i x -> (i,x))
>> Seq.filter (fun (i,_) -> i % size = 0)
>> Seq.map (fun (_,x) -> Array.toSeq x)
但有(轻微)缺点,对于有限序列,当序列长度不能被'size'整除时,最后一个短元素不在列表中。接下来,由于生成了所有数组,我对性能略感担忧。
稍微考虑一下,我想出了尝试#6:
let block size = Seq.mapi (fun i x -> (i % size,x))
>> Seq.unfold (fun state -> if Seq.isEmpty state then None
else
match Seq.tryFind (fun (i,_) -> i = size-1) state with
| None -> state |> Seq.map snd,
Seq.empty
| _ -> state |> Seq.take size
|> Seq.map snd,
Seq.skip size state
|> Some)
很高兴听到是否有更好的选择。