点列表的质心

3

初学Haskell

问题如下:

-- The centroid of a list of points is a point whose x (and y) coordinates are
-- the means of the x (and y) coordinates of the points in the list.
--
-- You may assume the list contains at least one point.
--
-- > centroid [Pt 1 1, Pt 2 2]
-- Pt 1.5 1.5
-- > centroid [Pt (-1.5) 0, Pt 3 2, Pt 0 1]
-- Pt 0.5 1.0

尝试像这样编写代码

data Point = Pt Double Double deriving (Show, Eq)

centroid :: [Point] -> Point

pointX :: Point -> Double
pointX (Pt x y) = x
pointY :: Point -> Double
pointY (Pt x y) = y

pointsX :: [Point] -> [Double]
pointsX xs = map pointX xs
pointsY :: [Point] -> [Double]
pointsY xs = map pointY xs

average :: [Double] -> Double
average xs = (sum xs) `div` (genericLength xs)

centroid cenpoint = (Pt average(pointsX cenpoint) average(pointsY cenpoint))

我得到了

Project1.hs:35:22: error:
    • Couldn't match expected type ‘([Double] -> Double)
                                    -> [Double] -> Point’
                  with actual type ‘Point’
    • The function ‘Pt’ is applied to four arguments,
      but its type ‘Double -> Double -> Point’ has only two
      In the expression:
        (Pt average (pointsX cenpoint) average (pointsY cenpoint))
      In an equation for ‘centroid’:
          centroid cenpoint
            = (Pt average (pointsX cenpoint) average (pointsY cenpoint))
   |
35 | centroid cenpoint = (Pt average(pointsX cenpoint) average(pointsY cenpoint))
   |                      ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^

Project1.hs:35:25: error:
    • Couldn't match expected type ‘Double’
                  with actual type ‘[Double] -> Double’
    • Probable cause: ‘average’ is applied to too few arguments
      In the first argument of ‘Pt’, namely ‘average’
      In the expression:
        (Pt average (pointsX cenpoint) average (pointsY cenpoint))
      In an equation for ‘centroid’:
          centroid cenpoint
            = (Pt average (pointsX cenpoint) average (pointsY cenpoint))
   |
35 | centroid cenpoint = (Pt average(pointsX cenpoint) average(pointsY cenpoint))
   |                         ^^^^^^^

Project1.hs:35:33: error:
    • Couldn't match expected type ‘Double’ with actual type ‘[Double]’
    • In the second argument of ‘Pt’, namely ‘(pointsX cenpoint)’
      In the expression:
        (Pt average (pointsX cenpoint) average (pointsY cenpoint))
      In an equation for ‘centroid’:
          centroid cenpoint
            = (Pt average (pointsX cenpoint) average (pointsY cenpoint))
   |
35 | centroid cenpoint = (Pt average(pointsX cenpoint) average(pointsY cenpoint))
   |

1
FYI,使用记录语法,data Point = Pt { pointX,pointY :: Double } deriving (Show, Eq),你可以免费获得两个函数pointXpointY - Will Ness
3个回答

3
这里的主要问题是你像在Java、C++或Python等语言中一样调用函数。需要将average包裹在括号中,例如:
centroid cenpoint = Pt <b>(</b>average (pointsX cenpoint)<b>)</b> <b>(</b>average (pointsY cenpoint)<b>)</b>

@dfeuer所说,此处外括号不必要。
另外,由于您正在使用Double,而div适用于Integral类型,因此您可能想在这里使用(/)
average :: [Double] -> Double
average xs = sum xs <b>/</b> genericLength xs

但就像@leftroundabout所说,我们可以更好地使用length,然后使用fromIntegral

average :: [Double] -> Double
average xs = sum xs <b>/</b> <b>fromIntegral</b> (length xs)

我认为值得一提的是最外层的括号是不必要的。 - dfeuer
genericLength 是有争议的。我会使用 fromIntegral (length xs) - leftaroundabout
@leftaroundabout 为什么 genericLength 会引起争议?我认为它与 fromIntegral . length 完全相同,即具有多态返回值的 length 版本(实际上应该在 Prelude 中使用而不是现有的单态 length)。 - Robin Zigmond
1
@RobinZigmond:我认为这是因为对于Double类型,存在(大)值使得x + 1.0 == x,因为尾数不能“分辨率”增加一。这意味着对于大型列表,genericLength函数将在给定长度处“停止计数”。 - Willem Van Onsem
2
@RobinZigmond 是的,关于 x + 1 ≡ x 的问题是浮点数特别容易出现的一个具体问题 - 但在 Double 的情况下,这只会在处理大量数据时才会成为问题。但更普遍地说,在自定义数字类型中进行大量加法运算并不合理,因为 Int 基本上总是足够大且性能保证良好的最佳选择。使用 genericLength 可能会导致您在非常低效的类型中执行大量加法运算,而不是在 Int 中执行所有运算,最后仅转换结果。 - leftaroundabout
显示剩余2条评论

3
你之前的进展还不错-注意到编译错误都指向你的最后一行(即centroid的定义)。而且所有错误都是由于括号不正确导致的。应该将其更正为以下内容:
centroid cenpoint = Pt (average (pointsX cenpoint)) (average (pointsY cenpoint))

即,得到的 Pt 值的 x 坐标是相应点的 x 坐标的平均值(pointsX cenpoint 是 x 坐标的列表,因此 average(pointsX cenpoint) 是它们的平均值),y 坐标同理。

与您的错误版本进行比较(我稍微整理了一下,但没有改变编译器解释的方式):

Pt average (pointsX cenpoint) average (pointsY cenpoint)

这意味着你将 Pt 函数应用于4个参数: averagepointsX cenpoint、再次是averagepointsY cenpoint。这根本行不通,因为Pt函数只接受2个参数。

1

虽然这并不是回答问题的方式,但这是做到它的首选方法:

{-# LANGUAGE DeriveGeneric, DeriveAnyClass #-}

import Data.VectorSpace
import GHC.Generics

data Point = Pt Double Double
   deriving (Eq, Show, Generic, AdditiveGroup, VectorSpace)

average :: (VectorSpace v, Fractional (Scalar v)) => [v] -> v
average ps = sumV ps ^/ fromIntegral (length xs)

然后你可以直接执行以下操作。
> average [Pt (-1.5) 0, Pt 3 2, Pt 0 1]
Pt 0.5 1.0

即,centroid ≡ average

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接