序列的F#array_chunk

时间:2009-04-04 03:34:18

标签: arrays f# sequence chunking

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

13 个答案:

答案 0 :(得分:5)

这是一个很好的命令,可以使用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] }

答案 1 :(得分:5)

我爱Seq.take&amp; 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 )

答案 2 :(得分:4)

这个答案可能会被埋没,但这是我对这个问题的看法:

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)运行时。不是像Oq(n ^ 2)那样的Seq.skip / take solutions
  • Seq.length不必是n
  • 的倍数
  • 小而易懂?

缺点:

  • 可能不如命令式/可变循环那样有效

答案 3 :(得分: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个元素)。此外,这不如显式使用枚举器那样有效,但它更优雅。

答案 4 :(得分: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)           
            }

(代码取自我的回答here

答案 5 :(得分: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

答案 6 :(得分: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 []

}

答案 7 :(得分:0)

我更喜欢这个解决方案。它从现有序列生成一个新序列(意味着它不需要遍历整个序列来获得结果 - 如果你正在执行类似日志处理的事情,那么这是很关键的,你不能调用像Length这样的东西。) / p>

我最后写了一篇blog post,详细介绍了我是如何到达这里的。

module Seq =  

let grouped_by_with_leftover_processing f(f2:'a list - &gt; list&lt;'a&gt;选项)(s:seq&lt;'a&gt;)=     let rec grouped_by_with_acc(f:'a - &gt;'列表 - &gt;'列表选项*'列表)acc(即:IEnumerator&lt;'a&gt;)=       seq {         if ie.MoveNext()         然后           let nextValue,leftovers = f ie.Current acc           如果nextValue.IsSome然后产生nextValue.Value           让! groups_by_with_acc f剩菜即         其他           让rems = f2 acc           如果rems.IsSome然后产生rems.Value       }     seq {       让! grouped_by_with_acc f [](s.GetEnumerator())     }

让YieldReversedLeftovers(f:'列表)=     如果f.IsEmpty     没有     其他一些(List.rev f)

let grouped_by f s =     grouped_by_with_leftover_processing f YieldReversedLeftovers s

让group_by_length_n n s =     让grouping_function newValue acc =       让newList = newValue :: acc       //如果我们有合适的长度,请返回       //一些作为第一个值。那会       //由序列产生。       如果List.length acc = n - 1       一些(List.rev newList),[]       //如果我们没有合适的长度,       //使用无(因此不会产生任何结果)       否则,新列表
    groups_by grouping_function s

大序列不是问题:

seq {for i in 1..1000000000 - &gt; i} |&gt; Seq.group_by_length_n 3 ;; val it:seq&lt; int list&gt; = seq [[1; 2; 3]; [4; 5; 6]; [7; 8; 9]; [10; 11; 12]; ...] &GT;

答案 8 :(得分:0)

来自Princess的好版本已被修复以获得尾部并转换为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 ] }

答案 9 :(得分: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的回答完全相同。

我不记得(链接?)一个序列不记得位置,所以连续的take / skip不是最佳的。

答案 10 :(得分:0)

这是@kvb的解决方案,修复了Seq.skip / take限制。它小巧,优雅,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)
 }

答案 11 :(得分: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
    |]

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

答案 12 :(得分: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的位置提前。但是,事实并非如此。