排序功能似乎没有打破联系

时间:2015-08-05 18:56:54

标签: haskell

我刚开始学习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可能有问题。

1 个答案:

答案 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)