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