所以我有一个函数的示例代码,它通过一个数组和它所有的子数组通过递归,并计算匹配找到的字符串的数量。
示例数组:
const array = [
'something',
'something',
[
'something',
'something'
],
'something',
[
'something',
[
'something',
'something'
],
'something',
[
[
'something',
'something',
[
'anything'
]
],
'something',
],
[
'something',
[
'something',
'something',
'something',
[
'anything',
'something',
[
'something',
'anything'
]
]
]
]
],
'anything'
];
我的功能可以通过这个数组来计算其中的#34;"的数量。
此代码效果很好:
let somethingsFound = 0;
const searchArrays = (array, stringMatch) => {
for(let i=0;i<array.length;i++){
const item = array[i];
if((typeof item) === 'string'){
(item === stringMatch) ? somethingsFound++ : undefined;
} else if ((typeof item) === 'object'){
searchArrays(item, stringMatch);
}
}
}
searchArrays(array, 'something');
console.log(`Found ${somethingsFound} somethings`);
控制台输出:
>Found 18 somethings
然而
这是我不理解的部分,需要一些解释。如果我删除let
变量forloop
上的i
声明,只是隐式声明它i=0;<array.length;i++
,那么我的函数会进入无限递归。我通过放置一个console.log("running search)
语句来查看它。
在这种情况下,让我们做什么?我已经尝试过阅读它,但不能完全理解发生了什么,以及递归和forloop究竟是如何相互作用的。
这里是代码的失败代码以防万一,所有不同的是让我们声明:
let somethingsFound = 0;
const searchArrays = (array, stringMatch) => {
for(i=0;i<array.length;i++){
const item = array[i];
if((typeof item) === 'string'){
(item === stringMatch) ? somethingsFound++ : undefined;
} else if ((typeof item) === 'object'){
searchArrays(item, stringMatch);
}
}
}
searchArrays(array, 'something');
console.log(`Found ${somethingsFound} somethings`);
谢谢! CodeAt30
答案 0 :(得分:4)
...只是隐含地声明它
i=0
;
这不是声明,只是创建implicit global *。由于它是全局,所以对searchArrays
的所有调用都会共享,因此您的外部调用过早结束(因为i
已经被内部调用增加了。)< / p>
示例:
function withDeclaration(recurse) {
for (let i = 0; i < 3; ++i) {
console.log((recurse ? "Outer " : "Inner ") + i);
if (recurse) {
withDeclaration(false);
}
}
}
function withoutDeclaration(recurse) {
for (i = 0; i < 3; ++i) {
console.log((recurse ? "Outer " : "Inner ") + i);
if (recurse) {
withoutDeclaration(false);
}
}
}
console.log("With declaration:");
withDeclaration(true);
console.log("Without declaration:");
withoutDeclaration(true);
.as-console-wrapper {
max-height: 100% !important;
}
故事的寓意:从不依赖隐式全局变量。在您需要它们的最里面的范围内声明您的变量。使用"use strict"
使隐式全局创建成为错误。
* (这是我贫血的小博客上的帖子)
答案 1 :(得分:2)
当您使用“隐式声明”变量时,您将只有一个这样的变量,与您在递归树中的位置无关。它将是一个全局变量。
这将有效地破坏算法的逻辑,因为最深的递归级别会将i
的值移到数组长度之外,然后当你在前一个递归级别回溯循环时会突然跳转到值i
,可能会跳过应该处理的几个有效数组条目。
始终声明变量。
答案 2 :(得分:2)
TJ和Trincot在修复你的计划方面做得很好 - 我会尝试修复你的想法......
递归是一种功能性传承
递归是一种来自功能风格的概念。将它与命令式风格混合是新程序员的痛苦和困惑的源泉。
To design a recursive function,我们确定基础和归纳案例
first
值为Empty
- 如果输入为空,则显然没有匹配项,因此返回0
< / LI>
first
不为空,但 是一个数组 - 重复first
plus 重复了rest
的值first
不为空且不数组,因此它是一个普通值 - 如果{ {1}}匹配first
,为匹配项返回1 加上在值match
上重复出现的结果rest
不为空,不数组,也不匹配first
- 重复match
值由于这种实施,所有的痛苦都会从计划中消除。我们不关心本地状态变量,变量重新赋值,数组迭代器,递增迭代器或其他副作用,如rest
为了简洁起见,我分别用for
和'something'
替换了数据中的'anything'
和'A'
。
'B'
具有功能风格的
或将const Empty =
Symbol ()
const searchArrays = (match, [ first = Empty, ...rest ]) =>
{
/* no value */
if (first === Empty)
return 0
/* value is NOT empty */
else if (Array.isArray (first))
return searchArrays (match, first) + searchArrays (match, rest)
/* value is NOT array */
else if (first === match)
return 1 + searchArrays (match, rest)
/* value is NOT match */
else
return searchArrays (match, rest)
}
const data =
['A','A',['A','A'],'A',['A',['A','A'],'A',[['A','A',['B']],'A',],['A',['A','A','A',['B','A',['A','B']]]]],'B']
console.log (searchArrays ('A', data)) // 18
console.log (searchArrays ('B', data)) // 4
console.log (searchArrays ('C', data)) // 0
编码为纯函数表达式 - 此程序相同但交换命令式searchArrays
/ if
/ else if
和else
三元表达式的语句语法
return
没有魔法
上面,我们使用rest parameter到destructure输入数组。如果这让您感到困惑,那么在简化示例中查看它会有所帮助。注意const Empty =
Symbol ()
const searchArrays = (match, [ first = Empty, ...rest ]) =>
first === Empty
? 0
: Array.isArray (first)
? searchArrays (match, first) + searchArrays (match, rest)
: first === match
? 1 + searchArrays (match, rest)
: searchArrays (match, rest)
const data =
['A','A',['A','A'],'A',['A',['A','A'],'A',[['A','A',['B']],'A',],['A',['A','A','A',['B','A',['A','B']]]]],'B']
console.log (searchArrays ('A', data)) // 18
console.log (searchArrays ('B', data)) // 4
console.log (searchArrays ('C', data)) // 0
用于使我们的函数可以识别何时停止。
Empty
这是较新版本的JavaScript中包含的高级功能,但如果它让我们感到不舒服,我们就不必使用它。下面,我们重写const Empty =
Symbol ()
const sum = ([ first = Empty, ...rest]) =>
first === Empty
? 0
: first + sum (rest)
console.log (sum ([ 1, 2, 3, 4 ])) // 10
console.log (sum ([])) // 0
而没有幻想的解构语法
sum
我们现在可以执行const isEmpty = (xs = []) =>
xs.length === 0
const first = (xs = []) =>
xs [0]
const rest = (xs = []) =>
xs.slice (1)
const sum = (values = []) =>
isEmpty (values)
? 0
: first (values) + sum (rest (values))
console.log (sum ([ 1, 2, 3, 4 ])) // 10
console.log (sum ([])) // 0
,isEmpty
和first
函数并重新实现rest
- 注意相似之处; 粗体
searchArrays
展开代码段,看它是否有相同的效果
const searchArrays = (match, values = []) =>
isEmpty (values)
? 0
: Array.isArray (first (values))
? searchArrays (match, first (values)) + searchArrays (match, rest (values))
: first (values) === match
? 1 + searchArrays (match, rest (values))
: searchArrays (match, rest (values))
具有强大的抽象功能
作为程序员,“遍历数据结构并对每个元素执行操作”是我们需要做的常见事情。识别这些模式并将它们抽象为通用的,可重用的函数是高级思维的核心,这使得编写更高级别的程序成为可能。
const isEmpty = (xs = []) =>
xs.length === 0
const first = (xs = []) =>
xs [0]
const rest = (xs = []) =>
xs.slice (1)
const searchArrays = (match, values = []) =>
isEmpty (values)
? 0
: Array.isArray (first (values))
? searchArrays (match, first (values)) + searchArrays (match, rest (values))
: first (values) === match
? 1 + searchArrays (match, rest (values))
: searchArrays (match, rest (values))
const data =
['A','A',['A','A'],'A',['A',['A','A'],'A',[['A','A',['B']],'A',],['A',['A','A','A',['B','A',['A','B']]]]],'B']
console.log (searchArrays ('A', data)) // 18
console.log (searchArrays ('B', data)) // 4
console.log (searchArrays ('C', data)) // 0
这项技能并非自动生效,但有一些技巧可以帮助您实现更高层次的思考。在this answer我的目标是给读者一些洞察力。如果这种事情让你感兴趣,我建议你看看^ _ ^
递归警告
JavaScript还不支持尾调用消除,这意味着在编写递归函数时需要extra precaution。有关密切关注程序的代码示例,请参阅this answer。