haskelleclipse-fp

Comparing List Elements in Haskell


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.


Solution

  • 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.