Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell - Sum up the first n elements of a list

I´m new to Haskell.

Let´s say I want to sum up the first n elements of a list with a generated function on my own. I don´t know how to do this with Haskell. I just know how to sum up a whole given list, e.g.

sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs

In order to sum up the first n elements of a list, for example

take the first 5 numbers from [1..10], which is 1+2+3+4+5 = 15

I thought I could do something like this:

sumList :: Int -> [Int] -> Int
sumList take [] = 0
sumList take (x:xs) = x + take $ sumList xs

But it doesn´t work... What´s wrong?

like image 994
Slifer Avatar asked Dec 06 '22 11:12

Slifer


1 Answers

So you know how to sum up the numbers in a list,

sumList :: [Int] -> Int
sumList [] = 0
sumList (x:xs) = x + sumList xs

and if that list has no more than 5 elements in it, this function will even return the correct result if you indeed intended to sum no more than 5 elements in an argument list. Let's make our expectations explicit by renaming this function,

sumUpToFiveElements :: [Int] -> Int
sumUpToFiveElements [] = 0
sumUpToFiveElements (x:xs) = x + sumUpToFiveElements xs

it won't return the correct result for lists longer than five, but at least the name is right.

Can we fix that? Can we count up to 5? Can we count up to 5 while also advancing along the input list as we do?

sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs

This still isn't right of course. We do now count, but for some reason we ignore the counter. What is the right time to react to the counter, if we want no more than 5 elements? Let's try counter == 5:

sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements 5       [] = 0
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs

But why do we demand the list to also be empty when 5 is reached? Let's not do that:

sumUpToFiveElements :: Int -> [Int] -> Int
sumUpToFiveElements 5       _  = 0        -- the wildcard `_` matches *anything*
sumUpToFiveElements counter [] = 0
sumUpToFiveElements counter (x:xs) = x + sumUpToFiveElements (counter + 1) xs

Success! We now stop counting when 5 is reached! More, we also stop the summation!!

Wait, but what was the initial value of counter? We didn't specify it, so it's easy for a user of our function (that would be ourselves) to err and use an incorrect initial value. And by the way, what is the correct initial value?

Okay, so let's do this:

sumUpToFiveElements :: [Int] -> Int
sumUpToFiveElements xs = go 1 xs     -- is 1 the correct value here?
  where
    go counter  _ | counter == 5 = 0  
    go counter [] = 0
    go counter (x:xs) = x + go (counter + 1) xs

Now we don't have that extraneous argument that made our definition so brittle, so prone to a user error.

And now for the punchline:

Generalize! (by replacing an example value with a symbolic one; changing 5 to n).

sumUpToNElements :: Int -> [Int] -> Int
sumUpToNElements n xs = .......
   ........

Done.


One more word of advice: don't use $ while at the very beginning of your learning Haskell. Use explicit parens.

sumList take (x:xs) = x + take $ sumList xs 

is parsed as

sumList take (x:xs) = (x + take) (sumList xs)

This adds together two unrelated numbers, and then uses the result as a function to be called with (sumList xs) as an argument (in other words it's an error).

You probably wouldn't write it that way if you were using explicit parens.

like image 126
Will Ness Avatar answered Feb 12 '23 00:02

Will Ness