如何检查列表中的下一个元素是否大于 Haskell 中的前一个元素?

How to check if next element in a list is greater than the previous in Haskell?

(我目前正在 Haskell 上在线课程,这是一个练习。我不是在寻找答案,而只是寻求一些关于如何进行的指导!)

我很难理解这个问题。在命令式语言中,我会简单地使用一个循环,但由于 Haskell 并没有真正让我摸不着头脑。

我需要编写一个函数 nextIsGreater :: [Int] -> [Int] ,给定一个数字列表,生成一个包含输入列表中所有元素的列表,使得该元素后跟一个更大的数字在输入列表中(下一个数字更大)。

这是我到目前为止想出的办法。

nextIsGreater :: [Int] -> [Int]

nextIsGreater xs = [x | x <- init xs, y <- tail xs, x < y]

到目前为止,如果我在列表中只有两个数字,它就可以工作。说 [0,5],它应该是 returns [0]。如果我有,比如 [0,5,6] 那么我的代码似乎会根据列表中的下一个数字和 returns [0,0,5] 检查 0,而它应该 [=24] =] [0,5]。我如何比较每个相邻的数字?

不错的尝试,但是

   [x | x <- init xs, y <- tail xs, x < y]

对应于一个 嵌套 循环:您从 init xs 中选择 x,然后为每个选择选择 all可能 y 来自 tail xs.

为了使这个想法按预期发挥作用,您需要使用 {-# LANGUAGE ParallelListComp #-} 或等效地 zip 来源:

nextIsGreater xs = [x | (x,y) <- zip (init xs) (tail xs), x<y]

但是有一个更简单的方法来获取两个连续元素的所有选择,tails:

nextIsGreater xs = [x | (x:y:_) <- tails xs, x<y]

这个练习的目的几乎可以肯定是让你使用模式匹配编写一个标准的递归解决方案,而不是使用列表理解或更高级别的函数或类似的东西。

如果课程不错,您应该已经涵盖了一些递归 list-to-list 转换,具有以下形式定义的函数:

foo :: [Int] -> [Int]
foo (x:xs) = ... something involving "x" and "foo xs" ...
foo [] = ...

或类似内容,您应该按照相同的思路写一些东西。

这是第一个提示,下面还有更多剧透。

编写对列表的相邻元素进行操作的递归函数的一种简单方法是编写一个命名前两个元素的模式:

foo (x:y:zs) = ...

“...”可以对xy进行操作,然后执行递归调用来处理列表的“其余部分”。递归调用可能是 foo zsfoo (y:zs)(或根据某些条件在它们之间切换),具体取决于函数在做什么。

因为此模式只会匹配至少包含两个元素的列表,所以您通常还需要模式来匹配 one-element 和空列表:

foo [x] = ...
foo [] = ...

如果这还不够清楚,让我从一个 检查相邻元素的示例开始,让我重温您对基本递归 list-to-list 转换的记忆。

剧透

.

.

.

假设我们要从列表中过滤掉所有偶数元素。递归解决方案将考虑两种情况:

evens (x:xs) = ...
evens [] = ...

对于第一种情况,从 x:xs 中提取所有事件包括 x 加上来自 xs 的所有事件(即, evens xs) 排除x只包含evens xs,取决于x本身是否偶:

evens (x:xs) | even x = ...
             | otherwise = ...

特别是,如果 x 是偶数,则答案应包括 xevens xs:

evens (x:xs) | even x = x : evens xs

如果 x 是奇数,答案应该包括 evens xs:

             | otherwise = evens xs

最后一种情况是空表中偶数的子集,也就是空表:

evens [] = []

给出完整的定义:

evens :: [Int] -> [Int]
evens (x:xs) | even x = x : evens xs
             | otherwise = evens xs
evens [] = []

您示例中的主要区别在于包含 x 的决定不仅取决于 x,还取决于出现在 x 之后的元素,因此让我们考虑一个稍微不同的问题: 取一个列表,输出所有后面为偶数的元素。

我们可能会考虑从类似的结构开始:

beforeEvens (x:xs) | ... = x : beforeEvens xs     -- include x
                   | otherwise = beforeEvens xs   -- exclude x
beforeEvens [] = []

其中“...”检查 x 之后的元素(即 xs 的第一个元素)是否为偶数。例如,我们可能会调用一个单独的函数来检查:

beforeEvens (x:xs) | headIsEven xs = x : beforeEvens xs
                   | otherwise     = beforeEvens xs
beforeEvens [] = []

你应该能够写出 headIsEven 的一个像样的定义来完成这个。如果不使用 head,而是使用模式匹配,则加分。注意特殊情况 headIsEven [] 应该 return False.

不过,更直接的方法是利用模式可用于检查列表开头的多个元素这一事实。在这里,我们匹配一个模式,命名前两个元素 xy,加上列表的其余部分 zs:

beforeEvens (x:y:zs) | even y = x : beforeEvens (y:zs)
                     | otherwise = beforeEvens (y:zs)
beforeEvens [x] = []
beforeEvens [] = []

注意这里的几个棘手点。如果我们匹配模式 (x:y:zs),那么我们必须小心我们是否单独递归 y:zszs。这取决于 y 是否应该被考虑包含在输出中。此外,模式 (x:y:zs) 不会匹配单例列表,因此我们需要一个额外的模式匹配。

由于最后两个案例相同,我们可以将它们合并为一个案例:

beforeEvens (x:y:zs) | even y = x : beforeEvens (y:zs)
                     | otherwise = beforeEvens (y:zs)
beforeEvens _ = []

您应该会发现修改 beforeEvens 以编写您的 nextIsGreater 函数相对简单。