3

im new to haskell and i have to do a function that takes a list and calculates the distance recursively.

For example:
distance [(0,0),(2,0),(2,5)]
->7
distance [(1,1),(3,4)]
->3.6055512

I made the distance between just two points like this

distance (x1 , y1) (x2 , y2) = sqrt 
(x'*x' + y'*y')
where
  x' = x1 - x2
  y' = y1 - y2

But dont know how do to it with a variable list size, thanks

John
  • 45
  • 5
  • Hint: calculate the distance between each pair of points, then add up all the distances you have found. – bradrn Oct 17 '19 at 08:42

3 Answers3

3

We can rename this function to distance2 to specify that it calculates the distance between two points:

distance2 :: Floating a => (a, a) -> (a, a) -> a
distance2 (x1 , y1) (x2 , y2) = sqrt (x'*x' + y'*y')
    where x' = x1 - x2
          y' = y1 - y2

Next we can make use of zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] to iterate concurrently over two lists, and apply a function to the elements. Here we iterate over the list, and its tail. This will thus produce a list of distances. We can make use of sum :: (Num a, Foldable f) => f a -> a to sum up these distances:

distance2 :: Floating a => [(a, a)] -> a
distance [] = 0
distance xa@(_:xs) = sum (zipWith distance2 xa xs)
Willem Van Onsem
  • 443,496
  • 30
  • 428
  • 555
2

Firstly, like @WillemVanOnsem, I will rename distance to distance2:

distance2 :: Floating a => (a, a) -> (a, a) -> a
distance2 (x1 , y1) (x2 , y2) = sqrt (x'*x' + y'*y')
    where x' = x1 - x2
          y' = y1 - y2

Next, given a list, a function to split it into pairs:

splitPairs :: [a] -> [(a,a)]
splitPairs (x:y:xs) = (x,y) : (splitPairs (y:xs))
splitPairs _ = error "can’t split into pairs when <2 elements!"

Finally, given a list of points, split it into pairs, calculate the distance between each pair, and add them up:

distance :: Floating a => [(a,a)] -> a
distance = sum . map (uncurry distance2) . splitPairs
bradrn
  • 8,337
  • 2
  • 22
  • 51
  • 4
    `splitPairs = zip <*> tail`; while not being immediately obvious, it's a nice idiom. – chepner Oct 17 '19 at 12:33
  • I think Willem's interpretation for the <2elems case to give 0 is more sensible than throwing an error. Really the function should probably be called something like `pathLength`, not `distance`. In that case a path with less than 2 vertices has length 0. – leftaroundabout Oct 17 '19 at 16:54
  • @chepner That is a really nice definition! But I opted to use my definition for clarity — it isn’t immediately obvious how `zip <*> tail` works. – bradrn Oct 17 '19 at 21:19
  • @leftaroundabout Good point about <2elems — I’ll edit that in now. And I agree about `pathLength`, but when the question was asked it was called `distance`, and I felt that it was easier to keep the function name the same. – bradrn Oct 17 '19 at 21:20
1

Without recursion it would be best to use zipWith and sum functions with the applicative operator <*>.

Prelude> :{
Prelude| dist :: Floating a => [(a, a)] -> a
Prelude| dist = sum . (ds <*> tail)
Prelude|        where ds = zipWith (\f s -> sqrt ((fst f - fst s)^2 + (snd f - snd s)^2))
Prelude| :}
Prelude> dist [(0,0),(2,0),(2,5)]
7.0
Prelude> dist [(1,1),(3,4)]
3.605551275463989
Redu
  • 25,060
  • 6
  • 56
  • 76