Haskell函数寻求进一步解释

时间:2016-11-12 23:18:15

标签: haskell

我一直在尝试创建一个haskell函数,它获取列表中最大的奇数。听起来很简单,我确信我能做到,但看起来我已经卡住了!这是我的代码,到目前为止它返回列表中的最大数字,包括奇数和偶数:

maximum :: (Ord a) => [a] -> a  
maximum [x] = x  
maximum (x:xs) = max x (maximum xs)

提前感谢您的帮助!

3 个答案:

答案 0 :(得分:2)

首先 - 如果你的列表中只有偶数元素就会出现问题,因为你的函数不是全部(即产生错误) - 因此最好修改结果的类型来解释可能性:

正如其他人已经提到的那样 - odd要求您的约束为Integral而不是Ord

maximodd :: Integral a => [a] -> Maybe a
maximodd xs = case filter odd xs' of
                []  -> Nothing
                xs' -> Just (maximum xs')

函数filter odd删除所有非奇数元素(即所有偶数) - 然后我们对结果进行模式匹配以解决失败。

如果您想使用原始功能,那么代码会变得更加棘手:

maximum :: Integral a => [a] -> a
maximum [x] = x
maximum (x:xs) = let y = maximum xs
                 in if x >= y && odd x
                      then x
                      else if x < y && odd y
                             then y
                             else ??

嗯 - 我认为你不能让它像这样工作 - 但也许有辅助功能。

maximum :: Integral a => [a] -> Maybe a
maximum xs = maximum' Nothing xs
  where maximum' Nothing (x:xs) = if odd x
                                    then maximum' (Just x) xs
                                    else maximum' Nothing xs
        maximum' (Just y) (x:xs) = if odd x
                                     then maximum' (Just $ max x y) xs
                                     else maximum' (Just y) xs
        maximum' x _ = x

这也更复杂 - 而且haskell绝对不是因为复杂而闻名;)

答案 1 :(得分:2)

maximumOdd :: Integral a => [a] -> Maybe a
maximumOdd = foldl' go Nothing
  where
    go Nothing x | odd x = Just x
    go (Just gr) x | odd x && x > gr = Just x
    go acc _ = acc

答案 2 :(得分:1)

有几个选项可以教你一些语言。 首先, epsilonhalbe 提到的整体性问题。现在,我只会让代码抛出错误。

maximumOddError :: a
maximumOddError = error "maximumOdd: list contains no odd element"

最重要的复杂因素是,当你开始时,你根本不知道是否会有任何奇数。因此,您必须推迟该测试(我们将会这样做)或过滤并一次性检查。

选项1:尾递归

最简单的解决方案是同时进行两项检查。通常,这也是一种很好的优化技术。目前,它将错误处理和迭代分开。

maximumOdd :: (Integral a) => [a] -> a
maximumOdd ls = case dropWhile even ls of
                  []   -> maximumOddError
                  n:ns -> findOddMax n ns -- n is definitely odd
  where
    findOddMax currentMax [] = currentMax
    findOddMax currentMax (x:xs)
        | even x         = findOddMax currentMax xs
        | x < currentMax = findOddMax currentMax xs
        | otherwise      = findOddMax x          xs

选项2:排序

不是看每一件作品,你也可以采取高层次的观点。为什么要搜索,何时只需对列表进行排序并将搜索的元素放在一端?当然你必须仍然过滤,所以你想要的基本上是last . sort . filter odd。但是last是另一次遍历,所以最好按降序对列表进行排序:

import Data.List ( sortBy )

maximumOdd :: (Integral a) => [a] -> a
maximumOdd = head . sortBy reverseOrder . filter odd
  where
    reverseOrder a b = compare b a

请注意head在空列表中失败。所以错误处理并不好。但它应该很容易添加。与简单的maximum . filter odd相比,性能有何影响?你告诉我!

选项3:具有良好错误处理的尾递归

现在让我们通过Maybe添加一些更好的错误处理。最简单的版本是尾递归,因为我们分离了错误处理和迭代。

maximumOdd :: (Integral a) => [a] -> Maybe a
maximumOdd ls = case dropWhile even ls of
                  []   -> Nothing
                  n:ns -> Just $ findOddMax n ns -- n is definitely odd
  where
    findOddMax … -- same as above

这与 dfeuer 的方法非常相似。他只是使用了更多的现有函数来使其更短,更高效,并且他将错误处理集成到递归中。这是最有经验的Haskell程序员将要做的事情。 (你可以争论包装和展开的性能成本,但是呃。当你需要它时这样做。)

选项4:排序错误处理

这个也很容易,因为head的一个很好的版本完全符合我们的需要。

import Data.Maybe ( listToMaybe )

maximumOdd :: (Integral a) => [a] -> Maybe a
maximumOdd = listToMaybe . sortBy reverseOrder . filter odd
  where
    reverseOrder a b = compare b a

选项5:具有良好错误处理的直接遍历

最后,我们回到最初的方法。但是Maybe现在允许我们推迟错误检查。问题是:您需要了解一些特殊工具,或者您必须发明它们。我只是使用现有的。

import Control.Applicative ( (<|>) )

maximumOdd :: (Integral a) => [a] -> Maybe a
maximumOdd []                 = Nothing
maximumOdd [x]    | even x    = Nothing
                  | otherwise = Just x
maximumOdd (x:xs) | even x    =           maximumOdd xs
                  | otherwise = max x <$> maximumOdd xs -- either "max x" applied inside the Maybe-construct
                                <|> Just x              -- or x, if that produced an error