Haskell ::为一组元组找到Min和Max

时间:2017-08-28 08:51:48

标签: haskell

我试图找到元组的最小值和最大值。代码在这里:

fFindMinMax :: (Ord a, Ord b, Ord c, Ord d) => [(a, b, c, d)] -> (Double,Double,Double,Double,Double,Double)
fFindMinMax [(_, x, y, z)] = (minimum x, maximum x, minimum y, maximum y, minimum z, maximum z)   

错误来自最大x,最大y和最大z。

  

无法匹配预期类型t1 Double与实际类型b。

有人可以启发我吗?

2 个答案:

答案 0 :(得分:3)

首次尝试

首先,您当前的模式:

fFindMinMax [(_, x, y, z)] = ...

表示仅当您使用一个元素传递列表时它才有效。因此,从传递空列表或包含两个或更多元素的列表开始,这将失败。

你需要的是map来获取元组的所有第二/第三/第四元素。我们可以这样做:

fFindMinMax cs = ...
    where cSnd = map (\(_,x,_,_) -> x) cs
          cThr = map (\(_,_,y,_) -> y) cs
          cFou = map (\(_,_,_,z) -> z) cs

所以现在的问题是:我们应该将什么写为函数谓词。实际上你已经在那里尝试了:我们可以为每个坐标计算minimum cSndmaximum cSnd等等。所以:

fFindMinMax cs = (minimum cSnd, maximum cSnd, minimum cThr, maximum cThr, minimum cFou, maximum cFou)
    where cSnd = map (\(_,x,_,_) -> x) cs
          cThr = map (\(_,_,y,_) -> y) cs
          cFou = map (\(_,_,_,z) -> z) cs

现在该功能的类型是什么?我们不必将我们限制为Double,也不必确保所有坐标都具有相同的类型,fFindMinMax的类型签名可以是:

fFindMinMax :: (Ord x, Ord y, Ord z) => [(a, x, y, z)] -> (x, x, y, y, z, z)
fFindMinMax cs = (minimum cSnd, maximum cSnd, minimum cThr, maximum cThr, minimum cFou, maximum cFou)
    where cSnd = map (\(_,x,_,_) -> x) cs
          cThr = map (\(_,_,y,_) -> y) cs
          cFou = map (\(_,_,_,z) -> z) cs

请注意,如果您为其指定一个空列表,则此程序将出错,因为未定义空列表的minimummaximum

提高内存消耗

上述解决方案的一个问题是我们使用第二,第三和第四个元素构建列表。由于我们不会同时计算最小值和最大值,这意味着我们最终会将此列表存储到内存中。

我们可以通过编写一个帮助函数来改进代码,该函数同时为同一个列表计算minimummaximum

{-# LANGUAGE BangPatterns #-}

minmax :: Ord a => [a] -> (a,a)
minmax [] = error "Empty list"
minmax (h:t) = minmax' h h t
    where minmax' a b [] = (a,b)
          minmax' !a !b (c:cs) = minmax' (min a c) (max b c) cs

这个功能如何运作?那么第一行是相当简单的:如果给出一个空列表,我们无法计算最小值或最大值,因此我们引发错误空列表。在后一种情况下,该列表具有第一项h和其余项t。我们认为h是迄今为止获得的最小值和最大值:如果列表包含单个元素,则该元素既是该列表的最小值也是最大值。因此,我们调用一个额外函数minmax' :: Ord a => a -> a -> [a] -> a,通过给出迄今为止获得的最小值和最大值(hh)以及列表的剩余部分来计算最小值和最大值。

每次迭代时,它将采用下一个元素c,并使用迄今为止最小min和下一个元素a计算c的最小值,并计算到目前为止最大b和下一个元素c的最大值。我们一直在迭代这样做,直到我们到达列表的末尾。在这种情况下,迄今为止获得的最小值和最大值是最终的最小值和最大值。所以我们可以将它们作为(a,b)返回。

然后我们可以使用如下函数:

fFindMinMax :: (Ord x, Ord y, Ord z) => [(a, x, y, z)] -> (x, x, y, y, z, z)
fFindMinMax cs = (xmi, xma, ymi, yma, zmi, zma)
    where (xmi, xma) = minmax $ map (\(_,x,_,_) -> x) cs
          (ymi, yma) = minmax $ map (\(_,_,y,_) -> y) cs
          (zmi, zma) = minmax $ map (\(_,_,_,z) -> z) cs

答案 1 :(得分:1)

恒定空间解决方案

fFindMinMax :: (Ord x, Ord y, Ord z) => [(a, x, y, z)] -> (x, x, y, y, z, z)
fFindMinMax ((_, x, y, z):rest) = foldl' minmax (x, x, y, y, z, z) rest
fFindMinMax []                  = error "fFindMinMax: empty list"
  where
    (xMin', xMax', yMin', yMax', zMin', zMax') `minmax` (_, x, y, z) =
        let
          !xMin = min xMin' x
          !xMax = max xMax' x
          !yMin = min yMin' y
          !yMax = max yMax' y
          !zMin = min zMin' z
          !zMax = max zMax' z
        in (xMin, xMax, yMin, yMax, zMin, zMax)