xentaquadrin
xentaquadrin

Reputation: 57

Calculate the differences of list elements using zipWith

I have to make a function called differences, where I calculate the difference of every pair with zipWith and put it in a list.

For example differences [1..5] == [1, 1, 1, 1].

So [2-1, 3-2, 4-3, 5-4] == [1, 1, 1, 1].

I thought of making list of tuples, like this:

[1..5] = [(1,2), (2,3), (3,4), (4,5)]

Then use list comprehesion like this:

[zipWith (-) a b | a <- y, b <- x]

where x is the first element of the tuple and y is the second.

The functions type is differences :: Num a => [a] -> [a].

Upvotes: 2

Views: 941

Answers (3)

duplode
duplode

Reputation: 34378

As 4castle suggests, using drop 1 instead of tail in Robin Zigmond's second, init-less solution means we can omit the [] case, as drop 1 [] = [] (unlike tail [], which leads to a runtime error):

differences xs = zipWith (-) (drop 1 xs) xs

To contrast with this solution with no explicit pattern deconstruction, I will mention a spelling using none of init, tail and drop:

differences xs@(_:ys) = zipWith (-) ys xs

Upvotes: 1

chepner
chepner

Reputation: 531075

Building off Robin Zigmond's answer, the Applicative instance for functions works well here:

(f <*> g) xs == f xs (g xs)

so

differences = zipWith subtract <*> tail

(where subtract = flip (-).)

Upvotes: 3

Robin Zigmond
Robin Zigmond

Reputation: 18249

You're nearly there - but zipWith itself returns a list, so you don't want to put that inside a list comprehension unless you want the result to be a list of lists (which you don't, here).

zipWith (-) is absolutely the right idea here - it takes 2 lists and gives a new list given by taking the difference between corresponding elements of the given lists. Your output list, in your case, is intended to be 1 element shorter than the one input list, and you want to use zipWith (-) on 2 lists which consist of:

  • all elements of the given list, other than the first
  • all elements of the given list, other than the last

Haskell already gives us convenient functions for these, namely tail and init.

So the function you're looking for is:

differences xs = zipWith (-) (tail xs) (init xs)

Note that this isn't ideal, because both init and tail will crash your program with an ugly runtime error if xs is empty. It makes sense to output an empty list if you present an empty list to this function (although you could argue it doesn't, since you will get an empty list from a singleton list), so you can avoid a runtime crash by defining the function via pattern matching to explicitly cater for the empty list:

differences [] = []
differences xs = zipWith (-) (tail xs) (init xs)

While personally I think this is fine, and very explicit, you don't actually need to use both init and tail - zipWith works just fine if presented with lists of unequal length, when it will simply trim the larger one down to size. So differences xs = zipWith (-) (tail xs) xs is a viable, and slightly terser, alternative.

Upvotes: 3

Related Questions