编程风格的练习,递归

本周的帖子将回到基础知识,因为限制是使用递归

计算机科学中的递归是一种解决问题的方法,其中解决方案取决于对同一问题的较小实例(而不是迭代)的解决方案。 该方法可以应用于许多类型的问题,并且递归是计算机科学的中心思想之一。

—维基百科
https://zh.wikipedia.org/wiki/递归((computer_science)

这是4在编程风格焦点series.Other职位练习交包括:

  1. 以编程风格介绍练习
  2. 以编程风格进行练习,将内容堆叠起来
  3. 编程风格的练习,Kwisatz Haderach风格
  4. 编程风格的练习,递归 (本文)
  5. 具有高阶功能的编程风格的练习
  6. 以编程风格进行练习
  7. 以编程风格进行练习,回到面向对象的编程
  8. 编程风格的练习:地图也是对象
  9. 编程风格的练习:事件驱动的编程
  10. 编程风格的练习和事件总线
  11. 反思编程风格的练习
  12. 面向方面的编程风格的练习
  13. 编程风格的练习:FP&I / O
  14. 关系数据库风格的练习
  15. 编程风格的练习:电子表格
  16. 并发编程风格的练习
  17. 编程风格的练习:在线程之间共享数据
  18. 使用Hazelcast以编程风格进行练习
  19. MapReduce样式的练习
  20. 编程风格的练习总结

递归原理

我已经在将函数式编程的方法应用于Dijkstra算法的上下文中写过关于递归的文章。 让我们详细介绍其实现。 递归函数应提供两个分支:

  1. 停止分支返回最终结果
  2. 一个使用不同参数调用函数本身的分支

这是阶乘函数的简单实现:

funfact(n:Int):Int{
    varresult=1
    for(iin1..n){
        result*=i
    }
    returnresult
}

fact(5)

在传统的命令式编程中,函数使用局部变量来累积临时计算。 在递归函数中,这些变量被函数参数替换。

与上一个函数的递归等效项如下:

privatefunrecurseFact(acc:Int,n:Int):Int=
    if(n==1)acc (1)
    elserecurseFact(acc*n,n-1) (2)

funfact(n:Int)=recurseFact(1,n) (3)

fact(5)
  1. 停止分支
  2. 自呼分支
  3. 与以前的功能签名相同

请注意, acc参数与命令示例中的result局部变量具有相同的作用。

以下是练习中的功能。 它采用与阶乘示例中所述完全相同的原理:

funwords(rest:List<String>,
                  stopwords:List<String>,
                  words:List<String>):List<String>{
    returnif(rest.isEmpty())words
    else{
        valsplit=split(rest.last(),stopwords,listOf())
        words(rest.dropLast(1),stopwords,words+split)
    }
}

递归问题

尽管递归代码比命令代码要简洁得多,但是它遇到了一个巨大的问题:函数调用被推入线程的调用堆栈中。 超出调用堆栈的大小时,将引发臭名昭著的StackOverflowError

尽管可以在启动时设置堆栈大小,但是它的大小是有限的。

用于管理堆栈大小的命令行选项-Xss标准JVM热点选项-XX:ThreadStackSize专有选项如有更改,恕不另行通知

为了解决这个问题,Kotlin(和Scala)提供了一个编译器技巧:尽管源代码是递归的,但是编译后的字节码是通过标准循环实现的。 有两个要求:

  1. 递归函数调用必须是最后一个。 这称为拖尾递归
  2. 修饰符tailrec必须添加到功能签名中

上面的words()函数是尾递归的,因此添加tailrec关键字非常容易。 可以相应地对其进行更新,以使其永远不会溢出。

相反,以下函数不是尾递归的,因为有两个使用递归的调用。 因此, 字节码不能被优化。

fun<T>quicksort(list:List<Pair<T,Int>>):List<Pair<T,Int>>=
    if(list.size<=1)list
    elselist.random().let{pivot->
        valbelow=filter(list,listOf()){it.second<=pivot.second}
        valabove=filter(list,listOf()){it.second>pivot.second}
        quicksort(below-pivot)+pivot+quicksort(above)
    }

结论

通常,递归是一开始很难破解的。 但是,迁移代码以使用递归很简单:只需将局部变量移动到累加器参数即可。 最难的部分是使递归函数尾递归以避免堆栈溢出。 有些功能允许,有些则不允许。

这篇文章的完整源代码可以在Github上找到。

翻译自: https://blog.frankel.ch/exercises-programming-style/4/

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值