1

I'm just learning Haskell and am kind of stuck. I'd like to compare list elements and measure the difference between them and return the highest one. Unfortunatly, I do not know how to approach that problem. For usual, I'd just iterate the list and compare the neighbours but that does not seem to be the way to go in Haskell. I already tried using map but as I said I do not really know how you can solve that problem. I'd be thankful for every kind of advice!

Best wishes

Edit: My idea is to first zip all pairs like this pairs a = zip a (tail a). Then I'd like to get all differences (maybe with map?) and then just chose the highest one. I just can't handle the Haskell syntax.

Henry
  • 727
  • 1
  • 6
  • 25
  • Do you want to return the largest difference or the largest element? – Matt Ellen Dec 11 '13 at 19:09
  • What would be the result for a list such as `[2,0,5,2,7,7,5,6]`? – Frerich Raabe Dec 11 '13 at 19:13
  • I need the largest difference between two neighbours. Hence the result of [2,0,5,2,7,7,5,6] would be the 5-0 or 7-2 = 5. – Henry Dec 11 '13 at 21:19
  • This smells like a homework assignment, is it? –  Dec 11 '13 at 22:14
  • Kind of.. Don't know what else I would need that for :D – Henry Dec 11 '13 at 22:29
  • 1
    @Henry I use a similar function to this (albeit in Python) regularly. It is essentially calculating the derivative of an array (although I don't take the absolute value), which is an incredibly useful thing to be able to do. This sort of function is highly useful in numeric computing. – bheklilr Dec 11 '13 at 22:32

3 Answers3

6

I don't know what you mean by "measure the discrepancy" between list elements, but if you want to calculate the "largest" element in a list, you'd use the built-in maximum function:

maximum :: Ord a => [a] -> a

This function takes a list of values that can be ordered, so all numbers, chars, and strings, among others.

If you want to get the difference between the maximum value and the minimum value, you can use the similar function minimum, then just subtract the two. Sure, there might be a slightly faster solution whereby you only traverse the list once, or you could sort the list then take the first and last elements, but for most cases doing diff xs = maximum xs - minimum xs is plenty fast enough and makes the most sense to someone else.


So what you want to do is compute a difference between successive elements, not calculate the minimum and maximum of each element. You don't need to index directly, but rather use a handy function called zipWith. It takes a binary operation and two lists, and "zips" them together using that binary operation. So something like

zipWith (+) [1, 2, 3] [4, 5, 6] = [1 + 4, 2 + 5, 3 + 6] = [5, 7, 9]

It is rather handy because if one of the lists runs out early, it just stops there. So you could do something like

diff xs = zipWith (-) xs ???

But how do we offset the list by 1? Well, the easy (and safe) way is to use drop 1. You could use tail, but it'll throw an error and crash your program if xs is an empty list, but drop will not

diff xs = zipWith (-) xs $ drop 1 xs

So an example would be

diff [1, 2, 3, 4] = zipWith (-) [1, 2, 3, 4] $ drop 1 [1, 2, 3, 4]
                  = zipWith (-) [1, 2, 3, 4] [2, 3, 4]
                  = [1 - 2, 2 - 3, 3 - 4]
                  = [-1, -1, -1]

This function will return positive and negative values, and we're interested only in the magnitude, so we can then use the abs function:

maxDiff xs = ??? $ map abs $ diff xs

And then using the function I highlighted above:

maxDiff xs = maximum $ map abs $ diff xs

And you're done! If you want to be fancy, you could even write this in point-free notation as

maxDiff = maximum . map abs . diff

Now, this will in fact raise an error on an empty list because maximum [] throws an error, but I'll let you figure out a way to solve that.

bheklilr
  • 53,530
  • 6
  • 107
  • 163
  • Thank you for your answer. Unfortunatly, this is not exactly what I need. As mentioned in another comment I have to compare every difference between 2 neighbours and return the highest one.. I know I can just use !! to select single elements but I don't know how to iterate the whole list – Henry Dec 11 '13 at 21:23
  • @Henry I noticed that you had the right idea to combine `zip` with `tail` (although as I point out `drop 1` is safer), and you just might not have heard of `zipWith` yet. You had the right idea, just needed to figure out how to put the different parts together. – bheklilr Dec 11 '13 at 21:43
  • Actually, thanks to implementation details or `zip`/`zipWith`, `tail` is perfectly safe for this use case. But `drop 1` is much better in many use cases. – Carl Dec 12 '13 at 02:57
3

As mentioned by bheklilr, maximum is the quick and easy solution.

If you want some of the background though, here's a bit. What we're trying to do is take a list of values and reduce it to a single value. This is known as a fold, and is possible with (among others) the foldl function, which has the signature foldl :: (a -> b -> a) -> a -> [b] -> a.

The (a -> b -> a) section of foldl is a function which takes two values and returns one of the first type. In our case, this should be our comparison function:

myMax :: Ord a => a -> a -> a
myMax x y | x > y     = x
          | otherwise = y

(note that Ord a is required so that we can compare our values).

So, we can say

-- This doesn't work!

myMaximum :: Ord a => [a] -> a
myMaximum list = foldl myMax _ list

But what is _? It doesn't make sense to have a starting value for this function, so we turn instead to foldl1, which does not require a starting value (instead it takes the first two values from the list). That makes our maximum function

myMaximum :: Ord a => [a] -> a
myMaximum list = foldl1 myMax list

or, in pointfree format,

myMaximum :: Ord a => [a] -> a
myMaximum = foldl1 myMax

If you look at the actual definition of maximum in Data.List, you'll see it uses this same method.

Elliot Robinson
  • 1,384
  • 7
  • 16
  • Thank you for all that information! Honestly, I didn't understand most of the syntax used but as I explained below other answers I cannot really use maximum. But foldl might seem to work for my purposes as well, could that be? – Henry Dec 11 '13 at 21:36
  • 1
    Not really. `foldl` (and folds in general) is primary useful for reducing a list of things to a single value. For example, finding the maximum or minimum element, or finding the sum or product of items in a list. Generating a list from a list is something that I'd look to `map` or (as used by bheklilr) `zip` or `zipWith`. – Elliot Robinson Dec 11 '13 at 23:28
1

map maps a function over a list. It transforms each thing1 in a list to a thing2.

What you want is to find the biggest difference between two neighbours, which you can't do with map alone. I'll assume you're only looking at numbers for now, because that's just easier.

diffs :: (Num a) => [a] -> [a]
diffs [] = []
diffs [x] = []
diffs (x1:x2:xs) = abs(x1-x2) : (diffs$x2:xs)

mnd :: (Num a, Ord a) => [a] -> a
mnd [] = 0
mnd [x] = 0
mnd xs = maximum$diffs xs

So diffs takes each list item one at a time and gets the absolute difference between it and its neighbour, then puts that at the front of a list it creates at it goes along (the : operator puts an individual element at the front of a list).

mnd is just a wrapper around maximum$diffs xs that stop exceptions being thrown.

Matt Ellen
  • 11,268
  • 4
  • 68
  • 90