Beginner question, how to count number of keys in map

I’m trying to get the number keys of a map. the code I came up with is since I have to use map and set and prelude only

type Film = (Fan, Title, Review)
filmRating :: [Film] -> [(Fan, Int)]
filmRating l = undefined

Map.fromListWith (++) [(f, [t]) | (f, t, _) <- l]

I put [t] so I could then count that list, but apparently im doing it wrong somewhere and it doesnt work

I want it to give me something like this:

input : [(“fan1”, “film1”, 3), (“fan1”, “film2”, 3), (“fan3”, “film1”, 5), (“fan4”, “film3”, 8)]
output: [(“fan1”, 2). (“fan3”, 1), (“fan4”, 1)] (number of films a fan watched)

is there another way other than recursion? using map / set

Hello. Here is a solution if you are allowed to import Data.Map from the containers package:

import qualified Data.Map.Strict as M

filmRating :: [Film] -> [(Fan, Int)]
filmRating = M.assocs . foldl (\m (fan, _, _) -> M.alter incrCount fan m) M.empty
  where
    incrCount Nothing = Just 1
    incrCount (Just n) = Just (n + 1)
1 Like

is there a way to check for duplicate films watched and not count them?

is there another way other than recursion? using map / set

You were almost there with your suggestion

Map.fromListWith (++) [(f, [t]) | (f, t, _) <- l]

However, this has type Map Fan [Title], which is not quite what we
want:

>>> example = [("fan1", "film1", 3), ("fan1", "film2", 3), ("fan3", "film1", 5), ("fan4", "film3", 8)]
>>> f films = Map.fromListWith (++) [(f, [t]) | (f, t, _) <- films]
>>> f example
fromList [("fan1",["film2","film1"]),("fan4",["film3"]),("fan3",["film1"])]

However, all hope is not lost! Observe that to get to our solution we
only need to count the length of the list of movies associated to each
fan and replace the list with that number. Sounds like a job for
Map.map :: Map k v -> Map k v', doesn’t it?

>>> g :: [Film] -> Map Fan Int
>>> g films = Map.map length (f films)

To fulfill the API of filmRating, you now only have to turn that map
back into a list.

Also, if you only want to know the number of movies that a certain
person watched and you don’t want to work with the list at all, you can
combine these two operations quite nicely using your original idea of
leveraging fromListWith:

1 Like

You mean not incrementing if someone has already seen the film? You can build a Map Fan (Set Title) instead of a Map Fan Int, then mapping a function like Set.size to get a Map Fan Int not counting the duplicates.

1 Like

this makes so much sense to me now. thank you

ah yes thank you idk why my mind didnt go to sets

In general anything of the form Semigroup v => Map k v where v could be a Sum Int is handy for counts of distinct things, like keeping a running histogram and the like. Once things are in this form you can compute the total with a unionWith mappend ^^ have fun!

2 Likes

Of course there is. But you need to understand your task components. What it means to be “done” for them.

Or you’ll waste time on playing type tetris with random functions ending with a code soup.
You don’t even need Map for this task unless your data set is huge. And you can swap out your accumulator later, when the algorithm is in place.

1 Like