I think your approach is generally good. There was an answer that is now deleted that suggested to use try/with
to prevent double-evaluation of the first item by catching the error for empty sequences, but that too can be expensive.
If you want to prevent double evaluation, you can use Seq.cache
, or not use Seq
at all (use List
or Array
instead). Or use fold, which iterates only once:
module Seq =
let tryMin sq =
sq
|> Seq.fold(fun x y ->
match x with None -> Some y | Some x -> Some(min x y)) None
Usage:
> Seq.tryMin Seq.empty<int>;;
val it : int option = None
> Seq.tryMin (Seq.singleton 2L);;
val it : int64 option = Some 2L
> Seq.tryMin (seq { 2; 3});;
val it : int option = Some 2
> Seq.tryMin (seq { 2; -3});;
val it : int option = Some -3
A potentially faster method (I didn't time it), is to prevent the creation of option
on each min- or max-calculation result, and at the same time preventing multiple iterations of the first item.
This should have much less GC pressure too ;).
module Seq =
let tryMin (sq: seq<_>) =
use e = sq.GetEnumerator()
// this returns false if there is no first item
if e.MoveNext() then
let mutable result = e.Current
while e.MoveNext() do
result <- min e.Current result
Some result
else
None
Usage:
> Seq.tryMin Seq.empty<int>;;
val it : int option = None
> Seq.tryMin (Seq.singleton 2L);;
val it : int64 option = Some 2L
> Seq.tryMin (seq { 2; 3});;
val it : int option = Some 2
> Seq.tryMin (seq { 2; -3});;
val it : int option = Some -3
min
andmax
build-in functions as they may be better inlined/optimized and add readability. – Mislay