【问题标题】:F# array_chunk for SequenceF# array_chunk 用于序列
【发布时间】:2010-10-17 12:25:13
【问题描述】:

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

【问题讨论】:

    标签: arrays f# sequence chunking


    【解决方案1】:

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

    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 的倍数
    • 小巧易懂?

    缺点:

    • 可能不如命令式/可变循环高效

    【讨论】:

    【解决方案2】:

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

    【讨论】:

    • 很好的答案。我的代码接近这一点,但还没有。
    【解决方案3】:

    我喜欢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 )
    

    【讨论】:

    • 我选择了这个答案,因为学习理解这段代码让我对 F# 有了更深入的了解。
    • 我做了一个快速的工作台,你的第一个解决方案比 MichaelGG 命令式解决方案慢了大约 50%,第二个大约慢了 100%。我最终使用了您的第一个解决方案。谢谢:)
    • 要使用无意义的风格,你可以这样做 "|> Seq.filter (fst >> (=) n)" "|> Seq.filter (fun (i,_) -> i = n )"。
    【解决方案4】:

    怎么样:

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

    【讨论】:

    • 我想支持递归 seq(我个人经常使用的一种技术),但是当 sq.Length 不能被 n 整除时,此代码会引发异常。
    • 是的,我在代码之后提到了这一点。如果 Seq.take 和 Seq.skip 的实现更像 LINQ 的相应操作,那就太好了,这样它们就不需要至少与被采用或跳过一样多的元素。
    • 查看我的答案这里
    【解决方案5】:

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

     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

    【讨论】:

    • 虽然这很简单而且很干净,但它是 O(n^2)。感谢您的回复。 :)
    • 我不是专家,但据我所见,在 F# 中优化性能通常会导致可变性,但不知道情况总是如此。
    • 它是 O(n^2),因为 Seq.length 通常是 O(n)。如果 s 是一个数组,那么 Seq.length 就是 O(1) 而 chunks() 就是 O(n)
    【解决方案6】:

    这很好,简洁:

    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
    

    【讨论】:

      【解决方案7】:

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

      }

      【讨论】:

        【解决方案8】:

        我更喜欢这个解决方案。它从现有序列生成一个新序列(这意味着它不需要遍历整个序列来获得结果 - 如果您正在执行诸如日志处理之类的事情,而您不能调用诸如 Length 之类的事情,这一点至关重要)。

        我最终写了一封blog post,详细说明了我是如何到达这里的。

        module Seq =  
        

        let grouped_by_with_leftover_processing f (f2: 'a list -> list option) (s: seq)= let rec grouped_by_with_acc (f: 'a -> 'a list -> 'a list option * 'a list) acc (ie: IEnumerator) = 序列{ 如果 ie.MoveNext() 然后 让 nextValue, leftovers = f ie.Current acc 如果 nextValue.IsSome 则产生 nextValue.Value 屈服! grouped_by_with_acc f 剩菜,即 别的 让 rems = f2 acc 如果 rems.IsSome 则产生 rems.Value } 序列{ 屈服! grouped_by_with_acc f [] (s.GetEnumerator()) }

        让 YieldReversedLeftovers (f: 'a list) = 如果 f.IsEmpty 然后无 其他一些(List.rev f)

        让 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 // 如果我们有正确的长度,返回 // a Some 作为第一个值。那会 // 由序列产生。 如果 List.length acc = n - 1 然后一些(List.rev newList),[] // 如果我们没有正确的长度, // 使用 None (所以什么都不会产生) 其他无,新列表
        grouped_by grouping_function s

        大序列不是问题:

        seq { for i in 1..1000000000 -> i} |> Seq.group_by_length_n 3;; 验证它:seq = 序列 [[1; 2; 3]; [4; 5个; 6]; [7; 8个; 9]; [10; 11; 12]; ...] >

        【讨论】:

          【解决方案9】:

          来自 Princess 的漂亮版本已被修复为获取 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 ] }
          

          【讨论】:

            【解决方案10】:

            这个怎么样:

            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 的回答一脉相承。

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

            【讨论】:

              【解决方案11】:

              这是@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)
               }
              

              【讨论】:

              • 这不是 O(n)。它是 O(n^2)。尝试传递一个打印出评估的序列。 seq { for i in 1 .. 15 do printf "%A " i; yield i }
              【解决方案12】:

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

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

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

              【讨论】:

                【解决方案13】:

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

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

                【讨论】:

                  猜你喜欢
                  • 1970-01-01
                  • 1970-01-01
                  • 2021-03-16
                  • 1970-01-01
                  • 1970-01-01
                  • 2011-08-01
                  • 2012-05-22
                  • 1970-01-01
                  • 1970-01-01
                  相关资源
                  最近更新 更多