我将尝试帮助您了解如何通过递归开发适用于列表的函数。首先学习如何以“低级”方式进行操作会很有帮助,这样您就可以更好地了解在实际代码中更常见的“高级”方式中发生的事情。
首先,您必须考虑要使用的数据类型的性质。列表在某种意义上是 Haskell 中递归定义类型的典型示例:列表要么是空列表 [],要么是某个列表元素 a 与通过 a : list 的列表组合。这是仅有的两种可能性。我们称空列表为基本情况,因为它在定义中不引用自身。如果没有基本情况,递归将永远不会“触底”并且会无限期地继续下去!
列表的定义中有两种情况意味着您必须在使用列表的函数的定义中考虑两种情况。在 Haskell 中考虑多种情况的规范方法是模式匹配。 Haskell 语法提供了许多进行模式匹配的方法,但我现在只使用基本的case 表达式:
case xs of
[] -> ...
x:xs' -> ...
这是列表必须考虑的两种情况。第一个匹配文字空列表构造函数;第二个匹配添加元素的构造函数:,并将两个变量x 和xs' 绑定到列表中的第一个元素和包含其余元素的子列表。
如果您的函数传递了一个与第一种情况匹配的列表,那么您就知道初始列表是空的,或者您已经完成了对列表的递归,直到它的最后一个元素。无论哪种方式,都没有要处理的列表;您要么完成(如果您的调用是尾递归的),要么您需要将答案构造的基本元素传递回调用它的函数(通过返回它)。如果您的答案是一个列表,那么基本元素通常又是一个空列表[]。
如果您的函数传递了一个与第二种情况匹配的列表,那么您就知道它传递了一个非空列表,而且您还有一些绑定到有用值的新变量。根据这些变量,您需要决定两件事:
- 假设我在列表的 rest 上执行算法得到了正确答案,我该如何在那个元素上执行我的算法的一步?
- 如何将这一步的结果与对列表其余部分执行的结果结合起来?
一旦你想出了这些问题的答案,你就需要构建一个组合它们的表达式;获得列表其余部分的答案只需对列表的其余部分调用递归调用,然后您需要对第一个元素执行该步骤并进行组合。
这是一个查找列表长度的简单示例
listLength :: [a] -> Int
listLength as =
case as of
[] -> 0 -- The empty list has a length of 0
a:as' -> 1 + listlength as' -- If not empty, the length is one more than the
-- length of the rest of the list
这是另一个从列表中删除匹配元素的示例
listFilter :: Int -> [Int] -> Int
listFilter x ns =
case ns of
[] -> [] -- base element to build the answer on
n:ns' -> if n == x
then listFilter x ns' -- don't include n in the result list
else n : (listFilter x ns') -- include n in the result list
现在,您提出的问题有点困难,因为它涉及辅助“列表匹配”递归,以识别列表上基本递归中的分隔符。有时向递归函数添加额外参数会很有帮助,以便保存有关您在问题中所处位置的额外信息。也可以通过将它们放在一个元组中同时对两个参数进行模式匹配:
case (xs, ys) of
([] , [] ) -> ...
(x:xs', [] ) -> ...
([] , y:ys') -> ...
(x:xs', y:ys') -> ...
希望这些提示能帮助您解决问题!