【问题标题】:Decide if a list has an even number of elements without counting the number of elements判断一个列表是否有偶数个元素而不计算元素个数
【发布时间】:2021-12-04 21:35:02
【问题描述】:

实现isLengthEven :: [a] -> Bool 函数,该函数决定列表是否包含偶数个元素。在本练习中,禁止使用length 函数或任何其他返回列表元素数量的函数。提示:我们只需要检查我们是否可以两个两个遍历整个列表,或者函数是否在最后遗漏了一个项目。

例如:isLengthEven "Apple" == TrueisLengthEven "Even" == TrueisLengthEven [] == False

到目前为止,我尝试了模式匹配和递归,我认为这是进行此练习的最佳方式。我的代码如下:

isLengthEven :: [a] -> Bool
isLengthEven [] = False
isLengthEven (x:[]) = False
isLengthEven (x:(y:[])) = True
isLengthEven (x:(y:(z:[]))) = False
isLengthEven (x:(y:(z:(q:[])))) = True
isLengthEven (x:xs) = isLengthEven (xs)

这会返回正确的值,直到我将第五个元素插入到列表中。对于大于或等于 5 的任意数量的元素,它会返回 True。我想递归部分有问题。

【问题讨论】:

  • 您的递归案例是错误的。而且您几乎不需要那么多基本案例。
  • 另外,您的基本情况之一是错误的。
  • 你做了太多的基本情况,[] 应该映射到 True,而不是 False,因为空列表的长度为 0,这是偶数。
  • @AWheelbarrow:(x:(y:[]))(x:(y:(z:[])))(x:(y:(z:(q:[])))) 不是必需的。
  • 模式(x:(y:(z:(q:[])))) 与模式[x,y,z,q] 相同,就像值一样。

标签: function haskell boolean


【解决方案1】:

单步解决方案

请注意,优秀的answer by Willem 已经是优化的了。

如果您更喜欢直接未优化的版本并选择忽略提示,您可以只使用初始代码的第一个和最后一个子句。像这样:

-- warning: the following is incorrect, needs changes:
isLengthEven :: [a] -> Bool
isLengthEven [] = False
isLengthEven (x:xs) = isLengthEven (xs)

这两个子句都不正确,但很容易修复它们:

isLengthEven :: [a] -> Bool
isLengthEven [] = True
isLengthEven (x:xs) = not (isLengthEven xs)

综合起来,这两个子句涵盖了所有可能的情况。

附录:

按原样,此函数使用大量堆栈空间来处理长输入。这可以通过常见的 accumulator-as-argument 技巧来解决。这里:

isLengthEven :: [a] -> Bool
isLengthEven xs = go True xs  where
    go b   []    =  b
    go b (x:xs)  =  go (not b) xs

go 辅助步进函数是尾递归的。

在我的机器上,这个单步解决方案(包括最后的更改)的性能非常接近问题文本中提倡的“两两”解决方案。

这两种方法的性能大约是直接但被禁止的(even (length xs)) 解决方案的三分之二。

【讨论】:

  • 如果上面有isLengthEven (_:_:_:_:xs) = isLengthEven xs,甚至isLengthEven (_:_:_:_:_:_:_:_:xs) = isLengthEven xs,会不会更“优化”?
  • @WillNess,我不这么认为。我会尝试isLengthEven xs = foldl' (\acc _ -> acc `xor` 1) 0 xs == (0 :: Int)。这将很好地融合,并且应该避免不必要的条件分支。
  • @dfeuer 很有趣,谢谢。你的版本实际上会做我的_:s 的不确定数量,随着它在累加器中翻转一点。这个答案中的最后一个版本“应该”可能会这样做。 :)
  • @WillNess - 我的 PC 上的 FWIW,当我更改步骤时,无论是 1、2 还是 4 个列表项,我都没有得到任何显着的性能变化。 GHC v8.8.4 -O2。因此,模式匹配似乎主导了函数调用开销。显而易见的解决方案even . length 比这些手动递归版本快约 45%。图书馆获胜。
  • @jpmarinier,你的测试怎么样?如果可以使用lengthfoldl' 进行融合,则手动版本会很糟糕。
【解决方案2】:

这里只需要两个基本情况:

  1. 一个列表,长度为0,因此应该返回True;和
  2. 一个单例列表,它包含一个元素,因此长度奇数。

递归情况每次在列表中向前移动两步,所以:

isLengthEven :: [a] -> Bool
isLengthEven [] = True
isLengthEven [x] = False
isLengthEven (_:_:xs) = isLengthEven xs

通常定义两个函数:isLengthEvenisLengthOdd,因此这些函数每次都以递归方式相互调用:

isLengthEven :: [a] -> Bool
isLengthEven [] = True
isLengthEven (_:xs) = isLengthOdd xs

isLengthOdd :: [a] -> Bool
isLengthOdd [] = False
isLengthOdd (_:xs) = isLengthEven xs

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2011-05-24
    • 2015-06-13
    • 2010-09-20
    • 2018-11-17
    相关资源
    最近更新 更多