【问题标题】:Issues while trying to use Maybe尝试使用 Maybe 时的问题
【发布时间】:2017-02-06 10:37:23
【问题描述】:

我创建了以下 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 函数?

【问题讨论】:

  • 我会说 0 是对“此列表中橙色的最大连续出现次数是多少?”这个问题的完全合理的答案。鉴于您有这样的功能,您可以调用它并将答案与 0 进行比较:
  • 当您返回相同的值(例如 0)时,添加 Maybe 包装器是一个好主意,原因有两个。 IE。当你想区分NothingJust 0时。在这里,添加包装器后,您将永远不会返回 Just 0。在这种情况下,添加包装器几乎没有什么好处。具体来说,你想用length :: [a] -> Maybe Int 代替普通的吗?
  • max x y = if x <= y then y else x from source,对于这个简单的逻辑,我想不出更好的自定义构建函数。

标签: haskell maybe


【解决方案1】:

我不明白你为什么这么难:只需使用scanl 确定“连续计数”,然后在其上应用maximum,如果结果为0,则返回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

话虽如此,我不明白你为什么要返回Nothing。在这种情况下,零是一个完全有效的答案。 Maybe 通常用于返回某种“异常”答案。就像 find 一样,它可能无法找到元素。

代码工作如下:

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

-- 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

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

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

接下来在函数定义中,我们检查该值是否等于0。如果是这种情况,我们返回Nothing。否则我们返回Just maxc

【讨论】:

  • 您介意解释一下where 中的代码吗?我还是函数式编程的新手
  • @AndiPavllo:更好?
【解决方案2】:

在这里您可以找到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+) &lt;$&gt; t 利用了 2 元组是函子这一事实,&lt;$&gt; 运算符将作为第一个参数(即我们的例子中的 (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) 元组中获取最大值的原因。

【讨论】:

  • 目标是计算连续Oranges 的最长序列,但不是橙子的总数。
  • 谢谢@Ryan,我已经编辑了答案以提供最长的橙子序列的长度。
  • 可以使用 numOranges = uncurry max $ …countOranges t Orange = (1+) &lt;$&gt; tcountOranges (x, y) _ = (max x y, 0) 来减少比较。
  • 很好的@Ryan 答案已根据您的建议更新。
猜你喜欢
  • 1970-01-01
  • 2014-09-27
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-09-13
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多