Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Best way to do tryMax and tryMin in F#?

Tags:

f#

seq

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
  • Are there any problems with this approach?
  • Is there a built-in solution for this?
like image 660
sdgfsdh Avatar asked May 29 '20 09:05

sdgfsdh


2 Answers

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
like image 93
Abel Avatar answered Sep 17 '22 05:09

Abel


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
like image 25
Brian Berns Avatar answered Sep 17 '22 05:09

Brian Berns