Min-Max Sum Hackerrank Problem using haskell

344 views Asked by At

I'm trying to solve the Mini-Max Sum from HackerRank using Haskell. I'm just getting started with Haskell. Watched video's from #tsoding (youtube channel) and got inspired with his one line solutions to the problems.

I'm trying to provide a one-line solution to this problem. I'm following the 2nd approach from this youtube video:

  1. Find the minimum element in the array;
  2. Find the maximum element in the array;
  3. Calculate the sum of all the elements in the array;
  4. Calculate minimum by subtracting max from sum; and
  5. Calculate maximum by subtracting min from sum.

This is what my code looks like so far:

main = interact $ show . foldr (-) 0 [minimum, maximum, sum] . map read . words

foldr (-) 0 [minimum, maximum, sum] (here is it possible to get the sum of numbers to be piped for foldr function?)

Even though my current code is wrong, I'm imagining the solution would look something like this. Could someone please help me with the syntax if this is possible?

1

There are 1 answers

3
MikaelF On BEST ANSWER

The main issue in your code (and I think also what you were trying to solve) is that you're trying to collapse a list of functions by combining them with (-) :: Num a => a -> a -> a, which doesn't really make sense. If I understand correctly, what you want is to apply a single value (the input list) to every function in a list. There are multiple ways to do this, as explained in the answers to this recent SO question. With this simple trick, you're almost there. Here's the rest of the algorithm.

For brevity's sake, I will use the following input list:

$> let list = [1,2,3,4,5]

Using one of the methods from the linked SO question, you can feed this to every function in the list:

$> sequenceA [maximum, minimum, sum] list
$> [5,1,15]

Then you can pattern match on the list to get the values you want:

$> let [ma, mi, su] = sequenceA [maximum, minimum, sum] list in [su - ma, su - mi]
$> [10,14]

One inconvenient of this let-in construct is that it doesn't allow for eta-reduction of the list variable, so you will either have to use a lambda or a where clause. For a function this long, I think a lambda isn't very readable, so I prefer a where clause:

main = interact $ unwords . map show . f . map read . words
  where f xs = let [ma, mi, su] = sequenceA [maximum, minimum, sum] xs 
               in  [su - ma, su - mi]

I modified your main function a bit, since what you want to print out is not the list show list, but every number in the list, separated by spaces: unwords $ map show list. You can also think of this as the opposite of what you had done to get the list in the first place (map read . words).

Because this involves pattern-matching, I think it works better as a multi-line function, but there might be ways to solve it as a one-liner (other than making f a long and ugly lambda).

EDIT

Here is a one-liner which uses arrows (from Control.Arrow) and the Applicative instance of -> to feed the list to steps 4 and 5, and therefore avoid the long lambda:

main = interact $ unwords . map show . (\(a,b) -> [a,b]) . 
       ((-) . sum <*> maximum &&& (-) . sum <*> minimum) . 
       map read . words

There is still some pattern-matching involved (to convert the tuple produced by &&& into a list), and I don't think it's more readable. Maybe there's a better way, but I don't really see it.