Home > Blockchain >  Haskell: Return the item of a list which absolute difference to its predecessor is equal or smaller
Haskell: Return the item of a list which absolute difference to its predecessor is equal or smaller

Time:12-15

Let's assume I have a list:

[1, 2, 1.2, 1.8, 1.3, 1.7, 1.35, 1.65...]

Obviously the absolute difference between one element and its predecessor becomes smaller and smaller. I am looking for a function to return the first value in the list which absolute difference to its predecessor is equal or smaller than a certain value.

The definition should be:

approx :: Rational -> [Rational] -> Rational

where the first argument is the desired absolute difference and the second is the given list. E.g.:

approx 0.4 [1, 2, 1.2, 1.8, 1.3, 1.7, 1.35, 1.65...]

This should return 1.7.

I am pretty new to Haskell. That's why I have a hard time to get into the mindset of functional programming. My idea would be to remove the first item of the list as long as the absolute difference to the next item is not equal or below the desired value, otherwise return the second value. How can I get this idea into Haskell?

CodePudding user response:

As suggested in comments, you need to zip the list with its own tail.

Assuming lst is [1, 2, 1.2, 1.8, 1.3, 1.7, 1.35, 1.65]:

zip lst (tail lst)

Yields:

[(1.0,2.0),(2.0,1.2),(1.2,1.8),(1.8,1.3),(1.3,1.7),(1.7,1.35),(1.35,1.65)]

Want the second element from each tuple, and the difference between the two values. We'll use map.

map (\(f, s) -> (s, abs (f - s))) $ zip lst (tail lst)

Yields:

[(2.0,1.0),(1.2,0.8),(1.8,0.6000000000000001),(1.3,0.5),(1.7,0.3999999999999999),(1.35,0.34999999999999987),(1.65,0.2999999999999998)]

Now we just need to find the first element that meets a condition. Let's define a function that can do this. We'll use the Maybe type because we might not find anything which satisfies the predicate.

findFirst :: (t -> Bool) -> [t] -> Maybe t
findFirst pred [] = Nothing
findFirst pred (x:xs) = if pred x then Just x else findFirst pred xs

And now:

findFirst (\(val, diff) -> diff <= 0.4) $ map (\(f, s) -> (s, abs (f - s))) $ zip lst (tail lst)

Yields the following, and the data you need just needs to be extracted and all of this logic wrapped up in a function.

Just (1.7,0.3999999999999999)

As an additional step, we could embellish findFirst to make findFirstAndTransform.

findFirstAndTransform :: (t -> Bool) -> (t -> a) -> [t] -> Maybe a
findFirstAndTransform f t [] = Nothing
findFirstAndTransform f t (x:xs) = if f x then Just (t x) else findFirstAndTransform f t xs

And now when we evaluate:

findFirstAndTransform (\(val, diff) -> diff <= 0.4) fst $ map (\(f, s) -> (s, abs (f - s))) $ zip lst (tail lst)

We get:

Just 1.7

CodePudding user response:

The answer is:

approx eps list=snd (head (filter (\(x,y)-> abs(x-y) <= eps) (zip list (tail list))))
  • Related