理解Haskell中的递归

理解Haskell中的递归,haskell,Haskell,我很难理解如何以递归的方式思考问题,并使用Haskell解决问题。我花了好几个小时的时间阅读,试图对递归进行思考。我最常从理解它的人那里得到的解释从来都不清楚,类似于“你传递一个函数,函数的名称作为参数,然后函数将执行,解决问题的一小部分,并一次又一次地调用函数,直到达到基本情况” 有没有人能给我介绍一下这三个简单递归函数的思想过程?与其说是它们的功能,不如说是代码如何递归地执行和解决问题 非常感谢 职能1 maximum' [] = error "maximum of empty list"

我很难理解如何以递归的方式思考问题,并使用Haskell解决问题。我花了好几个小时的时间阅读,试图对递归进行思考。我最常从理解它的人那里得到的解释从来都不清楚,类似于“你传递一个函数,函数的名称作为参数,然后函数将执行,解决问题的一小部分,并一次又一次地调用函数,直到达到基本情况”

有没有人能给我介绍一下这三个简单递归函数的思想过程?与其说是它们的功能,不如说是代码如何递归地执行和解决问题

非常感谢

职能1

maximum' [] = error "maximum of empty list"
maximum' [x] = x
maximum' (x:rest) = max x(maximum' rest)
职能2

take' n _  
    | n <= 0   = []  
take' _ []     = []  
take' n (x:xs) = x : take' (n-1) xs  
查看功能3:

reverse' [] = []  
reverse' (x:xs) = reverse' xs ++ [x] 
假设你称之为反向“[1,2,3],那么

1. reverse' [1,2,3] = reverse' [2,3] ++ [1]

   reverse' [2,3] = reverse' [3] ++ [2] ... so replacing in equation 1, we get:

2. reverse' [1,2,3] = reverse' [3] ++ [2] ++ [1]

   reverse' [3] = [3] and there is no xs ...

  ** UPDATE ** There *is* an xs!  The xs of [3] is [], the empty list. 

   We can confirm that in GHCi like this:

     Prelude> let (x:xs) = [3]
     Prelude> xs
     []

   So, actually, reverse' [3] = reverse' [] ++ [3]

   Replacing in equation 2, we get:

3. reverse' [1,2,3] = reverse' [] ++ [3] ++ [2] ++ [1]

   Which brings us to the base case: reverse' [] = []
   Replacing in equation 3, we get:

4. reverse' [1,2,3] = [] ++ [3] ++ [2] ++ [1], which collapses to:

5. reverse' [1,2,3] = [3,2,1], which, hopefully, is what you intended!
也许你可以试着对另外两个做一些类似的事情。选择小参数。
成功

指导方针 当试图理解递归时,您可能会发现更容易考虑算法对于给定输入的行为。执行路径看起来是什么样子很容易让人挂断电话,因此,要问自己这样的问题:

  • 如果我传递一个空列表会发生什么
  • 如果我传递一个包含一项的列表,会发生什么
  • 如果我传递了一个包含许多项的列表,会发生什么
或者,对于数字的递归:

  • 如果我传递一个负数会发生什么
  • 如果我超过0会发生什么
  • 如果我传递的数字大于0,会发生什么
递归算法的结构通常只是覆盖上述情况的问题。让我们来看看你的算法是如何运作的,以感受这种方法:

最大值' 如你所见,唯一有趣的行为是#3。其他人只是确保算法终止。从定义上看,

maximum' (x:rest) = max x (maximum' rest)
[1,2]
调用此函数可扩展为:

