1
\$\begingroup\$

I'm looking for feedback on my solution to the following prompt:

Given an array of ints, return the index such that the sum of the elements to the right of that index equals the sum of the elements to the left of that index. If there is no such index, return Nothing. If there is more than one such index, return the left-most index. Example: peak([1,2,3,5,3,2,1]) = 3, because the sum of the elements at indexes 0,1,2 == sum of elements at indexes 4,5,6. We don't sum index 3.

Questions:

  1. Is the solution best defined recursively? Or is there some kind of higher-order function that I could use?
  2. The function peak' takes four arguments. That feels a little unwieldy. Any reasonable way to shorten that parameter list?
  3. Conventional programming wisdom says to avoid "magic numbers", so I introduced some bindings in the let clause. Does that make peak easier to read, or does it just seem like bloat?
peak :: [Int] -> Maybe Int
peak numbers =    
    let leftSum = 0
        rightSum = sum numbers
        startingIndex = 0
    in  peak' numbers leftSum rightSum startingIndex

peak' :: [Int] -> Int -> Int -> Int -> Maybe Int
peak' [] _ _ _ = Nothing
peak' (x:xs) leftSum rightSum index
    | leftSum + x == rightSum   = Just index
    | otherwise                 = peak' xs (leftSum + x) (rightSum - x) (index + 1) 

Prompt source: https://www.codewars.com/kata/5a61a846cadebf9738000076/train/haskell

\$\endgroup\$

1 Answer 1

2
\$\begingroup\$
  1. You can use scans to implement this:
import Data.List

peak :: [Int] -> Maybe Int
peak [] = Nothing
peak xs = elemIndex True $ zipWith (==) (scanl1 (+) xs) (scanr1 (+) xs)
  1. I don't think there is a way to reduce the number of arguments, but I think you can give them shorter names in the helper function and if you put the helper function in a where clause then you can also leave out the type signature:
peak :: [Int] -> Maybe Int
peak numbers =    
    let leftSum = 0
        rightSum = sum numbers
        startingIndex = 0
    in  peak' numbers leftSum rightSum startingIndex
  where
    peak' [] _ _ _ = Nothing
    peak' (x:xs) l r i
        | l + x == r = Just i
        | otherwise  = peak' xs (l + x) (r - x) (i + 1) 

Personally, I believe shorter code is usually more readable than long code. And the length of variable names should be proportional to the size of the scope in which they are used, i.e. local variables should get short names and global or top-level variables and functions should get longer names.

  1. I think it is bloat in this case, again I really like short code. And I think 0 is never really considered a magic number.

And recursive helper functions are usually called go in Haskell. And if you put the list as the last argument in the helper function then you can eta-reduce the main function. So the end result would be:

peak :: [Int] -> Maybe Int
peak = go 0 (sum xs) 0 where
  go _ _ _ [] = Nothing
  go l r i (x:xs) 
      | l + x == r = Just i
      | otherwise  = go (l + x) (r - x) (i + 1) xs
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.