Calling Seq.skip and Seq.take in F#
Asked Answered
N

6

12
let aBunch = 1000
let offset = 0

let getIt offset =
  MyIEnumerable
  |> Seq.skip aBunch * offset
  |> Seq.take aBunch
  |> Seq.iter ( .. some processing ...)

Calling getIt() with different offsets eventually gives me an 'Invalid operation' exception with additional info that 'the input sequence had insufficient elements'

I try to understand why, as both the Seq.Skip and Seq.take do not generate an exception according to the online documentation FSharp Collections

Version: (Visual Studio 2010) Beta 1

Nutrient answered 8/8, 2009 at 2:28 Comment(1)
The documentation doesn't say anything about exceptions; the docs are incomplete. I'll file a doc bug.Engedus
C
29

I know this is an old question, but in case someone comes across this in a search the way I did:

You can use Seq.truncate if you want at most n items. It won't throw an exception if fewer than n items are available.

Ceratodus answered 5/3, 2010 at 1:16 Comment(1)
this only applies to the Seq.take part of the question, not Seq.skip, I guess?Etruria
A
7

Both Seq.skip and Seq.take will throw this exception if called with a value larger than the sequence. You can check the source code in Seq.fs to see why:

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 }
Altheaalthee answered 8/8, 2009 at 2:46 Comment(0)
A
3

For an exceptionless skip you can add your own version to the Seq module like this:

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 
        }

Combined with Seq.truncate (which is an exceptionless Seq.take equivalent - it will take as much items are available without throwing an exception).

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

(* returns empty seq *)
Anthropomorphic answered 15/8, 2014 at 14:2 Comment(0)
B
3

Here's a slightly shorter "skipSafe" implementation using built in functions:

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

Or if you wish to just inline it into your current pipeline directly, replace

|> Seq.skip num

with

|> Seq.zip (Seq.initInfinite id)
|> Seq.skipWhile (fun (i, _) -> i < num)
|> Seq.map snd
Birefringence answered 1/3, 2016 at 21:27 Comment(0)
B
2
module Seq = 
    let trySkip count source  =
        source |> Seq.indexed |> Seq.filter(fst >> (<=) count) |> Seq.map snd
Batey answered 12/4, 2016 at 10:23 Comment(1)
Liked it, short and sweet.Icy
S
0

For skip, you can easily define a trivial skipSafe with (presumably) optimal performance by delegating to LINQ's Skip:

open System.Linq

let skipSafe count (source: seq<'a>) = source.Skip(count)

For take, as the other answers say, you can just use Seq.truncate, or optionally alias it:

let takeSafe count source = Seq.truncate count source
Spout answered 16/2 at 9:50 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.