Suppose I have a seq and I want to return the largest if there are any elements or None otherwise. F# does not appear to have this built-in. 
Here is my attempt:
let tryMax xs = 
  if Seq.isEmpty xs
  then 
    None
  else 
    Seq.max xs |> Some
let tryMin xs = 
  if Seq.isEmpty xs
  then 
    None
  else 
    Seq.min xs |> Some
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
FWIW, here's tryMinBy as well:
let tryMinBy projection (items : seq<_>) =
    use e = items.GetEnumerator()
    if e.MoveNext() then
        let mutable minItem = e.Current
        let mutable minValue = projection minItem
        while e.MoveNext() do
            let value = projection e.Current
            if value < minValue then
                minItem <- e.Current
                minValue <- value
        Some minItem
    else
        None
The full suite:
module Seq
let tryMinBy projection (items : seq<_>) =
  use e = items.GetEnumerator ()
  if e.MoveNext ()
  then
    let mutable minItem = e.Current
    let mutable minValue = projection minItem
    while e.MoveNext () do
      let value = projection e.Current
      if value < minValue
      then
        minItem <- e.Current
        minValue <- value
    Some minItem
  else
    None
let tryMaxBy projection (items : seq<_>) =
  use e = items.GetEnumerator ()
  if e.MoveNext ()
  then
    let mutable maxItem = e.Current
    let mutable maxValue = projection maxItem
    while e.MoveNext () do
      let value = projection e.Current
      if value > maxValue
      then
        maxItem <- e.Current
        maxValue <- value
    Some maxItem
  else
    None
let tryMin (items : seq<_>) =
  use e = items.GetEnumerator ()
  if e.MoveNext ()
  then
    let mutable minItem = e.Current
    while e.MoveNext () do
      if e.Current < minItem
      then
        minItem <- e.Current
    Some minItem
  else
    None
let tryMax (items : seq<_>) =
  use e = items.GetEnumerator ()
  if e.MoveNext ()
  then
    let mutable maxItem = e.Current
    while e.MoveNext () do
      if e.Current > maxItem
      then
        maxItem <- e.Current
    Some maxItem
  else
    None
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With