【问题标题】:Javascript recursive array flatteningJavascript递归数组展平
【发布时间】:2015-07-14 21:59:47
【问题描述】:

我正在锻炼并尝试编写一个递归数组展平函数。代码在这里:

function flatten() {
    var flat = [];
    for (var i = 0; i < arguments.length; i++) {
        if (arguments[i] instanceof Array) {
            flat.push(flatten(arguments[i]));
        }
        flat.push(arguments[i]);
    }
    return flat;
}

问题是,如果我在那里传递一个数组或嵌套数组,我会收到“超出最大调用堆栈大小”错误。我做错了什么?

【问题讨论】:

标签: javascript arrays recursion


【解决方案1】:

问题是你如何传递数组的处理,如果值是一个数组,那么你一直在调用它导致无限循环

function flatten() {
    var flat = [];
    for (var i = 0; i < arguments.length; i++) {
        if (arguments[i] instanceof Array) {
            flat.push.apply(flat, flatten.apply(this, arguments[i]));
        } else {
            flat.push(arguments[i]);
        }
    }
    return flat;
}

演示:Fiddle

这是一个更现代的版本:

function flatten(items) {
  const flat = [];

  items.forEach(item => {
    if (Array.isArray(item)) {
      flat.push(...flatten(item));
    } else {
      flat.push(item);
    }
  });

  return flat;
}

【讨论】:

  • 这是另一个现代的:function flatten(array, accu = []) { array.forEach(a => { if(Array.isArray(a)) flatten(a, accu) else acc .push(a) }) 返回精度 }
  • 为什么flat的声明在flatten()的递归调用中不屏蔽最外层的flat变量?
  • @mrwnt10 我并不是要听起来讽刺,而是因为它实际上是最外层的flat 变量。每个连续帧中的底层flats 最终被返回,然后被推入顶层flat 最终返回。
【解决方案2】:

2019 中使用 ES6 展平数组的简洁方法是 flat():

const array = [1, 1, [2, 2], [[3, [4], 3], 2]]

// All layers
array.flat(Infinity) // [1, 1, 2, 2, 3, 4, 3, 2]

// Varying depths
array.flat() // [1, 1, 2, 2, Array(3), 2]

array.flat(2) // [1, 1, 2, 2, 3, Array(1), 3, 2]
array.flat().flat() // [1, 1, 2, 2, 3, Array(1), 3, 2]

array.flat(3) // [1, 1, 2, 2, 3, 4, 3, 2]
array.flat().flat().flat() // [1, 1, 2, 2, 3, 4, 3, 2]

Mozilla Docs

Can I Use - 21 年 12 月 94%

