前文阅读:迭代和递归:一道面试题引发的思考
本篇主要举两个案例,来强化使用递归和迭代去解决复杂问题的思考过程。掌握了精髓,一般的问题都能迎刃而解。
不说废话,直接上正文。
案例1
已知信息如下,loadUrls用于控制多个异步请求,通过max限制同一时刻的并发请求数,其返回Promise,结果为所有urls的异步结果。有点类似于Promise.all
,不过内部对最大并发数做了控制。
const urls = ['url1', 'url2', 'url3', 'url4','url5'];
function load(url){
return new Promise((resolve, reject) => {
setTimeout(() => resolve(`${url}result`), Math.random(0,6)*1000);
})
}
function loadUrls(urls, max) { }
分析:每次最多同时有max个异步请求,有一个请求返回,补上下一个请求,以此类推。
关键信息
1)首先需要发max个请求;
2)只有一个请求结束,才会补上一个请求发出;
3)当urls中的url全部处理后,结束并返回所有异步结果。
很显然,这是个循环处理的问题。前面我们讨论过递归与迭代,不再赘述。
迭代和递归的选择
由于while循环是同步控制,顺序执行的;而上例是异步的,执行循环的时机由.then
决定,因此这里我们选择递归实现。
明确循环体和出口条件
按上期制定的原则,接下来最重要的是明确出口条件以及循环体:
1)出口条件:urls的中url是否已全部处理完成;
2)循环体:一个请求返回后,load().then()中触发下一个url进行load
需要注意下,上例多了个条件:限制max个并发请求
,这个放到循环函数中好像没法处理啊,怎么办呢?
加一条原则:和循环过程无关的条件提到函数外
基于上面的分析,对loadUrls进行改造:
1) 由于loadUrls需要返回Promise对象,因此第一步可先构造返回:
function loadUrls(urls, max) {
return Promise((resolve, reject) => {
//...
});
}
2) 再者由于loadUrls返回的Promise对象得到的结果是所有异步请求的结果,因此,需要其resolve:
function loadUrls(urls, max) {
return Promise((resolve, reject) => {
//启动max个请求,传入resolve,供执行结束返回结果
for(let i = 0; i < max; i++ ) {
loadNext(urls[i], i, resolve);
}
});
}
3) 递归函数loadNext:
function loadUrls(urls, max) {
//记录所有异步请求的结果
let result = [];
//记录已经发出请求的序号
let loadIndex = max - 1;
//已完成加载的数量
let loadedNum = 0;
//递归函数,初始条件已提出
function loadNext(index, resolve) {
load(urls[index]).then((val) => {
result[index] = val;
loadedNum++;
//判断是否所有的url已发出请求
if(loadedNum < urls.length && loadIndex < urls.length-1) {
//下一个待发起url
loadIndex++;
loadNext(loadIndex, resolve);
}else if(loadedNum === urls.length){
resolve(result);
}
})
}
return new Promise((resolve, reject) => {
for(let i = 0; i < max; i++ ) {
loadNext(i, resolve);
}
})
}
执行结果:
loadUrls(urls, 2).then(i => console.log(i))
// ["url1result", "url2result", "url3result", "url4result", "url5result"]
案例2
二维数组:[[1,2,3], [9,10,11,4], [8,7,6,5]]
1 2 3
9 10 11 4
8 7 6 5
回形输出:1 2 3 4 5 6 7 8 9 10 11
复杂问题一般都是由简单问题组成,这里我们尝试按照上面的分析过程找关键信息:一定是由简单问题组成
1)循环体:一圈输出是一个循环
2)出口条件:遍历层级 == Math.ceil(二维数组的长度)
完美,找到了简化的处理逻辑:
1) 输出一圈数值
2) 改变层级
dealStr = (arr) => {
let line = 0;
while(line <= Math.ceil(arr.length/2)) {
let startIndex = 0;
//输出一圈的上层
startIndex = line;
for(let i = startIndex; i < arr[line].length - line; i++) {
console.log(arr[line][i]);
}
//输出一圈的右层
startIndex = line + 1;
for(let i = startIndex; i < arr.length - 1 - line; i++) {
console.log(arr[i][arr[i].length - 1 - line]);
}
//输出一圈的底层
bottomLine = arr.length - 1 - line;
if( bottomLine > line){
startIndex = arr[bottomLine].length - 1 - line;
for(let i = startIndex; i >= line; i--) {
console.log(arr[bottomLine][i]);
}
}
//输出一圈的左层
for(let i = bottomLine - 1; i > line; i--) {
console.log(arr[i][line]);
}
line++;
}
}
总结
一般遇到复杂问题,按如下思路想一下:
1.适不适合用递归或迭代解决(出口条件 | 循环体)
2.两者二选1。案例1显然适合使用递归,案例2适合使用迭代;具体问题具体分析。
3.决定了大方向,如何细化分析:
递归函数如果遇到和循环逻辑无关的,不要慌,试着提出函数外考虑,那么关于递归总结的三大原则:1)出口条件
2)循环逻辑
3)初始逻辑(考虑提出函数外执行)
迭代的不同在于:while可以简单理解为一个独立的'循环函数',因此其初始条件(如果有的话),并不需要提出函数外,只要写在while外即可。
() => {
//1.初始信息
while() { //2.终止条件
//3.循环体
}
}
好了,递归和迭代的内容到此为止。
获取更多干货分享,请【扫码关注】~
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。