【问题标题】:Haskell longest contiguous series of elements in a listHaskell 列表中最长的连续元素系列
【发布时间】:2016-12-30 12:50:57
【问题描述】:

我是函数式编程和语言 haskell 的新手。我正在尝试根据谓词函数确定列表中最长连续元素系列的长度。函数如下所示:

 longestSequence :: (a -> Bool) -> [Int] -> Int

当我这样称呼它时:

 longestSequence (\x -> x >= 10) [1,44,33,22,2,3,55,66,66,77,88,99]

它应该给我作为解决方案6

到目前为止,我的解决方案是:

longestSequence :: (a -> Bool) -> [a] -> Int
longestSequence p [] = 0
longestSequence p (x:xs) 
  | (p x) = 1 + (longestSequence p xs)
  | otherwise = longestSequence p xs

关于我如何解决这个问题的任何提示或想法?

【问题讨论】:

  • 为什么它应该给你 6,它给你的是什么?
  • 我想我明白了:它是所有满足谓词的最长连续元素系列的长度。
  • 使用我的解决方案我得到 9,因为如果谓词为真,我将计算所有项目
  • 对不起,这个问题的误导性话题。 @luqui 规范是正确的。
  • 因为你只想要最长序列的长度,而不是最长序列本身,它可能有助于map 整个列表中的谓词,然后找到True 的最长序列。跨度>

标签: haskell recursion functional-programming


【解决方案1】:

尝试排除较小的子问题。对于这个例子,一般策略可能是这样的:

  1. 如果对应条目满足谓词,则将给定列表转换为[Bool] 列表,其中条目为True。那就是写一个函数(Int -> Bool) -> [Int] -> [Bool]
  2. 对连续的TrueFalse 值进行分组。那就是写一个函数[Bool] -> [[Bool]]。你可能想看看Data.List.group
  3. 过滤掉False 组:[[Bool]] -> [[Bool]]
  4. 为每个内部列表计算它们的长度。 [[Bool]] -> [Int]
  5. 找出这些长度的最大值:[Int] -> Int

然后你只需要组合这些函数就可以了。对于 1. 和 4. 您将需要使用函数 map :: (a -> b) -> [a] -> [b]。如果您已经知道map,请使用它。如果你不这样做,我建议你自己也写。

我给出的函数的一些类型签名过于具体。可以尝试将它们概括一下。

【讨论】:

  • 需要过滤掉False 组,以防其中一个很长
  • 这是解决我问题的很好的解释 :) 非常感谢@jpath!
  • 给你的最后一个问题@jpath:采用你的解释线性时间的解决方案 -> O(n)?
  • @Oni1 是的,如果你正确地实现它。从概念上讲,如果您检查所有步骤 1 到 5,您可以通过对列表的单次遍历来实现它们,因此它们都需要 O(n),除非您在实现中做了一些愚蠢的事情。
  • 你能告诉我如何使用 Haskell 解决它吗?
【解决方案2】:

在守卫中,p xotherwise部分都有问题。

(p x) = 1 + (longestSequence p xs)不一定是真的,如果x的下一个元素不持有predictp,就不能加1

如果当前最长序列比剩余序列长,otherwise = longestSequence p xs 不为真

一个可能的解决方法是保持最大长度和当前长度

longestSequence :: (a -> Bool) -> [a] -> Int
longestSequence p x = ls x 0 0
  where
    ls [] mx cur = max mx cur
    ls (x:xs) mx cur
        | p x = ls xs mx (cur+1)
        | otherwise = ls xs (max mx cur) 0

【讨论】:

  • @Oni1 真的很抱歉,复制粘贴错误,在其他部分修复
【解决方案3】:

您可以经常使用fold,它比递归更简单,在这种情况下,我们要使用scanl,它就像foldl,只是它保留了整个列表。

以下遍历列表,增加与谓词匹配的每个项目的计数。如果它找到一个不匹配的,它会将计数重置为零。最后,它会找到结果列表的最大值。

longestSequence p = maximum . scanl (\count x -> if (p x) then count + 1 else 0) 0

对于[1,44,33,22,2,3,55,66,66,77,88,99] 的示例输入和(\x -> x >= 10) 的谓词,scanl 将产生[0,0,1,2,3,0,0,1,2,3,4,5,6] 的输出。取这个列表的最大值会得到6,你可以看到最大值出现在输入的末尾,还有列表中谓词不匹配的零。

顺便说一句,我完全同意 jpath 关于将问题分解为小部分的观点。我已经看到人们通过在 \count 参数中用元组填充 max 计算来解决类似的问题 fold,但这使得它更难阅读。顺便说一句,这也是使像您这样的单功能递归解决方案难以理解的原因。一次要跟踪的内容太多,因此很难看出在哪里以及如何适应归零部分。

【讨论】:

    猜你喜欢
    • 2021-11-18
    • 2020-12-16
    • 1970-01-01
    • 1970-01-01
    • 2020-04-17
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多