递归

问题描述

在开发过程中,遇到一个需求:在系统初始化时通过http获取一个第三方服务器端的列表,第三方服务器提供了一个接口,可通过分页形式获取列表。

这里有两个问题:

  • 未知的列表数量。就算已知总数量,如果数据量巨大,也不应该一次获取全部信息。
  • 在node.js中,http是异步的。

假设该接口为:

GET http://www.example.com/api/list
QUERY page=PAGE_INDEX&size=PAGE_SIZE
RETURN {data: [DATA,...], page: {page: PAGE_INDEX, size: PAGE_SIZE}}

期望结果:
所有的数据

[DATA, ...]

目的

借以该问题,了解关于递归的更多知识。

解决方案

  • callback + 辅助变量

      let request = require('superagent')
      let api = 'http://www.example.com/api/list'
      function getList (page, size, data = [], cb) {
        request(api).query({page, size})
          .then(({body}) => {
            if (body.data && body.data.length) {
              // getList(page++, size, data.concat(body.data), cb)
              getList(++page, size, data.concat(body.data), cb)
            } else {
              cb(data)
            }
          })
      }
      getList(1, 15, [], (data) => {
          console.log(data) 
      })
    

    还是能够理解,不断请求,并把获得的数据传到下一个请求;直到返回数据为空时终止,并返回累加的数据。就算不用promise而是普通回答(如使用end方法)也是可以的,可以不依赖函数返回值。但是需要一个没有意义的变量。

  • promise

      let request = require('superagent')
      let api = 'http://www.example.com/api/list'
      function getList (page, size) {
        return request(api).query({page, size})
          .then(({body}) => {
            if (body.data && body.data.length) {
              return getList(++page, size)
                .then((data) => {
                   return body.data.concat(data)
                })
            } else {
               return []
            }
          })
      }
      getList(1, 15).then((data) => {
        console.log(data)
      })
    

    相比较上面的方法,不在需要辅助变量和回调函数,看起来更像普通的递归。但是由于promise中不断return,使得代码难以理解,且容易忽略掉数据为空时else判断。

  • generater

      let request = require('superagent')
      let co = require('co')
      let api = 'http://www.example.com/api/list'
      function* getList (page, size) {
        let {body} = yield request(api).query({page, size})
        if (body.data && body.data.length) {
          return body.data.concat(yield getList(++page, size))
        } else {
          return []
        }
      }
      co(function *() {
        let data = yield getList(1, 100)
        console.log(data.length);
      })
    

    同步方式的代码,更加方便阅读和理解。但是复杂的generater函数需要使用相应的环境才能运行,如co,最不喜欢的就是在数组的一些遍历方法中不能使用。

  • async + await 和generater差不多,也需要相应的环境,但是可以在数组的遍历方法中使用。

思考

对于一下这句话,我总是抱着怀疑的,因为很多时候我会很快想到使用递归可以解决,而不会想到使用循环来解决。在JavaScript中,那些异步的递归函数怎么才能转换为循环呢?

递归和循环两者完全可以互换

在一次面试中,面试官给我描述了这么一个需求:

一个函数期望通过输入两个数,返回一个数组,且不能使用循环。第一个参数x表示结果数组中元素,第二个参数n代表数组长度。

我第一反应就是使用递归,或者使用数组的遍历方法。说真的,我真的没有那么一丢丢的想到循环,然而循环貌似最应该容易想到...。

// 循环
function repeat (x, n) {
  let arr = []
  while (arr.length < n) {
    arr.push(x)
  }
  return arr
}
// 递归
function repeat (x, n) {
  return n > 0 ? [x].concat(repeat(x, --n)) : []
}
// 数组
new Array(x).fill(n)

那么像上面那个请求的异步递归,如果没有ECMScript 2015(使用回调),该怎么写成循环呢?
经典广告词:想不出来

最后复制一段别人总结

如果你看到这里了,文中如有错误,还请指出。关于最后的问题,我也希望得到你的帮助。

留着有时间再来弄懂,首先得知道有什么,然后才是为什么?什么都不知道哪来那么多为什么呢。上次面试一堆相关问题,一脸懵逼,最后挂了,这是在不断提醒应该去弄清楚他啊。

递归算法:
优点:代码简洁、清晰,并且容易验证正确性。(如果你真的理解了算法的话,否则你更晕)
缺点:它的运行需要较多次数的函数调用,如果调用层数比较深,需要增加额外的堆栈处理(还有可能出现堆栈溢出的情况),比如参数传递需要压栈等操作,会对执行效率有一定影响。但是,对于某些问题,如果不使用递归,那将是极端难看的代码。

循环算法:
优点:速度快,结构简单。
缺点:并不能解决所有的问题。有的问题适合使用递归而不是循环。如果使用循环并不困难的话,最好使用循环。

递归算法和循环算法总结:

  1. 一般递归调用可以处理的算法,也通过循环去解决常需要额外的低效处理。
  2. 现在的编译器在优化后,对于多次调用的函数处理会有非常好的效率优化,效率未必低于循环。
  3. 递归和循环两者完全可以互换。如果用到递归的地方可以很方便使用循环替换,而不影响程序的阅读,那么替换成递归往往是好的。(例如:求阶乘的递归实现与循环实现。)

复制自递归的效率问题及递归与循环比较

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,524评论 5 460
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,869评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,813评论 0 320
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,210评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,085评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,117评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,533评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,219评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,487评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,582评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,362评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,218评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,589评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,899评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,176评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,503评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,707评论 2 335

推荐阅读更多精彩内容