Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Is there an F# equivalent to Python's Counter collection?

Tags:

python

counter

f#

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?

like image 735
jsm2000 Avatar asked Oct 20 '25 17:10

jsm2000


1 Answers

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).

like image 85
Aaron M. Eshbach Avatar answered Oct 22 '25 07:10

Aaron M. Eshbach