Haskell function that returns a list of elements in a list with more than given amount of occurrences

Viewed 690

I tried making a function that as in the title takes 2 arguments, a number that specifies how many times the number must occur and a list that we are working on, I made a function that counts number of appearances of given number in a list and I tried using it in my main function, but I cannot comprehend how the if else and indentations work in Haskell, it's so much harder fixing errors than in other languages, i think that I'm missing else statement but even so I don't know that to put in there

count el list = count el list 0
     where count el list output
             | list==[] = output
             | head(list)==el = count el (tail(list)) output+1
             | otherwise = count el (tail(list)) output


moreThan :: Eq a => Int -> [a] -> [a]
moreThan a [] = []
moreThan a list = moreThan a list output i
    where moreThan a list [] 0
            if i == length (list)
                then output
            else if elem (list!!i) output
                 then moreThan a list output i+1
            else if (count (list!!i) list) >= a 
                then moreThan a list (output ++ [list!!i]) i+1 

All I get right now is

parse error (possibly incorrect indentation or mismatched brackets) 
3 Answers

You just forgot the = sign and some brackets, and the final else case. But also you switched the order of the internal function declaration and call:

    moreThan :: Eq a => Int -> [a] -> [a]
    moreThan a [] = []
    moreThan a list = go a list [] 0   -- call
        where go a list output i =      --  declaration  =
                if i == length (list)
                    then output
                else if elem (list!!i) output
                     then go a list output (i+1)    -- (i+1) !
                else if (count (list!!i) list) >= a 
                    then go a list (output ++ [list!!i]) (i+1)   -- (i+1) !
                else
                    undefined

I did rename your internal function as go, as is the custom.

As to how to go about fixing errors in general, just read the error messages, slowly, and carefully -- they usually say what went wrong and where.

That takes care of the syntax issues that you asked about.

As to what to put in the missing else clause, you've just dealt with this issue in the line above it -- you include the ith element in the output if its count in the list is greater than or equal to the given parameter, a. What to do else, we say in the else clause.

And that is, most probably, to not include that element in the output:

                    then go a list (output ++ [list!!i]) (i+1)
                else               ---------------------
                    undefined

So, just keep the output as it is, there, instead of the outlined part, and put that line instead of the undefined.

More importantly, accessing list elements via an index is an anti-pattern, it is much better to "slide along" by taking a tail at each recursive step, and always deal with the head element only, like you do in your count code (but preferably using the pattern matching, not those functions directly). That way our code becomes linear instead of quadratic as it is now.

Will Ness's answer is correct. I just wanted to offer some general advice for Haskell and some tips for improving your code.

First, I would always avoid using guards. The syntax is quite inconsistent with Haskell's usual fare, and guards aren't composable in the same way that other Haskell syntax is. If I were you, I'd stick to using let, if/then/else, and pattern matching.

Secondly, an if statement in Haskell is very often not the right answer. In many cases, it's better to avoid using if statements entirely (or at least as much as possible). For example, a more readable version of count would look like this:

count el list = go list 0 where
    go [] output = output
    go (x:xs) output = go xs (if x == el
                              then 1 + output
                              else output)

However, this code is still flawed because it is not properly strict in output. For example, consider the evaluation of the expression count 1 [1, 1, 1, 1], which proceeds as follows:

count 1 [1, 1, 1, 1]
go [1, 1, 1, 1] 0
go [1, 1, 1] (1 + 0)
go [1, 1] (1 + (1 + 0))
go [1] (1 + (1 + (1 + 0)))
go [] (1 + (1 + (1 + (1 + 0))))
(1 + (1 + (1 + (1 + 0))))
(1 + (1 + 2))
(1 + 3)
4

Notice the ballooning space usage of this evaluation. We need to force go to make sure output is evaluated before it makes a recursive call. We can do this using seq. The expression seq a b is evaluated as follows: first, a is partially evaluated. Then, seq a b evaluates to b. For the case of numbers, "partially evaluated" is the same as being totally evaluated.

So the code should in fact be

count el list = go list 0 where
    go [] output = output
    go (x:xs) output = 
        let new_output = if x == el
                         then 1 + output
                         else output
        in seq new_output (go xs new_output)

Using this definition, we can again trace the execution:

go [1, 1, 1, 1] 0
go [1, 1, 1] 1
go [1, 1] 2
go [1] 3
go [] 4
4

which is a more efficient way to evaluate the expression. Without using library functions, this is basically as good as it gets for writing the count function.

But we're actually using a very common pattern - a pattern so common, there is a higher-order function named for it. We're using foldl' (which must be imported from Data.List using the statement import Data.List (foldl')). This function has the following definition:

foldl' :: (b -> a -> b) -> b -> [a] -> b
foldl' f = go where
    go output [] = output
    go output (x:xs) =
       let new_output = f output x
       in seq new_output (go new_output xs)

So we can further rewrite our count function as

count el list = foldl' f 0 list where
    f output x = if x == el
                 then 1 + output
                 else output

This is good, but we can actually improve even further on this code by breaking up the count step into two parts.

count el list should be the number of times el occurs in list. We can break this computation up into two conceptual steps. First, construct the list list', which consists of all the elements in list which are equal to el. Then, compute the length of list'.

In code:

count el list = length (filter (el ==) list)

This is, in my view, the most readable version yet. And it is also just as efficient as the foldl' version of count because of laziness. Here, Haskell's length function takes care of finding the optimal way to do the counting part of count, while the filter (el ==) takes care of the part of the loop where we check whether to increment output. In general, if you're iterating over a list and have an if P x statement, you can very often replace this with a call to filter P.

We can rewrite this one more time in "point-free style" as

count el = length . filter (el ==)

which is most likely how the function would be written in a library. . refers to function composition. The meaning of this is as follows:

To apply the function count el to a list, we first filter the list to keep only the elements which el ==, and then take the length.

Incidentally, the filter function is exactly what we need to write moreThan compactly:

moreThan a list = filter occursOften list where
    occursOften x = count x list >= a

Moral of the story: use higher-order functions whenever possible.

Whenever you solve a list problem in Haskell, the first tool you should reach for is functions defined in Data.List, especially map, foldl'/foldr, filter, and concatMap. Most list problems come down to map/fold/filter. These should be your go-to replacement for loops. If you're replacing a nested loop, you should use concatMap.

in a functional way, ;)

moreThan n xs = nub $ concat [ x | x <- ( group(sort(xs))), length x > n ]

... or in a fancy way, lol

moreThan n xs = map head [ x | x <- ( group(sort(xs))), length x > n ]

...

mt1 n xs =  [ head x | x <- ( group(sort(xs))), length x > n ]
Related