Haskell 如何使用数据列表比较和返回数据

Haskell 如何使用数据列表比较和返回数据,haskell,ghci,winghci,Haskell,Ghci,Winghci,我是Haskell的新手,我正在努力找到一种使用类成员变量来返回我正在寻找的成员变量的方法。我有以下数据: data Place = Place {name :: String, north :: Float, east :: Float, rainfall :: [Int] } deriving (Eq, Ord, Show) testData :: [P

我是Haskell的新手,我正在努力找到一种使用类成员变量来返回我正在寻找的成员变量的方法。我有以下数据:

 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]
        ]
我想做的是返回一个离给定位置最近的地方。到目前为止,我能够计算出每个地方到给定位置的距离,并且我确切地知道应该返回哪个项目,但我不知道如何实际执行此操作。这是我到目前为止的代码

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)
在控制台中键入“closestDry 51.5(-0.1)testData”输出:

[0.0,1.7804484,2.059126,2.3086786,3.8078866,5.946426,3.0479496] 
我可以看到,最近的区域必须是“伦敦”,以便与给定的地点列表保持一致,因为距离为“0.0”,但是如何将这个单一的地点返回给我


我不想返回距离列表,但我不知道如何告诉函数获取最小距离并返回相应的位置,因为它需要与其他位置进行比较。

closestDry
基本上是一个无用的混乱,所以请摆脱它。然后,让我们编写一个
distance to
函数,它给出从坐标到某个位置的距离:

distanceTo :: Float -> Float -> Place -> Float
distanceTo lat lon place = distance lat lon (north place) (east place)
现在,让我们编写一个函数,将位置与距离配对:

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

λ> 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)]
到目前为止看起来不错

现在,我们可以使用
minimumBy
comparing
snd
来获取元组,然后使用
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)
让我们试试看:

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


除了将距离设置为,您还可以通过比较来计算距离,如下所示:

closestTo :: Float -> Float -> [Place] -> Place
closestTo lat lon places = minimumBy (comparing (distanceTo lat lon)) places
这样做的优点是不需要任何元组,但缺点是多次重新计算同一位置的距离


注意:
minimumBy
是一个危险的部分函数,如果程序得到一个空列表,它将导致程序崩溃,如果
closestTo
得到一个空列表,就会发生这种情况:

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

如果您关心这一点,您需要通过返回
Maybe Place
来避免它,并在输入列表为空时调整代码以返回
Nothing
,而不是调用
minimumBy
。(依我看,这是哈斯克尔的一个缺点,
minimumBy
应该只返回一个
本身,而不必崩溃。)

非常感谢,这是一个非常聪明的方法。我以后一定会用这种方法,非常感谢你的帮助!挑剔:你的
distance
函数计算欧几里德距离,这实际上不适合经纬度坐标,因为地球不是平的。你应该用一个计算大圆距离的函数来代替它(如果你真的想挑剔的话,也要考虑地球是一个扁球体而不是一个完美的球体)。