6

我在制作序列时遇到了一些麻烦。基本上我需要将一个序列切成一系列数组。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
4

13 回答 13

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,不使用数组
  • O(n) 运行时。不像 Seq.skip/take 解决方案那样 O(n^2)
  • Seq.length 不必是 n 的倍数
  • 小而易懂?

缺点:

  • 可能不如命令式/可变循环高效
于 2014-02-06T22:41:52.617 回答
4

这是一个很好的命令,它可以与 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] }
于 2009-04-04T05:45:47.927 回答
4

我喜欢Seq.takeSeq.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 )
于 2009-04-04T17:47:45.150 回答
3

怎么样:

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 个元素)。此外,这不像显式使用枚举器那样有效,但它更优雅。

于 2009-04-04T04:47:23.030 回答
3

接受/跳过答案的更正版本,作为扩展功能。应该适用于不均匀的长度。虽然不能保证性能...

 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)           
            }

(代码取自我在这里的回答)

于 2009-04-07T08:14:53.753 回答
1

这很好,简洁:

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
于 2009-04-04T23:27:15.160 回答
0

这是另一种模式匹配的方法 - 它看起来更像 *.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 []

}

于 2009-04-06T05:20:48.393 回答
0

我更喜欢这个解决方案。它从现有序列生成一个新序列(这意味着它不需要遍历整个序列来获得结果 - 如果您正在执行诸如日志处理之类的事情,而您不能调用诸如 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];...] >
于 2009-04-06T20:12:00.450 回答
0

来自 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 ] }
于 2009-04-07T05:25:03.910 回答
0

这个怎么样 :

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的答案相同。

我以某种方式记得(链接?)序列不记得位置,因此连续拍摄/跳过不会是最佳的。

于 2012-11-21T17:18:44.693 回答
0

这是修复了 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)
 }
于 2016-03-21T19:49:27.303 回答
0

这是我的版本,将数组作为输入和输出:

  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
    |]

该函数尝试制作大小相似的块(而不是获得一个非常小的最后一个块)并以与构建序列相同的方式构建输出(使其易于重写以获得序列作为输出)

于 2016-08-01T20:42:43.303 回答
0

总结以上对序列、列表或数组的分块或缓冲或分段。两种形式:

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 将是不必要的。然而,事实并非如此。

于 2019-03-02T10:55:28.897 回答