在F#中调用Seq.skip和Seq.take

12
let aBunch = 1000
let offset = 0

let getIt offset =
  MyIEnumerable
  |> Seq.skip (aBunch * offset)
  |> Seq.take aBunch
  |> Seq.iter ( .. 进行一些处理... )

调用不同的偏移量调用getIt()最终会导致“无效操作”异常,并附加信息“输入序列元素不足”。

我试图理解为什么,因为根据在线文档 FSharp Collections,Seq.Skip和Seq.Take都不会引发异常。

版本:(Visual Studio 2010)Beta 1

2
文档没有关于异常的说明;文档不完整。我会提交一个文档错误报告。 - Brian
5个回答

29

我知道这是一个老问题,但是如果有人像我一样通过搜索找到它:

您可以使用 Seq.truncate 如果您希望最多有 n 个项目。如果少于 n 个项目可用,它将不会引发异常。


这仅适用于问题中的 Seq.take 部分,而不是 Seq.skip,我猜是这样吗? - knocte

7

如果使用比序列本身长度还大的值调用Seq.skip和Seq.take,这两个函数都会抛出异常。你可以查看Seq.fs中的源代码来了解具体原因:

let skip count (sequence: seq<_>) =
    { use e = sequence.GetEnumerator() 
      let latest = ref (Unchecked.defaultof<_>)
      let ok = ref false
      for i in 1 .. count do
          if not (e.MoveNext()) then 
              raise <| System.InvalidOperationException "the input sequence had insufficient elements" 
      while e.MoveNext() do
          yield e.Current }

let take count (sequence : seq<'T>)    = 
    if count < 0 then invalidArg "count" "the number of elements to take may not be negative"
    (* Note: don't create or dispose any IEnumerable if n = 0 *)
    if count = 0 then empty else  
    { use e = sequence.GetEnumerator() 
      for i in 0 .. count - 1 do
          if not (e.MoveNext()) then
              raise <| System.InvalidOperationException "the input sequence had insufficient elements" 
          yield e.Current }

3
这是一个使用内置函数的稍微简短的“skipSafe”实现:

module Seq =
    let skipSafe num = 
        Seq.zip (Seq.initInfinite id)
        >> Seq.skipWhile (fun (i, _) -> i < num)
        >> Seq.map snd

如果您希望将其直接内嵌到当前流程中,只需替换

|> Seq.skip num

使用

|> Seq.zip (Seq.initInfinite id)
|> Seq.skipWhile (fun (i, _) -> i < num)
|> Seq.map snd

3

如果需要无异常的 skip,您可以像这样向 Seq 模块添加自己的版本:

module Seq =
    let skipSafe (num: int) (source: seq<'a>) : seq<'a> =
        seq {
            use e = source.GetEnumerator()
            let idx = ref 0
            let loop = ref true
            while !idx < num && !loop do
                if not(e.MoveNext()) then
                    loop := false
                idx := !idx + 1

            while e.MoveNext() do
                yield e.Current 
        }

结合 Seq.truncate 使用(它是一个无异常的 Seq.take 替代品 - 它将获取尽可能多的项目而不会抛出异常)。

[1..10] 
|> Seq.skipSafe 20
|> Seq.truncate 5

(* returns empty seq *)

2
module Seq = 
    let trySkip count source  =
        source |> Seq.indexed |> Seq.filter(fst >> (<=) count) |> Seq.map snd

喜欢,简短而甜美。 - undefined

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接