我在制作序列时遇到了一些麻烦。基本上我需要将一个序列切成一系列数组。Seq.windowed 几乎做到了,但我不想要重复的元素。
我可以通过先将所有内容读入数组来获得我想要的东西,但我宁愿使用序列。
let array_chunk s (a:int[]) =
Array.init (a.Length / s) (fun i -> Array.sub a (i * s) s)
someSequence |> Seq.to_array |> array_chunk 5
这个答案可能会被埋没,但这是我对这个问题的看法:
let chunk n xs =
xs
|> Seq.mapi(fun i x -> i/n, x)
|> Seq.groupBy fst
|> Seq.map (fun (_, g) -> Seq.map snd g)
优点:
缺点:
这是一个很好的命令,它可以与 seq 一起使用并生成任何大小的数组。如果序列甚至不是 n,最后一个会更小。
let chunk n xs = seq {
let i = ref 0
let arr = ref <| Array.create n (Unchecked.defaultof<'a>)
for x in xs do
if !i = n then
yield !arr
arr := Array.create n (Unchecked.defaultof<'a>)
i := 0
(!arr).[!i] <- x
i := !i + 1
if !i <> 0 then
yield (!arr).[0..!i-1] }
我喜欢Seq.take
和Seq.skip
解决方案。它很漂亮,简单且易读,但我会使用这样的东西:
let chunks n (sequence: seq<_>) =
let fold_fce (i, s) value =
if i < n then (i+1, Seq.append s (Seq.singleton value))
else ( 1, Seq.singleton value)
in sequence
|> Seq.scan (fold_fce) (0, Seq.empty)
|> Seq.filter (fun (i,_) -> i = n)
|> Seq.map (Seq.to_array << snd )
它不是命令式代码,它应该比使用 Seq.skip 的解决方案更有效。另一方面,它将输入序列修剪为可被 n 整除的长度。如果这种行为是不可接受的,它可以通过简单的修改来修复:
let chunks n (sequence: seq<_>) =
let fold_fce (i, s) value =
if i < n then (i+1, Seq.append s (Seq.singleton value))
else ( 1, Seq.singleton value)
in sequence
|> Seq.map (Some)
|> fun s -> Seq.init_finite (n-1) (fun _ -> None) |> Seq.append s
|> Seq.scan (fold_fce) (0, Seq.empty)
|> Seq.filter (fun (i,_) -> i = n)
|> Seq.map (Seq.to_array << (Seq.choose (id)) << snd )
怎么样:
let rec chunks n sq =
if not (Seq.is_empty sq) then
seq {
yield Seq.take n sq |> Seq.to_array
yield! chunks n (Seq.skip n sq)
}
else
Seq.empty
请注意,这要求 sq 有许多可以被 n 整除的元素(因为 Seq.take 和 Seq.skip 与 LINQ 的 Take 和 Skip 扩展方法不同,要求序列至少包含 n 个元素)。此外,这不像显式使用枚举器那样有效,但它更优雅。
接受/跳过答案的更正版本,作为扩展功能。应该适用于不均匀的长度。虽然不能保证性能...
module Seq =
let rec chunks n (s:#seq<_>) =
seq {
if Seq.length s <= n then
yield s
else
yield Seq.take n s
yield! chunks n (Seq.skip n s)
}
(代码取自我在这里的回答)
这很好,简洁:
let chunk size (arr : 'a array) =
[| for a in 0 .. size .. arr.Length - size -> arr.[a..a + size - 1] |]
但是,这会丢失数组中的最后一个 (arr.Length % size) 元素。您可以通过抓取缺失的元素并使用 Array.append 来解决此问题:
let chunk2 size (arr : 'a array) =
let first = [| for a in 0 .. size .. arr.Length - size -> arr.[a..a + size - 1] |]
let numberOfMissingElements = arr.Length - (first.Length * size)
if numberOfMissingElements > 0 then
let last = [| arr.[arr.Length - numberOfMissingElements..] |]
Array.append first last
else first
这是另一种模式匹配的方法 - 它看起来更像 *.iter,我让它吐出列表而不是数组,因为这就是我通常喜欢我的数据的方式。
let sequence_in_lists_of_length_n_with_acc (s: seq<'a>) n acc = seq {
use e = s.GetEnumerator()
let rec next_with_acc acc =
match e.MoveNext(), acc with
| true, a when List.length a + 1 = n ->
yield (List.rev (e.Current :: a))
next_with_acc []
| true, _ -> next_with_acc (e.Current :: acc)
| false, _ ->
f(List.rev acc)
()
next_with_acc []
}
我更喜欢这个解决方案。它从现有序列生成一个新序列(这意味着它不需要遍历整个序列来获得结果 - 如果您正在执行诸如日志处理之类的事情,而您不能调用诸如 Length 之类的事情,这一点至关重要)。
我最终写了一篇博客文章,详细介绍了我是如何到达这里的。
module Seq =
let grouped_by_with_leftover_processing f (f2: 'a list -> list<'a> option) (s: seq<'a>)= let rec grouped_by_with_acc (f: 'a -> 'a list -> 'a list option * 'a list) acc (ie: IEnumerator<'a>) = seq { if ie.MoveNext() then let nextValue, leftovers = f ie.Current acc if nextValue.IsSome then yield nextValue.Value yield!grouped_by_with_acc f leftovers ie else let rems = f2 acc if rems.IsSome then yield rems.Value } seq { yield!grouped_by_with_acc f [] (s.GetEnumerator()) }
let YieldReversedLeftovers (f: 'a list) = if f.IsEmpty then None else Some (List.rev f)
让 grouped_by fs = grouped_by_with_leftover_processing f YieldReversedLeftovers s
let group_by_length_n ns = let grouping_function newValue acc = let newList = newValue :: acc // 如果我们有正确的长度,则返回 // a Some 作为第一个值。那将 // 由序列产生。if List.length acc = n - 1 then Some (List.rev newList), [] // 如果我们没有正确的长度, // 使用 None (所以不会产生任何结果) else None, newList
grouped_by grouping_function s
大序列不是问题:
seq { for i in 1..1000000000 -> i} |> Seq.group_by_length_n 3;; 验证它: seq<int list> = seq [[1; 2;3]; [4; 5个;6]; [7; 8个;9]; [10; 11; 12];...] >来自 Princess 的 Nice 版本已修复获取 tail 并转换为 seq
let array_chunk size (arr : 'a array) =
let maxl = arr.Length - 1
seq { for a in 0 .. size .. maxl -> arr.[a .. min (a + size - 1) maxl ] }
这个怎么样 :
let grouped n =
Seq.unfold(fun s -> if not (Seq.isEmpty s) then
Some (Seq.take n s, Seq.skip n s)
else None)
这与kvb的答案相同。
我以某种方式记得(链接?)序列不记得位置,因此连续拍摄/跳过不会是最佳的。
这是修复了 Seq.skip/take 限制的@kvb 解决方案。它小巧、优雅且 O(n)。
let eSkip n s = System.Linq.Enumerable.Skip(s, n)
let rec seq_chunks n sq =
if (Seq.isEmpty sq)
then Seq.empty
else seq {
yield Seq.truncate n sq
yield! seq_chunks n (eSkip n sq)
}
这是我的版本,将数组作为输入和输出:
let chunk chunkNumber (array : _ array) =
let chunkSize = array.Length/chunkNumber
let mutable startIndex = 0
[|
let n1 = array.Length % chunkNumber
for i = 1 to n1 do
yield Array.sub array startIndex (chunkSize+1)
startIndex <- startIndex + chunkSize+1
let n2 = chunkNumber - n1
for i = 1 to n2 do
yield Array.sub array startIndex chunkSize
startIndex <- startIndex + chunkSize
|]
该函数尝试制作大小相似的块(而不是获得一个非常小的最后一个块)并以与构建序列相同的方式构建输出(使其易于重写以获得序列作为输出)
总结以上对序列、列表或数组的分块或缓冲或分段。两种形式:
let rec chunk size xs = seq {
yield Seq.take size xs
yield! chunk size (Seq.skip size xs)
}
或者
let chunk size = Seq.unfold (fun xs ->
match Seq.isEmpty xs with
| false -> Some(Seq.take size xs, Seq.skip size xs)
| _ -> None
)
注意:如果 Seq 像游标一样正常运行(正如我所期望的那样是一个惰性求值),Seq.take 将推进位置 Seq.skip 将是不必要的。然而,事实并非如此。