【问题标题】:Sort function does not seem to break ties排序功能似乎并没有打破僵局
【发布时间】: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) &lt;&gt; compare (norm2 p1) (norm2 p2)。 (一旦你了解了comparingangleWithXCompare = comparing minusCosAngleWithX &lt;&gt; comparing norm2,后者就可以很好地简化。)
  • 感谢@DanielWagner,这比我的 5 行样板函数要清晰得多。我真的不明白&lt;&gt; 做了什么(除了这个用例中的正确的事情)。这个符号有没有可能有一个特定的名称?这对 Google 来说很难,而且 Hoogle 的结果似乎无关紧要;)。
  • alpha deployment of the next version of Hoogle 得到(&lt;&gt;) 的正确答案。这是 mappend 的另一个名称,来自 Monoid - 并且恰好有 MonoidOrderinga -&gt; b 实例(前提是 bMonoid)可以在这里做我们想做的事情。
  • 谢谢。目前还没有真正得到它,但至少我知道在哪里可以找到文档。

标签: haskell


【解决方案1】:

您的“比较”功能有问题,例如:

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)

【讨论】:

  • 谢谢!我被. 和部分函数应用程序迷住了……我想要的是(vectorDiff p1)在传递给angleWithXCompare 之前应用于所有参数。我认为发生的事情是我的函数将第一个参数传递给(vectorDiff p1),但第二个参数直接传递给angleWithXCompare
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2021-12-22
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-12-29
  • 2016-08-27
相关资源
最近更新 更多