Count of list elements that are a certain member of a discriminated union
-
12-06-2021 - |
Question
Let's say I have a discriminated union:
type foo = Bar | Baz | Batz
Then, I want to check how many members of a list are of type Baz
:
List.sumBy (function Bar -> 1 | _ -> 0) foos
Is there a more idiomatic way to do this?
Solution
Notice that your example is incorrect. It should be:
List.sumBy (fun x -> match x with Baz -> 1 | _ -> 0) foos
which could be rewritten as:
List.sumBy (function Baz -> 1 | _ -> 0) foos
I don't think there's a more idomatic way than using List.sumBy
here.
OTHER TIPS
It depends on how would you define "more idiomatic". If it is a synonym of language proficiency measured, among other factors, by code performance, then similarly terse
List.fold (fun sum -> function Baz -> sum + 1 | _ -> sum) 0 foos
may be considered more idiomatic as it executes 3-4 times faster, than version with List.sumBy
under F# 2.0.
Performance disparity that much significant has place because implementation of List.fold
is highly optimized specifically for lists, while List.sumBy
falls back to Seq.sumBy
where implementation goes through ordinary IEnumerable
traversal.
I would define a count
function. It's a pretty common use case to want to count the number of elements that satisfy the given predicate.
let count pred = List.sumBy (fun x -> if pred x then 1 else 0)
Use:
count (fun x -> x = Bar) foos
This is what I'd have done instinctively. Filter |> Length seems more natural than summing or folding. Haven't checked peformance.
let count =
myList
|> List.filter (fun elem -> match elem with | Baz -> true | _ -> false)
|> List.length
...and for completenesss, don't forget the little-known fact that for-loops can pattern match. You will get a warning about incomplete pattern matching, though, which is annoying.
open System
type foo = Bar | Baz of int | Batz of string * string
let myList = [Bar; Bar; Baz(1); Batz("x", "y"); Baz(2)]
let count =
let mutable acc = 0
for Baz(_) in myList do
acc <- acc + 1
acc