我刚开始学习Haskell。 我正在进行“真实世界Haskell”练习,第3章,我对我不理解的行为感到难过。
我不明白为什么grahamSort
似乎没有正确地打破关系。该函数首先找到参考点P(通过grahamGetFirstCandidate
)然后根据它们和P形成的角度对其他点进行排序。我使用(减去)余弦作为角度的代理
grahamGetFirstCandidate
似乎按预期工作。
代码(对不起,它可能不是很干净):
import Data.List as List
data TwoD = TwoD {
x :: Float,
y :: Float
} deriving (Show, Eq)
dotProduct :: TwoD -> TwoD -> Float
dotProduct (TwoD xa ya) (TwoD xb yb) = xa * xb + ya * yb
grahamGetFirstCandidate :: [TwoD] -> TwoD
grahamGetFirstCandidate [] = error "Trying to find point with minimum y in empty List"
grahamGetFirstCandidate (p:ps) = search p ps where
search :: TwoD -> [TwoD] -> TwoD
search pmin [] = pmin
search pmin (p1:ps) | y pmin > y p1 = search p1 ps
| y pmin < y p1 = search pmin ps
| x pmin > x p1 = search p1 ps
| otherwise = search pmin ps
norm2 :: TwoD -> Float
norm2 (TwoD x y) = sqrt (x ** 2 + y ** 2)
minusCosAngleWithX :: TwoD -> Float
minusCosAngleWithX v = (-1) * dotProduct (TwoD 1 0) v / norm2 v
-- compare according to the angle with X axis. I did not know about Data.Ord.comparing
angleWithXCompare :: TwoD -> TwoD -> Ordering
angleWithXCompare p1 p2 | minusCosAngleWithX p1 > minusCosAngleWithX p2 = GT
| minusCosAngleWithX p1 < minusCosAngleWithX p2 = LT
| norm2 p1 > norm2 p2 = GT -- break ties
| norm2 p1 < norm2 p2 = LT
| otherwise = EQ
vectorDiff :: TwoD -> TwoD -> TwoD
vectorDiff p1 p2 = TwoD (x p2 - x p1) (y p2 - y p1)
grahamSort :: [TwoD] -> [TwoD]
-- sortBy angle (~-cosine) of (p1, p) with x axis
grahamSort ps = let p1 = grahamGetFirstCandidate ps in
p1 : List.sortBy (angleWithXCompare . vectorDiff p1) (filter (/=p1) ps)
main :: IO()
main = let ps = [TwoD 4 3, TwoD 5 1, TwoD 4 1, TwoD 1 2, TwoD 5 2, TwoD 2 1, TwoD 3 5, TwoD 2 3]
in do
print ps
print $ grahamGetFirstCandidate ps
print $ grahamSort ps
这是我得到的输出
[TwoD {x = 4.0, y = 3.0},TwoD {x = 5.0, y = 1.0},TwoD {x = 4.0, y = 1.0},TwoD {x = 1.0, y = 2.0},TwoD {x = 5.0, y = 2.0},TwoD {x = 2.0, y = 1.0},TwoD {x = 3.0, y = 5.0},TwoD {x = 2.0, y = 3.0}]
TwoD {x = 2.0, y = 1.0} -- This is the correct result
[TwoD {x = 2.0, y = 1.0},TwoD {x = 5.0, y = 1.0},TwoD {x = 4.0, y = 1.0},TwoD {x = 5.0, y = 2.0},TwoD {x = 4.0, y = 3.0},TwoD {x = 2.0, y = 3.0},TwoD {x = 3.0, y = 5.0},TwoD {x = 1.0, y = 2.0}]
我想要(并且期待)的是点(4,1)在排序列表中出现之前(5,1)。
如果我改变输入点的顺序,它也会在输出中交换(4,1)和(5,1):
main = let ps = [TwoD 4 3, TwoD 4 1, TwoD 5 1, TwoD 1 2, TwoD 5 2, TwoD 2 1, TwoD 3 5, TwoD 2 3]
in do
print ps
print $ grahamGetFirstCandidate ps
print $ grahamSort ps
[TwoD {x = 4.0, y = 3.0},TwoD {x = 4.0, y = 1.0},TwoD {x = 5.0, y = 1.0},TwoD {x = 1.0, y = 2.0},TwoD {x = 5.0, y = 2.0},TwoD {x = 2.0, y = 1.0},TwoD {x = 3.0, y = 5.0},TwoD {x = 2.0, y = 3.0}]
TwoD {x = 2.0, y = 1.0}
[TwoD {x = 2.0, y = 1.0},TwoD {x = 4.0, y = 1.0},TwoD {x = 5.0, y = 1.0},TwoD {x = 5.0, y = 2.0},TwoD {x = 4.0, y = 3.0},TwoD {x = 2.0, y = 3.0},TwoD {x = 3.0, y = 5.0},TwoD {x = 1.0, y = 2.0}]
我显然缺少一些东西,任何帮助都会受到赞赏。
编辑:嗯,我现在注意到最后一点的顺序也不正确:(3,5)应该出现在(2,3)之前。当我打印余弦时,它们看起来是正确的(=应该给出正确的顺序,直到关系),所以angleWithXCompare
可能有问题。
答案 0 :(得分:5)
你的&#34;比较&#34;有些事情是不对的。功能,例如:
test =
let p1 = TwoD {x = 2.0, y = 1.0}
p2 = TwoD 5 1
p3 = TwoD 4 1
cmp = angleWithXCompare . vectorDiff p1
in (cmp p2 p3, cmp p3 p2)
这会产生:
ghci> test
(LT, LT)
我希望(LT,GT)
或(GT,LT)
更新:您想使用此比较功能:
cmp a b = angleWithXCompare (vectorDiff p1 a) (vectorDiff p1 b)
在List.sortBy
来电中,例如:
grahamSort ps =
let p1 = grahamGetFirstCandidate ps
in
p1 : List.sortBy cmp (filter (/=p1) ps)
where cmp a b = angleWithXCompare (vectorDiff p1 a) (vectorDiff p1 b)