【发布时间】:2015-08-05 18:56:54
【问题描述】:
我刚开始学习 Haskell。 我正在完成“Real World Haskell”第 3 章的练习,但我被一个我不理解的行为难住了。
我不明白为什么我的grahamSort 似乎没有正确打破关系。该函数首先找到一个参考点 P(通过grahamGetFirstCandidate),然后根据它们和 P 与 x 轴形成的角度对其他点进行排序。我使用(减去)余弦作为角度的代理。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 可能有问题。
【问题讨论】:
-
与您的问题完全无关,但您可能喜欢
angleWithXCompare p1 p2 = compare (minusCosAngleWithX p1, norm2 p1) (minusCosAngleWithX p2, norm2 p2)或angleWithXCompare p1 p2 = compare (minusCosAngleWithX p1) (minusCosAngleWithX p2) <> compare (norm2 p1) (norm2 p2)。 (一旦你了解了comparing:angleWithXCompare = comparing minusCosAngleWithX <> comparing norm2,后者就可以很好地简化。) -
感谢@DanielWagner,这比我的 5 行样板函数要清晰得多。我真的不明白
<>做了什么(除了这个用例中的正确的事情)。这个符号有没有可能有一个特定的名称?这对 Google 来说很难,而且 Hoogle 的结果似乎无关紧要;)。 -
alpha deployment of the next version of Hoogle 得到
(<>)的正确答案。这是mappend的另一个名称,来自Monoid- 并且恰好有Monoid的Ordering和a -> b实例(前提是b是Monoid)可以在这里做我们想做的事情。 -
谢谢。目前还没有真正得到它,但至少我知道在哪里可以找到文档。
标签: haskell