How can I compare and return data using a list of data

会有一股神秘感。 提交于 2020-05-15 07:48:23

问题


I'm a newbie to Haskell and I'm struggling to find a way to use class member variables to return the member variable I am looking for. I have this data:

 data Place = Place {name :: String, 
                north :: Float, 
                east :: Float, 
                rainfall :: [Int]
                } deriving (Eq, Ord, Show)

 testData :: [Place]
 testData = [
        Place "London"     51.5  (-0.1)  [0, 0, 5, 8, 8, 0, 0],
        Place "Norwich"    52.6  (1.3)   [0, 6, 5, 0, 0, 0, 3],
        Place "Birmingham" 52.5  (-1.9)  [0, 2, 10, 7, 8, 2, 2],
        Place "Hull"       53.8  (-0.3)  [0, 6, 5, 0, 0, 0, 4],
        Place "Newcastle"  55.0  (-1.6)  [0, 0, 8, 3, 6, 7, 5],
        Place "Aberdeen"   57.1  (-2.1)  [0, 0, 6, 5, 8, 2, 0],
        Place "St Helier"  49.2  (-2.1)  [0, 0, 0, 0, 6, 10, 0]
        ]

What I'm trying to do is to return a place closest to a given location. So far I am able to calculate the distances for each place to the given location, and I know exactly which Item should be returned, but I don't know how to actually go about doing this. This is the code I have so far;

closestDry :: Float -> Float -> [Place] -> [Float]
closestDry _ _ [] = []
closestDry lx ly (x:xs) = distance(lx)(ly)(north x)(east x)):closestDry lx ly xs

distance :: Float -> Float -> Float -> Float -> Float
distance x1 y1 x2 y2 = sqrt ((y1 - y2)^2 + (x1 - x2)^2)

Typing into the console 'closestDry 51.5 (-0.1) testData' outputs:

[0.0,1.7804484,2.059126,2.3086786,3.8078866,5.946426,3.0479496] 

I can see that the closest area must be "London" in order with the given list of places as the distance is '0.0', but how do I get this single Place returned to me?

I don't want to return the list of distances, but I can't figure out how to tell the function to get the smallest distance and return that corresponding Place, since it needs to be compared to the other places.


回答1:


closestDry is a basically-useless mess, so get rid of it. Then, let's write a distanceTo function that gives you the distance from coordinates to a place:

distanceTo :: Float -> Float -> Place -> Float
distanceTo lat lon place = distance lat lon (north place) (east place)

Now, let's write a function that pairs the places with the distances to them:

distancesTo :: Float -> Float -> [Place] -> [(Place, Float)]
distancesTo lat lon = map (\place -> (place, distanceTo lat lon place))

Trying it out:

λ> distancesTo 51.5 (-0.1) testData
[(Place {name = "London", north = 51.5, east = -0.1, rainfall = [0,0,5,8,8,0,0]},0.0),(Place {name = "Norwich", north = 52.6, east = 1.3, rainfall = [0,6,5,0,0,0,3]},1.7804484),(Place {name = "Birmingham", north = 52.5, east = -1.9, rainfall = [0,2,10,7,8,2,2]},2.059126),(Place {name = "Hull", north = 53.8, east = -0.3, rainfall = [0,6,5,0,0,0,4]},2.3086786),(Place {name = "Newcastle", north = 55.0, east = -1.6, rainfall = [0,0,8,3,6,7,5]},3.8078866),(Place {name = "Aberdeen", north = 57.1, east = -2.1, rainfall = [0,0,6,5,8,2,0]},5.946426),(Place {name = "St Helier", north = 49.2, east = -2.1, rainfall = [0,0,0,0,6,10,0]},3.0479496)]

Looks right so far!

Now we can use minimumBy, comparing, and snd to get the tuple, and then extract just the place with fst:

import Data.Foldable (minimumBy)
import Data.Ord (comparing)

closestTo :: Float -> Float -> [Place] -> Place
closestTo lat lon places = fst $ minimumBy (comparing snd) (distancesTo lat lon places)

Let's try it:

λ> closestTo 51.5 (-0.1) testData
Place {name = "London", north = 51.5, east = -0.1, rainfall = [0,0,5,8,8,0,0]}

Success!


As an alternative to having distancesTo, you could also calculate the distances with comparing, like this:

closestTo :: Float -> Float -> [Place] -> Place
closestTo lat lon places = minimumBy (comparing (distanceTo lat lon)) places

This has the advantage of not needing any of the tuples, but the disadvantage of recomputing the distance for the same place multiple times.


Caveat to either way: minimumBy is a dangerous partial function, which will crash your program if it ever gets an empty list, which will happen if closestTo gets an empty list:

λ> closestTo 51.5 (-0.1) []
*** Exception: Prelude.foldl1: empty list

If you care about that, you'd need to avoid it by returning a Maybe Place instead, and adjusting the code to return Nothing when the input list is empty, instead of calling minimumBy. (IMO, this is a wart in Haskell, and minimumBy should just return a Maybe itself instead of having to crash.)



来源:https://stackoverflow.com/questions/61262801/how-can-i-compare-and-return-data-using-a-list-of-data

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!