maximum [1, 2]    ~ max 1 (maximum' [2])
                  ~ max 1 2
max'
通过返回一个数字来工作,本例知道如何使用
max
递归处理该数字。让我们再看一个案例:

maximum [0, 1, 2] ~ max 0 (maximum' [1, 2]) 
                  ~ max 0 (max 1 2)
                  ~ max 0 2
您可以看到,对于这个输入,第一行中对
max'
的递归调用与前面的示例完全相同

反向' 相反的方法是将给定列表的开头粘贴到末尾。对于空列表,这不涉及任何工作,因此这是基本情况。因此,根据定义:

reverse' (x:xs) = reverse' xs ++ [x] 
让我们做一些替换。考虑到
[x]
相当于
x:[]
,您可以看到实际上有两个值需要处理:

reverse' [1]    ~ reverse' [] ++ 1
                ~ []          ++ 1
                ~ [1]
很简单。对于双元素列表:

reverse' [0, 1] ~ reverse' [1] ++ 0
                ~ [] ++ [1] ++ 0
                ~ [1, 0]
拿走 此函数在整数参数和列表上引入递归,因此有两种基本情况

  • 如果我们接受0或更少的项目会发生什么?我们不需要携带任何物品,所以只需返回空列表即可

    take' n _   | n <= 0    = [] 
    
    take' -1 [1]  = []
    take' 0  [1]  = []
    
  • 该算法的实质是遍历列表,分离输入列表并减少要获取的项的数量,直到上述任何一种基本情况停止该过程

    take' n (x:xs) = x : take' (n-1) xs
    
    因此,在首先满足数字基本情况的情况下,我们在到达列表末尾之前停止

    take' 1 [9, 8]  ~ 9 : take (1-1) [8]
                    ~ 9 : take 0     [8]
                    ~ 9 : []
                    ~ [9]
    
    在首先满足列表基本情况的情况下,我们在计数器达到0之前用完了项,只返回我们能返回的

    take' 3 [9, 8]  ~ 9 : take (3-1) [8]
                    ~ 9 : take 2     [8]
                    ~ 9 : 8 : take 1 []
                    ~ 9 : 8 : []
                    ~ [9, 8]
    

    递归是将特定函数应用于集合的一种策略。将函数应用于该集合的第一个元素,然后对其余元素重复该过程

    让我们举一个例子,您希望将列表中的所有整数加倍。首先,您考虑我应该使用哪个函数?答案->
    2*
    ,现在您必须递归地应用此函数。让我们称之为
    apply\u rec
    ,因此您有:

    apply_rec (x:xs) = (2*x)
    
    但这只会更改第一个元素,您希望更改集合中的所有元素。因此,您还必须将
    apply_rec
    应用于其余元素。因此:

    apply_rec (x:xs) = (2*x) : (apply_rec xs)
    
    现在你有了一个不同的问题。
    apply\u rec
    何时结束?当您到达列表的末尾时,它将结束。换句话说,
    []
    ,所以您也需要介绍这个案例

    apply_rec [] = []
    apply_rec (x:xs) = (2*x) : (apply_rec xs)
    
    当您到达终点时,您不想应用任何函数,因此函数
    apply\u rec
    应该“return”
    []

    让我们看看这个函数在集合中的行为=
    [1,2,3]

  • apply_rec[1,2,3]=(2*1):(apply_rec[2,3])
  • apply_rec[2,3]=2:((2*2):(apply_rec[3])
  • apply_rec[3]=2:(4:((2*3):(apply_rec[])
  • apply_rec[]=2:(4:(6:[]))
  • 导致
    [2,4,6]


    由于您可能不太了解递归,最好从比您所介绍的更简单的示例开始。再看看这个。

    也许你提出问题的方式不是很好,我的意思是,这不是通过研究现有递归函数的实现,你将了解如何复制它。我更愿意为您提供另一种方法,它可以被视为一个系统化的过程,帮助您编写递归调用的标准框架,然后促进关于它们的推理

    你们所有的例子都是关于列表的,当你们使用列表的时候,首先要做的是详尽无遗,我的意思是使用模式匹配

    rec_fun [] = -- something here, surely the base case
    rec_fun (x:xs) = -- another thing here, surely the general case  
    
    现在,基本情况不能包含recursive,否则您肯定会得到一个无限循环,那么基本情况应该返回一个值,掌握这个值的最好方法是查看函数的类型注释

    例如:

    reverse :: [a] -> [a]
    

    可以鼓励您将基础情况视为类型[a]的值,如[/]针对反向

    maximum :: [a] -> a 
    

    可以鼓励您将基本情况视为最大值

    类型A的值。 现在是递归部分,正如前面所说,函数应该
    apply_rec [] = []
    apply_rec (x:xs) = (2*x) : (apply_rec xs)
    
    rec_fun [] = -- something here, surely the base case
    rec_fun (x:xs) = -- another thing here, surely the general case  
    
    reverse :: [a] -> [a]
    
    maximum :: [a] -> a 
    
    rec_fun (x:xs) = fun x rec_fun xs 
    
    rec_fun (x:xs) = x `fun` rec_fun xs 
    
    g :: Integer -> Bool
    g x | x<=0 = False
    g 1 = True
    g 2 = True
    
    g x = x == y+z  where
              y = head [y | y<-[x-1,x-2..], g y]    -- biggest y<x that g y
              z = head [z | z<-[y-1,y-2..], g z]    -- biggest z<y that g z
    
    filter g [0..]
    
    
    reverse' l
      | lenL == 1 || lenL == 0 = l
      where lenL = length l
    reverse' xs ++ [x]
    
    
    reverse' :: [a] -> [a]
    reverse' [] = []
    reverse' (x:xs) = reverse' xs ++ [x]