【讨论】:

    【解决方案3】:

    如果项目是数组,我们只需将所有剩余的项目添加到这个数组中

    function flatten(array, result) {
      if (array.length === 0) {
        return result
      }
      var head = array[0]
      var rest = array.slice(1)
      if (Array.isArray(head)) {
        return flatten(head.concat(rest), result)
      }
      result.push(head)
      return flatten(rest, result)
    }
    
    console.log(flatten([], []))
    console.log(flatten([1], []))
    console.log(flatten([1,2,3], []))
    console.log(flatten([1,2,[3,4]], []))
    console.log(flatten([1,2,[3,[4,5,6]]], []))
    console.log(flatten([[1,2,3],[4,5,6]], []))
    console.log(flatten([[1,2,3],[[4,5],6,7]], []))
    console.log(flatten([[1,2,3],[[4,5],6,[7,8,9]]], []))

    【讨论】:

    • 这应该是选择的答案。它不使用任何迭代循环。
    【解决方案4】:
    [...arr.toString().split(",")]
    

    使用ObjecttoString() 方法。使用扩展运算符(...) 创建一个字符串数组,然后将其拆分为","

    例子:

    let arr =[["1","2"],[[[3]]]]; // output : ["1", "2", "3"]
    

    【讨论】:

    • 这会将数字转换为字符串,并将包含逗号的字符串分开。
    【解决方案5】:

    Haskellesque 方法...

    function flatArray([x,...xs]){
      return x !== undefined ? [...Array.isArray(x) ? flatArray(x) : [x],...flatArray(xs)]
                             : [];
    }
    
    var na = [[1,2],[3,[4,5]],[6,7,[[[8],9]]],10],
        fa = flatArray(na);
    console.log(fa);

    所以我认为上面的代码 sn-p 可以通过适当的缩进更容易理解;

    function flatArray([x,...xs]){
      return x !== undefined ? [ ...Array.isArray(x) ? flatArray(x)
                                                     : [x]
                               , ...flatArray(xs)
                               ]
                             : [];
    }
    
    var na = [[1,2],[3,[4,5]],[6,7,[[[8],9]]],10],
        fa = flatArray(na);
    console.log(fa);
    

    【讨论】:

      【解决方案6】:

      如果你假设你的第一个参数是一个数组,你可以让它变得非常简单。

      function flatten(a) {
          return a.reduce((flat, i) => {
            if (Array.isArray(i)) {
              return flat.concat(flatten(i));
            }
            return flat.concat(i);
          }, []);
        }
      

      如果您确实想要展平多个数组,只需在传递之前将它们连接起来。

      【讨论】:

        【解决方案7】:

        如果有人在寻找扁平化的对象数组(例如tree),那么这里有一个代码:

        function flatten(items) {
          const flat = [];
        
          items.forEach(item => {
            flat.push(item)
            if (Array.isArray(item.children) && item.children.length > 0) {
              flat.push(...flatten(item.children));
              delete item.children
            }
            delete item.children
          });
        
          return flat;
        }
        
        var test = [
        	{children: [
              {children: [], title: '2'}
              ],
          title: '1'},
        	{children: [
              {children: [], title: '4'},
              {children: [], title: '5'}
              ],
          title: '3'}
        ]
        
        console.log(flatten(test))

        【讨论】:

          【解决方案8】:

          您的代码缺少 else 语句并且递归调用不正确(您一遍又一遍地传递相同的数组,而不是传递它的项)。

          你的函数可以这样写:

          function flatten() {
              // variable number of arguments, each argument could be:
              // - array
              //   array items are passed to flatten function as arguments and result is appended to flat array
              // - anything else
              //   pushed to the flat array as-is
              var flat = [],
                  i;
              for (i = 0; i < arguments.length; i++) {
                  if (arguments[i] instanceof Array) {
                      flat = flat.concat(flatten.apply(null, arguments[i]));
                  } else {
                      flat.push(arguments[i]);
                  }
              }
              return flat;
          }
          
          // flatten([[[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]], [[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]]]);
          //            [0, 1, 2,   0, 1, 2,     0, 1, 2,   0, 1, 2,       0, 1, 2,   0, 1, 2,     0, 1, 2,   0, 1, 2]
          

          【讨论】:

          • 为什么flatflatten()的递归调用中的声明不屏蔽外部flat变量?
          • @mrwnt10(如果我正确理解您的问题)var flat 创建一个局部变量,因此它不会覆盖外部平面变量。
          【解决方案9】:

          现代但不是跨浏览器

          function flatten(arr) {
            return arr.flatMap(el => {
              if(Array.isArray(el)) {
                  return flatten(el);
              } else {
                return el;
              }
            });
          }
          

          【讨论】:

            【解决方案10】:

            这是针对这个问题的 Vanilla JavaScript 解决方案

            var _items = {'keyOne': 'valueOne', 'keyTwo': 'valueTwo', 'keyThree': ['valueTree', {'keyFour': ['valueFour', 'valueFive']}]};
            
            // another example
            // _items = ['valueOne', 'valueTwo', {'keyThree': ['valueTree', {'keyFour': ['valueFour', 'valueFive']}]}];
            
            // another example
            /*_items = {"data": [{
                "rating": "0",
                "title": "The Killing Kind",
                "author": "John Connolly",
                "type": "Book",
                "asin": "0340771224",
                "tags": "",
                "review": "i still haven't had time to read this one..."
            }, {
                "rating": "0",
                "title": "The Third Secret",
                "author": "Steve Berry",
                "type": "Book",
                "asin": "0340899263",
                "tags": "",
                "review": "need to find time to read this book"
            }]};*/
            
            function flatten() {
                var results = [],
                    arrayFlatten;
            
                arrayFlatten = function arrayFlattenClosure(items) {
                    var key;
                    
                    for (key in items) {
                        if ('object' === typeof items[key]) {
                            arrayFlatten(items[key]);
                        } else {
                            results.push(items[key]);
                        }
                    }
                };
            
                arrayFlatten(_items);
                
                return results;
            }
            
            console.log(flatten());

            【讨论】:

              【解决方案11】:

              这是一个从 absurdum 中提取的递归 reduce 实现,它模仿了 lodash 的 _.concat()

              它可以接受任意数量的数组或非数组参数。阵列可以是任何深度级别。结果输出将是一个扁平值数组。

              export const concat = (...arrays) => {
                return flatten(arrays, []);
              }
              
              function flatten(array, initial = []) {
                return array.reduce((acc, curr) => {
                  if(Array.isArray(curr)) {
                    acc = flatten(curr, acc);
                  } else {
                    acc.push(curr);
                  }
                  return acc;
                }, initial);
              }
              

              它可以将任意数量的数组或非数组值作为输入。

              来源:我是荒谬的作者

              【讨论】:

                【解决方案12】:

                这是我的函数式方法:

                const deepFlatten = (array => (array, start = []) => array.reduce((acc, curr) => {
                    return Array.isArray(curr) ? deepFlatten(curr, acc) : [...acc, curr];
                }, start))();
                
                console.log(deepFlatten([[1,2,[3, 4, [5, [6]]]],7]));
                

                【讨论】:

                • 回复较晚,但我很好奇您为什么要选择额外的 IIFE 包装器和 start 参数。为什么不只是const deepFlatten = (xs) =&gt; xs .reduce ((a, x) =&gt; [...a, ... (Array .isArray (x) ? deepFlatten (x) : [x])], [])
                【解决方案13】:

                在 JavaScript 中展平数组的递归方法如下。

                function flatten(array) {
                    let flatArray = [];
                    for (let i = 0; i < array.length; i++) {
                        if (Array.isArray(array[i])) {
                            flatArray.push(...flatten(array[i]));
                        } else {
                            flatArray.push(array[i]);
                        }
                    }
                    return flatArray;
                }
                
                let array = [[1, 2, 3], [[4, 5], 6, [7, 8, 9]]];
                console.log(flatten(array));    
                // Output = [ 1, 2, 3, 4, 5, 6, 7, 8, 9 ]
                
                let array2 = [1, 2, [3, [4, 5, 6]]];
                console.log(flatten(array2));    
                // Output = [ 1, 2, 3, 4, 5, 6 ]
                

                【讨论】:

                  【解决方案14】:

                  这应该可以工作

                  function flatten() {
                      var flat = [
                      ];
                      for (var i = 0; i < arguments.length; i++) {
                          flat = flat.concat(arguments[i]);
                      }
                      var removeIndex = [
                      ];
                      for (var i = flat.length - 1; i >= 0; i--) {
                          if (flat[i] instanceof Array) {
                              flat = flat.concat(flatten(flat[i]));
                              removeIndex.push(i);
                          }
                      }
                      for (var i = 0; i < removeIndex.length; i++) {
                          flat.splice(removeIndex - i, 1);
                      }
                      return flat;
                  }
                  

                  【讨论】:

                    【解决方案15】:

                    其他答案确实指出了 OP 代码故障的根源。编写更具描述性的代码,问题实际上归结为“数组检测/-reduce/-concat-recursion”......

                    (function (Array, Object) {
                    
                    
                    //"use strict";
                    
                    
                      var
                        array_prototype       = Array.prototype,
                    
                        array_prototype_slice = array_prototype.slice,
                        expose_internal_class = Object.prototype.toString,
                    
                    
                        isArguments = function (type) {
                          return !!type && (/^\[object\s+Arguments\]$/).test(expose_internal_class.call(type));
                        },
                        isArray     = function (type) {
                          return !!type && (/^\[object\s+Array\]$/).test(expose_internal_class.call(type));
                        },
                    
                        array_from  = ((typeof Array.from == "function") && Array.from) || function (listAlike) {
                          return array_prototype_slice.call(listAlike);
                        },
                    
                    
                        array_flatten = function flatten (list) {
                          list = (isArguments(list) && array_from(list)) || list;
                    
                          if (isArray(list)) {
                            list = list.reduce(function (collector, elm) {
                    
                              return collector.concat(flatten(elm));
                    
                            }, []);
                          }
                          return list;
                        }
                      ;
                    
                    
                      array_prototype.flatten = function () {
                        return array_flatten(this);
                      };
                    
                    
                    }(Array, Object));
                    

                    从其他答案之一借用代码作为概念证明......

                    console.log([
                      [[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]],
                      [[[0, 1, 2], [0, 1, 2]], [[0, 1, 2], [0, 1, 2]]]
                    ].flatten());
                    //[0, 1, 2, 0, 1, 2, 0, 1, 2, 0, 1, 2, ..., ..., ..., 0, 1, 2]
                    

                    【讨论】:

                      【解决方案16】:

                      我希望你能有所不同。一种结合了递归和“for循环”/高阶函数。我想在没有 for 循环或高阶函数的情况下回答。

                      再次检查数组的第一个元素是否为数组。如果是,请执行递归,直到到达最里面的数组。然后推到结果。我希望我以纯递归的方式来处理它。

                      function flatten(arr, result = []) {
                        if(!arr.length) return result;
                        (Array.isArray(arr[0])) ? flatten(arr[0], result): result.push(arr[0]);
                        return flatten(arr.slice(1),result)
                      }
                      

                      【讨论】:

                        【解决方案17】:

                        我认为问题在于您使用 arguments 的方式。

                        既然你说当你传递一个嵌套数组时,它会导致“超出最大调用堆栈大小”错误。

                        因为arguments[0] 是指向您传递给flatten 函数的第一个参数的引用。例如:

                           flatten([1,[2,[3]]]) // arguments[0] will always represents `[1,[2,[3]]]`
                        

                        所以,您的代码最终会使用相同的参数一次又一次地调用 flatten

                        为了解决这个问题,我认为最好使用named arguments,而不是使用arguments,它本质上不是一个“真正的数组”。

                        【讨论】:

                          【解决方案18】:

                          下面的函数扁平化数组并维护每个项目的类型,而不是将它们更改为字符串。如果您需要平面数组,它不仅包含像项目这样的数字,这很有用。它可以平整任何类型的阵列,没有副作用。

                          function flatten(arr) {
                            for (let i = 0; i < arr.length; i++) {
                              arr = arr.reduce((a, b) => a.concat(b),[])
                            }
                            return arr
                          }
                          
                          console.log(flatten([1, 2, [3, [[4]]]]));
                          console.log(flatten([[], {}, ['A', [[4]]]]));
                          

                          【讨论】:

                            【解决方案19】:

                            有几种方法可以做到这一点:

                            1. 使用 flat 方法和 Infinity 关键字:

                              const flattened = arr.flat(Infinity);

                            2. 您可以像这样使用 reduce 和 concat 方法展平任何数组:

                              function flatten(arr) { return arr.reduce((acc, cur) =&gt; acc.concat(Array.isArray(cur) ? flatten(cur) : cur), []); };

                            阅读更多: https://www.techiedelight.com/recursively-flatten-nested-array-javascript/

                            【讨论】:

                              【解决方案20】:
                              const nums = [1,2,[3,4,[5]]];
                              const chars = ['a',['b','c',['d',['e','f']]]];
                              const mixed = ['a',[3,6],'c',[1,5,['b',[2,'e']]]];  
                              
                              const flatten = (arr,res=[]) => res.concat(...arr.map((el) => (Array.isArray(el)) ? flatten(el) : el));
                              
                              console.log(flatten(nums)); // [ 1, 2, 3, 4, 5 ]
                              console.log(flatten(chars)); // [ 'a', 'b', 'c', 'd', 'e', 'f' ]
                              console.log(flatten(mixed)); // [ 'a', 3, 6, 'c', 1, 5, 'b', 2, 'e' ]
                              

                              以下是细分:

                              1. 用“map”循环遍历“arr”

                              arr.map((el) => ...)

                              1. 在每次迭代中,我们将使用三元组来检查每个“el”是否为数组

                              (Array.isArray(el))

                              1. 如果“el”是一个数组,则递归调用“flatten”并将“el”作为参数传入

                              展平(el)

                              1. 如果“el”不是数组,则直接返回“el”

                              : 埃尔

                              1. 最后,将三元的结果与“res”连接

                              res.concat(...arr.map((el) => (Array.isArray(el)) ? flatten(el) : el));

                              --> 扩展运算符将复制所有元素而不是数组本身,同时与“res”连接

                              【讨论】:

                                【解决方案21】:

                                var nestedArr = [1, 2, 3, [4, 5, [6, 7, [8, [9]]]], 10];
                                let finalArray = [];
                                const getFlattenArray = (array) => {
                                
                                    array.forEach(element => {
                                        if (Array.isArray(element)) {
                                            getFlattenArray(element)
                                        } else {
                                            finalArray.push(element)
                                        }
                                    });
                                
                                }
                                
                                getFlattenArray(nestedArr);
                                在 finalArray 中,您将获得展平的数组

                                【讨论】:

                                【解决方案22】:

                                使用 forEach 的解决方案

                                function flatten(arr) {
                                  const flat = [];
                                  arr.forEach((item) => {
                                    Array.isArray(item) ? flat.push(...flatten(item)) : flat.push(item);
                                  });
                                  return flat;  
                                }
                                

                                使用reduce的解决方案

                                function flatten(arr) {
                                  return arr.reduce((acc, curr) => {
                                    if (Array.isArray(curr)) {
                                      return [...acc, ...flatten(curr)];
                                    } else {
                                      return [...acc, curr];
                                    }
                                  }, []);
                                }
                                

                                【讨论】:

                                  【解决方案23】:

                                  你应该为递归添加停止条件。

                                  举个例子 if len (arguments[i]) ==0 返回

                                  【讨论】:

                                  • 为什么当 i==arguments.length 为真时它不停止?
                                  • 那么,有什么问题?(
                                  【解决方案24】:

                                  我已在 stackoverflow 中的this page 发布了我的数组展平的递归版本。

                                  【讨论】:

                                  • 这是指向答案的链接,而不是答案。如果您要链接到与此问题相同的问题,请将此问题标记为与该问题重复。如果您觉得问题有所不同,请定制此问题的链接答案并将其发布在此处。
                                  猜你喜欢
                                  • 2015-12-06
                                  • 1970-01-01
                                  • 2015-08-15
                                  • 1970-01-01
                                  • 1970-01-01
                                  • 2015-12-06
                                  • 1970-01-01
                                  • 2015-02-02
                                  • 1970-01-01
                                  相关资源
                                  最近更新 更多