尝试使用 Maybe 时出现问题

Issues while trying to use Maybe

我创建了以下 Haskell 函数来检查列表中 Orange 的最大连续出现次数:

import Data.Maybe
data Fruit = Apple | Orange
findMaxSubStr :: [Fruit] -> Maybe Int

findMaxSubStr xs = val xs 0 0
    where val [] prev current = (max prev current)
          val (Apple:xs) prev current = val xs (max prev current) 0
          val (Orange:xs) prev current = val xs prev (current + 1)

我现在正在尝试集成 Maybe 类型,以防出现次数为 0。例如,我希望 [][Apple, Apple] 生成 Nothing 而不是 0.我该如何进行?

此外,如果我想创建自己的函数来检查最大值,最好的方法是什么,而不是使用预建的 max 函数?

我不明白你为什么这么难:只需使用 scanl 确定 "consecutive counts",然后在其上应用 maximum,如果结果是 0, return Nothing, 否则 Just ...:

findMaxSubStr fruits | maxc == 0 = Nothing
                     | otherwise = Just maxc
    where maxc = maximum counts
          counts = scanl f 0 fruits
          f x Orange = x+1
          f _ _      = 0

话虽如此,我不明白你为什么要 return Nothing。在这种情况下,零是一个完全有效的答案。 Maybe 通常用于 return 某种 "exception" 答案。像 find 可能找不到元素。

代码的工作原理如下:

首先我们执行 scanlscanl 通过列表传递某种 累加器 :对于每个元素,它使用累加器和列表中的对象调用函数(此处 f)。结果是 "new" 累加器。然后将此结果 returned 作为结果列表中的一个元素,并重新用于将累加器传递给下一个元素。所以一般来说它是这样的:

-- example of scanl for three elements (this is not its real implementation)
scanl f acc0 [xa,xb,xc] = [acc1,acc2,acc3]
    where acc1 = f acc0 xa
          acc2 = f acc1 xb
          acc3 = f acc2 xc

在这种情况下,累加器是 Orange 序列的长度 到目前为止 : 所以 [Orange,Apple,Apple,Orange,Orange,Orange,Apple,Orange] 将映射到 [1,0,0,1,2,3,0,1] 此结果将存储在 counts.

现在我们计算该列表 countsmaximum。所以结果 - maxc - 是 counts 的最大值,因此是 最长 (不是当前)橙子序列的长度。

接下来在函数定义中我们检查该值是否等于 0。如果是这样的话,我们returnNothing。否则我们 return Just maxc.

在这里您可以找到 findMaxSubStr 函数的定义,它使用折叠直接计算列表中最长的橙子序列的长度,而无需创建中间列表:

import Data.List (foldl')

findMaxSubStr :: [Fruit] -> Maybe Int
findMaxSubStr fruits =
  case numOranges of
    0 -> Nothing
    n -> Just n
  where
    numOranges = uncurry max $ foldl' countOranges (0, 0) fruits
    countOranges t Orange = (1+) <$> t
    countOranges (x,y) _ = (max x y, 0)

this page 中,您可以找到关于何时使用 foldl' 与使用 foldrfoldl 的讨论,以及显示这些折叠函数如何工作的示例。

元组用于跟踪橙色序列的长度。第一个值保留找到的最长序列的长度,第二个元素存储当前序列的长度。

where 块的原始实现是:

where
  numOranges = fst $ foldl' countOranges (0, 0) fruits
  countOranges (x, y) Orange = let y' = y+1 in (max x y', y')
  countOranges (x, _) _  = (x, 0)

我已经根据@Ryan 的建议编辑了答案(请参阅下面的评论),这里有一些说明现在解决方案如何工作的注释:

countOranges t Orange = (1+) <$> t 利用二元组是函子这一事实,<$> 运算符将作为第一个参数传递的函数(即我们的例子中的 (1+))应用到第二个2元组的元素。因此,在计数器 (x, y) 中,我们在寻找橙子时增加 y,而不触及 x,即我们正在计算当前橙色序列的长度(当我们找到一个时)。

当我们找到苹果时,countOranges (x,y) _ = (max x y, 0) 将更新 (x,y) 中的 x 并将 y 重置为 0。您可以看到 x 将保存迄今为止找到的最长橙子序列的长度。

如果最长的橙子序列出现在水果列表的末尾,则不会发生上一段描述的x更新。这就是我们仍然需要 numOranges = uncurry max $ ...foldl'.

返回的 (x, y) 元组中获取最大值的原因