1

So I already have a function that finds the number of occurrences in a list using maps.

occur :: [a] -> Map a a
occur xs = fromListWith (+) [(x, 1) | x <- xs]

For example if a list [1,1,2,3,3] is inputted, the code will output [(1,2),(2,1),(3,2)], and for a list [1,2,1,1] the output would be [(1,3),(2,1)].

I was wondering if there's any way I can change this function to use foldr instead to eliminate the use of maps.

m.18
  • 70
  • 1
  • 8
  • 1
    If the list is `[1, 2, 1, 1]` is the ouptut supposed to be `[(1, 1), (2, 1), (1, 2)]`, or `[(1, 3), (2, 1)]`? – Willem Van Onsem Oct 09 '21 at 09:06
  • @WillemVanOnsem So the first element of the pair should be the element that is being searched for in the list and the second element in the pair should be the number of times it occurs in the list. So for a list [1,2,1,1] the output would be [(1,3),(2,1)] – m.18 Oct 09 '21 at 09:09
  • 3
    You can use a `foldr` where the initial accumulator is an empty list, and for each `foldr` "step" you increment the 2-tuple for the given item, or create a new one if that item does not exists yet. – Willem Van Onsem Oct 09 '21 at 09:10
  • @m.18 on SO, edits that change the question so much that it invalidates existing answers, are forbidden. – Will Ness Oct 10 '21 at 14:35

1 Answers1

2

You can make use of foldr where the accumulator is a list of key-value pairs. Each "step" we look if the list already contains a 2-tuple for the given element. If that is the case, we increment the corresponding value. If the item x does not yet exists, we add (x, 1) to that list.

Our function thus will look like:

occur :: Eq => [a] -> [(a, Int)]
occur = foldr incMap []

where incMap thus takes an item x and a list of 2-tuples. We can make use of recursion here to update the "map" with:

incMap :: Eq a => a -> [(a, Int)] -> [(a, Int)]
incMap x = go
  where go [] = [(x, 1)]
        go (y2@(y, ny): ys)
          | x == y = … : ys
          | otherwise = y2 : …

where I leave implementing the parts as an exercise.

This algorithm is not very efficient, since it takes O(n) to increment the map with n the number of 2-tuples in the map. You can also implement incrementing the Map for the given item by using insertWith :: Ord k => (a -> a -> a) -> k -> a -> Map k a -> Map k a, which is more efficient.

Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
  • Could you explain what I'm supposed to implement in the ... parts as I'm finding it to be a bit confusing? – m.18 Oct 10 '21 at 08:49
  • @m.18: for `x == y`, you should return a list `... : ys`, where the `...` should be a 2-tuple with `y` as key`, and the number of times it occurs as second item. The second `...` should recurse on the tail of the list. – Willem Van Onsem Oct 10 '21 at 08:52
  • So I implemented it this way incMap x = go where go [] = [(x, 1)] go (y2@(y, ny): ys) | x == y = (y, ny) : ys | otherwise = y2 : go ys and I am getting a list of tuples as an output but the count isn't correct as I am always getting 1. For for example for a list [2,2,3,4,1,1] I'm getting [(2,1), (3,1), (4,1), (1,1)] – m.18 Oct 10 '21 at 09:02
  • 1
    @m.18: that's almost correct, but for the first `...` part, you need to increment the count `ny`. – Willem Van Onsem Oct 10 '21 at 09:05
  • 1
    That is correct! I got it to work, thank you so much. – m.18 Oct 10 '21 at 09:06