Looking to port some Python code to F#. I'm new to both languages. The Python code uses the "Counter" collection "where elements are stored as dictionary keys and their counts are stored as dictionary values". In Python for example:
cnt = Counter()
for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']
cnt[word] += 1
cnt
Counter({'blue': 3, 'red': 2, 'green': 1})
>> # Tally occurrences of words in a list
>> cnt = Counter()
>> for word in ['red', 'blue', 'red', 'green', 'blue', 'blue']:
>> ... cnt[word] += 1
>> cnt
>> Counter({'blue': 3, 'red': 2, 'green': 1})
Is there an F# equivalent to Python's Counter collection?
In F#, state mutation such as cnt[word] += 1
is possible, but it is generally avoided. Instead, computations are expressed by using higher-order functions to describe the intent of an operation, rather than using imperative commands to describe the mechanism. In this case, a typical F# solution might look something like this:
let counts =
["red"; "blue"; "red"; "green"; "blue"; "blue"]
|> List.groupBy id
|> Map.ofList
|> Map.map (fun _ words -> words |> List.length)
Here, we take the same list of words, then we use List.groupBy
to to create a list of lists, and we pass it the id
function to say that we want to group by the values themselves (the words in the list). The list of words is actually the second parameter to List.groupBy
, but we use the |>
("pipe-forward") operator to pass the preceding value as the last parameter to the next function. This is useful, because it allows us to chain operations together, as we see with the next line, where we take the list of lists returned by List.groupBy
and pass it to Map.ofList
, which gives us a Map<string, string list>
.
A Map
type allows us to do lookups by a key, like the Counter
type. We then convert the Map<string, string list>
into a Map<string, int>
with the counts of each word by piping the result of Map.ofList
into Map.map
, which lets us pass a function that converts the values in the map from one type to another. In this case, we want to convert the values from a string list
to an int
, so we take the string list
(which we'll call 'words') and pass it to List.length
to get the count. The function parameter for Map.map
takes two parameters, the key and the values, but we're only interested in the values, so I've ignored the key parameter by calling it _
. This gives us a Map<string, int>
which contains the word as the key and the count of that word in the list as the value:
val counts : Map<string,int> = map [("blue", 3); ("green", 1); ("red", 2)]
EDIT
As kvb mentioned in the comments, the F# List
module already has a countBy
function that combines the groupBy
with the List.length
for you. That makes the code even simpler:
let counts =
["red"; "blue"; "red"; "green"; "blue"; "blue"]
|> List.countBy id
|> Map.ofList
This gives the same result as the above, because we take the list of string * int
tuples and pipe it to Map.ofList
, so that we get the lookup capabilities of a map (like the Python Counter
object).
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