因此,这里有一个解决数组扁平化问题的示例解决方案。我的问题不是"如何"使数组变平。相反,我试图理解这种递归中出现的一些底层功能。
该解决方案遍历原始数组的每个元素,通过函数将任何元素放回原来的数组中,从而分解这些元素,直到它们不再是数组,可以推送到新数组中。
我的问题是,"for"循环如何跟踪元素通过函数放回的所有时间,并继续循环它当时正在处理的"原始"数组的其余部分?它必须以某种方式保持跟踪,否则每次元素是一个数组并重新通过时,电流循环都会被切断。希望我的问题有道理。
function steamrollArray(array) {
var flatArray = [];
flatten(array);
function flatten(array) {
for (var i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
flatten(array[i]);
} else {
flatArray.push(array[i]);
}
}
}
return flatArray;
}
steamrollArray([1, [2], [3, [[4]]]]);
我认为会有更好的答案,但现在。。。
至少,不要把它看作是"打破"循环,而是把它看作继续按过程顺序执行代码。因此,在循环的上下文中,它将自己称为一个函数,当该函数完成后,它将继续循环。示例
var item = [1, [2, 3], 4]
flatten(item)
的执行将是:
Loop 1:
Push 1
Loop 2:
Start a call to flatten with [2, 3]
Loop 1:
push 2
Loop 2:
push 3
End loop
Loop 3:
Push 4
End Loop.
所以重点是,它只是执行一个步骤的过程。它不必"记住"它在哪里,当函数返回时,javascript只需从调用函数的位置继续处理。
您可能希望查看调用堆栈。
结果数组flatArray
位于辅助函数的词法闭包中,因此本身不是数组的元素被推到这个位置,并且它们在结果上的放置顺序与它们的迭代顺序相同。
当其中一个元素是数组时,它调用flatten(array[i])
,在返回之前对该元素的元素进行平坦化。与所有函数一样,一条语句的执行需要在完成下一条语句之前完成,调用一个函数根本不会取消当前函数。它继续进行,直到return
语句结束。
想象一下这个功能:
function test () {
console.log("hello, ");
console.log("world!");
}
当调用它时,它确实会等到整个console.log("hello, ")
完成后再执行语句二(console.log("world!")
)。如果是递归调用,则需要在执行语句二之前完成该调用。它对所有函数调用都是一样的,只是递归调用。
它实际上并没有通过只发生一次的函数将元素"放回"。它检查一个元素是否是数组,然后在该数组中循环。
如果它不是一个数组,则将其推送到flatArray
。这里的递归是,如果任何元素是一个数组,它将通过flatten函数。然后,如果该数组有一个数组元素,则该元素被发送到flatten,依此类推
让我们举以下例子:[1, [2], [3, [[4]]]]
我们开始循环->
- 索引0是1,而不是数组,因此它被推送到flatArray
- 索引1是一个数组,因此我们递归-因为我们有一个2-然后推
- 索引2是一个数组,所以我们递归,内部数组的索引0是非数组3,所以它被推送。然后我们有最后一个索引-一个数组,我们递归到其中,以找到另一个数组-再次递归-最后到达4,我们推它
每个递归步骤的"保持跟踪"与任何其他方法调用的工作方式相同;javascript引擎在调用堆栈中调用新方法时随时跟踪变量和作用域。
对于您的特定示例,也许当flatten
函数不再嵌套时会更容易看到发生了什么。
function steamrollArray(array) {
var flatArray = [];
flatten(array, flatArray);
return flatArray;
}
function flatten(array, flatArray) {
flatArray = flatArray || [];
for (var i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
flatten(array[i]);
} else {
flatArray.push(array[i]);
}
}
}
steamrollArray([1, [2], [3, [[4]]]]); # [1, 2, 3, 4]
由于flatten
函数不再可访问flatArray
变量,因此需要进行一些细微的修改。但是MOD让steamrollArray
看起来是多余的。。。让我们摆脱它。
function flatten(array, flatArray) {
flatArray = flatArray || [];
for (var i = 0; i < array.length; i++) {
if (Array.isArray(array[i])) {
flatten(array[i], flatArray);
} else {
flatArray.push(array[i]);
}
}
return flatArray;
}
flatten([1, [2], [3, [[4]]]]); # [1, 2, 3, 4]
现在,递归发生的位置和方式更加明显了。每当一个数组被"发现"时,它也会被压扁。值被推送到一个新数组中,或者被推送到作为第二个参数传递的数组中。每次在for
循环内调用flatten
时,位置i
处的阵列本身被展平并推到flatArray
上。由于flatArray
也被递归地传递给flatten
函数,因此所有值都将被收集到该数组中。
我有一个更简单的解决方案,可以处理数组中的任何级别的嵌套。
function flattenArray(arr){
for(var i=0;i<arr.length;i++){
if(arr[i] instanceof Array){
Array.prototype.splice.apply(arr,[i,1].concat(arr[i]))
i--;
}
}
return arr;
}
使用for-of
循环和2个其他方式压平阵列的解决方案
将用于循环
let array = [1, [2], [3, [[4]]]];
let output = [];
let flattenArray = (arr) => {
for(let a of arr) {
Array.isArray(a) ? flattenArray(a) : output.push(a);
}
return output;
}
console.log(flattenArray(array));
使用reduce()
方法
let array = [1, [2], [3, [[4]]]]
function flattenArray(ary) {
return ary.reduce((acc, val) => {
if (Array.isArray(val)) {
return acc.concat(flattenArray(val))
}
return acc.concat(val)
},[])
}
console.log(flattenArray(array));
使用flat()
方法
let array = [1, [2], [3, [[4]]]];
let output = array.flat(Infinity); // use Infinity for flattening nested levels.
console.log(output);
let array = [1, [2], [3, [[4]]]];
const final = [];
const stack = [];
for (let i = 0; i < array.length; i++) {
const ele = array[i];
stack.push(ele);
while (stack.length) {
const first = stack.shift();
if (Array.isArray(first)) {
first.forEach(ele => stack.push(ele))
} else {
final.push(first)
}
}
}
console.log( final.join(', '));