Javascript递归数组展平

Posted

技术标签:

【中文标题】Javascript递归数组展平【英文标题】:Javascript recursive array flattening 【发布时间】: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;

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

【问题讨论】:

旁注:***.com/questions/10865025/… flatten.apply(this, arguments[i]); 但这不是唯一的问题。 这能回答你的问题吗? Merge/flatten an array of arrays 【参考方案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】:

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

    使用 flat 方法和 Infinity 关键字:

    const flattened = arr.flat(Infinity);

    您可以像这样使用 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' ]

以下是细分:

    用“map”循环遍历“arr”

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

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

(Array.isArray(el))

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

展平(el)

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

: 埃尔

    最后,将三元的结果与“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 中,您将获得展平的数组

【讨论】:

请不要在几个现有问题中添加duplicate answers。【参考方案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】:

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

【讨论】:

这是指向答案的链接,而不是答案。如果您要链接到与此问题相同的问题,请将此问题标记为与该问题重复。如果您觉得问题有所不同,请定制此问题的链接答案并将其发布在此处。

以上是关于Javascript递归数组展平的主要内容,如果未能解决你的问题,请参考以下文章

Javascript:使用递归将多维数组展平到位

javascript 通过递归来展平嵌套数组Ex:[1,2,3,[4,5,[7,8,[10,11,[12,13,[[[[[[14]]]]]]]]]] ]]

在javascript中展平数组[重复]

Javascript函数没有正确展平数组

用于递归展平结果的 JS 数组串联

使用递归(并且不使用循环)展平嵌套数组