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