Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Calling Seq.skip and Seq.take in F#

Tags:

f#

sequences

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

like image 579
Moonlight Avatar asked Aug 08 '09 02:08

Moonlight


3 Answers

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.

like image 75
Joel Mueller Avatar answered Nov 07 '22 19:11

Joel Mueller


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 }
like image 29
Dustin Campbell Avatar answered Nov 07 '22 18:11

Dustin Campbell


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 *)
like image 3
krdx Avatar answered Nov 07 '22 18:11

krdx