如何在javascript中展平嵌套数组?

时间:2014-12-03 08:06:37

标签: javascript arrays

我们知道,使用[[0, 1], [2, 3], [4, 5]]方法展平数组reduce()

var flattened = [[0, 1], [2, 3], [4, 5]].reduce(function(a, b) {
  return a.concat(b);
});

那么如何将此数组[[[0], [1]], [[2], [3]], [[4], [5]]]展平为[0, 1, 2, 3, 4, 5]

26 个答案:

答案 0 :(得分:41)

递归的完美用例,可以处理更深层次的结构:

function flatten(ary) {
    var ret = [];
    for(var i = 0; i < ary.length; i++) {
        if(Array.isArray(ary[i])) {
            ret = ret.concat(flatten(ary[i]));
        } else {
            ret.push(ary[i]);
        }
    }
    return ret;
}

flatten([[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]]) // [0, 1, 2, 3, 4, 5]

或者,作为Array方法:

Array.prototype.flatten = function() {
    var ret = [];
    for(var i = 0; i < this.length; i++) {
        if(Array.isArray(this[i])) {
            ret = ret.concat(this[i].flatten());
        } else {
            ret.push(this[i]);
        }
    }
    return ret;
};

[[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]].flatten() // [0, 1, 2, 3, 4, 5]

编辑#1:嗯,认为它有点功能性的方式(除了命名的递归,应该使用Y-combinator实现纯函数:D)。

function flatten(ary) {
  return ary.reduce(function(a, b) {
    if (Array.isArray(b)) {
      return a.concat(flatten(b))
    }
    return a.concat(b)
  }, [])
}

让我们采用一些ES6语法,使其更短,一行。

const flatten = (ary) => ary.reduce((a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), [])

但请记住,这个不能用作数组方法,因为arrow functions没有自己的this


编辑#2:使用最新的Array.prototype.flat提案,这非常简单。 array方法接受一个可选参数depth,它指定嵌套数组结构应该被展平的深度(默认为1)。

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

为了展平任意深度的数组,只需使用flat调用Infinity方法。

[[[[[0]], [1]], [[[2], [3]]], [[4], [5]]]].flat(Infinity) // [0, 1, 2, 3, 4, 5]

答案 1 :(得分:40)

带递归的ES6风格:

function flatten(arr) {
  const flat = [].concat(...arr);
  return flat.some(Array.isArray) ? flatten(flat) : flat;
}

效率不高,所以除了相对微不足道的情况外,不要使用任何东西,但它至少看起来不错。

2018年6月更新:

现在有Array.prototype.flat method的ES提案。它目前处于第3阶段,这意味着它可能很快就会被浏览器实现(ish),并使其成为当前形式的规范。可能有一些聚四氟乙烯漂浮在周围。

示例:

const nested = [[[0], [1]], [[2], [3]], [[4], [5]]];
const flattened = nested.flat(2);  // Need to specify depth if > 1

答案 2 :(得分:31)

这是递归的替代方法( see jsfiddle here ),并且应该接受任何级别的深度,以避免堆栈溢出。

&#13;
&#13;
var array = [[0, 1], [2, 3], [4, 5, [6, 7, [8, [9, 10]]]]];
console.log(flatten(array), array); // does not mutate array
console.log(flatten(array, true), array); // array is now empty

// This is done in a linear time O(n) without recursion
// memory complexity is O(1) or O(n) if mutable param is set to false
function flatten(array, mutable) {
    var toString = Object.prototype.toString;
    var arrayTypeStr = '[object Array]';
    
    var result = [];
    var nodes = (mutable && array) || array.slice();
    var node;

    if (!array.length) {
        return result;
    }

    node = nodes.pop();
    
    do {
        if (toString.call(node) === arrayTypeStr) {
            nodes.push.apply(nodes, node);
        } else {
            result.push(node);
        }
    } while (nodes.length && (node = nodes.pop()) !== undefined);

    result.reverse(); // we reverse result to restore the original order
    return result;
}
&#13;
&#13;
&#13;

答案 3 :(得分:11)

ES6 one-liner:

function flatten(a) {
    return Array.isArray(a) ? [].concat(...a.map(flatten)) : a;
}

此外,非深层数组的非递归版本(效率不高但相当优雅)

function flatten(a) {
    var queue = a.slice();
    var result = [];
    while(queue.length) {
        let curr = queue.pop();
        if(Array.isArray(curr)) {
            queue.push(...curr);
        }
        else result.push(curr);
    }
    return result;
}

答案 4 :(得分:10)

基于@Leo的解决方案,但通过重复使用相同的数组并阻止.concat

更快
function flatten(ary, ret) {
    ret = ret === undefined ? [] : ret;
    for (var i = 0; i < ary.length; i++) {
        if (Array.isArray(ary[i])) {
            flatten(ary[i], ret);
        } else {
            ret.push(ary[i]);
        }
    }
    return ret;
}

示例:

function flatten(ary, ret) {
  ret = ret === undefined ? [] : ret;
  for (var i = 0; i < ary.length; i++) {
    if (Array.isArray(ary[i])) {
      flatten(ary[i], ret);
    } else {
      ret.push(ary[i]);
    }
  }
  return ret;
}
console.log(flatten([[[0], [1]], [[2], [3]], [[4], [5]]]));

或者Array.prototype.reduce,因为你提到了它:

function flatten(ary, ret) {
    return ary.reduce(function(ret, entry) {
        if (Array.isArray(entry)) {
            flatten(entry, ret);
        } else {
            ret.push(entry);
        }
        return ret;
    }, ret || []);
}

示例:

function flatten(ary, ret) {
  return ary.reduce(function(ret, entry) {
    if (Array.isArray(entry)) {
      flatten(entry, ret);
    } else {
      ret.push(entry);
    }
    return ret;
  }, ret || []);
}
console.log(flatten([[[0], [1]], [[2], [3]], [[4], [5]]]));

答案 5 :(得分:7)

仅展平2个级别:

var arr = [1, [2, 3], [4, 5, 6]];
[].concat.apply([], arr) // -> [1, 2, 3, 4, 5, 6]

答案 6 :(得分:4)

我喜欢我的解决方案:)

var flattenClosure = function(a) {
    var store = [];

    return function() {
        var internMapper = function(b) {
            if (Array.isArray(b)) {
                return b.map(internMapper);
            }
            store.push(b);
            return b;
        }
        a.map(internMapper);
        return store;
    }
};

console.log(flattenClosure([[[[[[[[1]]]], [2], [4], [6, 8, 9], 2]]], 10, 11, [15, 17, 20], [], 33])());

答案 7 :(得分:4)

免责声明:我知道这是一个陈旧且已经回答过的问题,但是@Nick让我进入了它,因为我已经评论了他的答案,因为是最昂贵的平铺方式之一数组。多年来我一直没有编写JavaScript代码,但就像骑自行车一样 - 一旦你知道你永远不会忘记;)

这是我的完整递归代码(不需要var flattened = []; function flatten(a, i) { if(a.length > i) { if(Array.isArray(a[i])) flatten(a[i], 0); else flattened.push(a[i]); flatten(a, i + 1); } } flatten([[0, 1], [2, 3], [4, 5]], 0); console.log(flattened); 循环):

toString().split(',')

我已经针对secitems.sections.push("Test") return secitems 解决方案对其进行了测试,并且我的速度提高了约7倍。这就是我在谈论昂贵;)时的意思。

答案 8 :(得分:4)

受到来自Eloquent JavaScript的代码和@axelduch提供的答案的启发(从我能说的内容来说效率更高。)

function flatten(array, mutable) {
  var nodes = (mutable && array) || array.slice(); // return a new array.
  var flattened = [];

  for (var node = nodes.shift(); node !== undefined; node = nodes.shift()) {
    if (Array.isArray(node)) {
      nodes.unshift.apply(nodes, node);
    } else {
      flattened.push(node);
    }
  }

  return flattened;
}

答案 9 :(得分:3)

这已经回答了,但我只是在研究JS并想知道:

    var array = [[[0], [1]], [[2], [3]], [[4], [5]]];
    var flattend = array.join(",").split(",");
    console.log(flattend);

只有副作用是联接将所有项目转换为字符串,但可以轻松修复

答案 10 :(得分:3)

function flatten(array) {
    return array.reduce(
        (previous, current) =>
            Array.isArray(current)
            ? [...previous, ...flatten(current)]
            : [...previous, current]
        , []
    );
}

答案 11 :(得分:3)

如果您知道阵列仅由数字组成,您可以执行以下操作:

array.join().split(',').map(Number);

答案 12 :(得分:2)

lodash中有三个与flatten中的问题flatten, flattenDeep, flattenDepth相关的效用函数。 flattenDeep进入一个深度,> var arr = [[[0], [1]], [[2], [3]], [[4], [5]]]; > _.flattenDeep(arr) [0, 1, 2, 3, 4, 5] 一直进入最深层,并且展平深度让你可以选择&#34;深度&#34;扁平化。

示例:

{{1}}

答案 13 :(得分:2)

具有函数式编程的实现

通过函数式编程,我们可以从另一个更通用的函数中导出flattentraverse

后者是一个遍历和减少任意嵌套数组的函数,就像平面数组一样。这是可能的,因为深度未知的嵌套数组不超过树数据结构的特定版本:

const traverse = f => g => acc => xs => {
  let [leaf, stack] = xs[0][0] === undefined
   ? [xs[0], xs.slice(1)]
   : f([]) (xs);

  return stack.length
   ? traverse(f) (g) (g(leaf) (acc)) (stack)
   : g(leaf) (acc);
};

const dfs = stack => tree => tree[0] === undefined 
 ? [tree, stack]
 : dfs(tree.length > 1 ? concat(stack) (tree.slice(1)) : stack) (tree[0]);

const concat = ys => xs => xs.concat(ys);
const flatten = f => traverse(f) (concat) ([]);

const xs = [[[1,2,3],4,5,6],7,8,[9,10,[11,12],[[13]],14],15];

console.log(flatten(dfs) (xs));

请注意,树数据结构可以先通过深度(DFS)或先通过广度(BFS)遍历。数组通常是第一顺序遍历的。

正如我所说,traverse是一个通用的缩减函数,如针对平面数组的reduce。因此,我们也可以轻松计算所有元素的总和:

const add = y => x => x + y;
traverse(dfs) (add) (0) (xs); // 120

结论:要以功能方式展平任意嵌套数组,我们只需要一个单行:const flatten = f => traverse(f) (concat) ([]);。所有其他涉及的功能都是通用的,并且具有一系列其他潜在的应用。这是100%的可重用性!

答案 14 :(得分:2)

var nested = [[[0], [1]], [[2], [3]], [[4], [5]]];
var flattened = [].concat.apply([],[].concat.apply([],nested));
console.log('-> flattened now: ' + flattened);

答案 15 :(得分:2)

基于dashambleanswer,但我相信这有点简单易懂:

var steamroller = function(arr) {
  var result = [];

  var dropHeavyObject = function(auxArr) {
    var flatnd = [];

    flatnd = auxArr.map(function(x) {
      if(Array.isArray(x)) {
        return dropHeavyObject(x);
      } else {
        result.push(x);
        return x;
      }
    });

    return flatnd;
  };

  dropHeavyObject(arr);
  return result;
}

答案 16 :(得分:1)

function flatten(arrayOfArrays) {
  return arrayOfArrays.reduce(function(flat, subElem) {
    return flat.concat(Array.isArray(subElem) ? flatten(subElem) : subElem);
  }, []);
}


var arr0 = [0, 1, 2, 3, 4];
var arr1 = [[0,1], 2, [3, 4]];
var arr2 = [[[0, 1], 2], 3, 4];
console.log(flatten(arr0));  // [0, 1, 2, 3, 4]
console.log(flatten(arr1));  // [0, 1, 2, 3, 4]
console.log(flatten(arr2));  // [0, 1, 2, 3, 4]
console.log(flatten([]));    // []

答案 17 :(得分:1)

使用Lisp约定。

然而,使用.shift()和.concat()是低效的。

    flatten (array) {

        // get first element (car) and shift array (cdr) 
        var car = array.shift();

        // check to see if array was empty
        if (car === undefined) {
            return [];

        // if the first element (car) was an array, recurse on it
        } else if (_.isArray(car)) {
            return flatten(car).concat(flatten(array));

        // otherwise, cons (concatenate) the car to the flattened version of cdr (rest of array)
        } else {
            return [car].concat(flatten(array))
        }
    }

答案 18 :(得分:1)

使用JSON.stringifyJSON.parse

arr = JSON.parse("[" + 
               JSON.stringify(arr)
                   .replace(/[\[\]]+/g,"")
                   .replace(/,,/g,",") +
               "]");

答案 19 :(得分:1)

function flatten(x) {
  if (x.length == 0) {return []};
  if (Array.isArray(x[0])) {
    return flatten(x[0].concat(flatten(x.slice(1,x.length))));
  }
  return [].concat([x[0]], flatten(x.slice(1,x.length)));
}

以递归方式展平数组。

答案 20 :(得分:0)

var flattenWithStack = function(arr) {
  var stack = [];
  var flat = [];
  stack.push(arr);
  while(stack.length > 0) {
    var curr = stack.pop();
    if(curr instanceof Array) {
      stack = stack.concat(curr);
    } else {
      flat.push(curr);
    }
  }
  return flat.reverse();
}

非递归。基本上是dfs。

答案 21 :(得分:0)

可以像这样解决

const array = [[0, 1], [2, 3], [4, 5, [6, 7, [8, [9, 10]]]]];

const flatten(arr) => arr.reduce((acc, item) => 
   acc.concat(Array.isArray(item) ? flatten(item) : item);
}, []);

console.log(flatten(array));

请注意,在深度数组的情况下,应该应用TCO

具有递归解决方案的TCO的版本

const array = [[0, 1], [2, 3], [4, 5, [6, 7, [8, [9, 10]]]]];

const flatten = (() => {
  const _flatten = (acc, arr) => arr.reduce((acc, item) =>  acc.concat(Array.isArray(item) ? _flatten([], item) : item), acc);

  return arr => _flatten([], arr);
})();

console.log(flatten(array))

答案 22 :(得分:0)

认为这个功能有效。

 function flatten(arr){
     return arr.reduce(function(a,b){
        return [].concat(Array.isArray(a)? flatten(a) :a,Array.isArray(b)? flatten(b):b);
      });

}

答案 23 :(得分:0)

继承人我所得到的:

function steamrollArray(arr) {
  // the flattened array
  var newArr = [];

  // recursive function
  function flatten(arr, newArr) {
    // go through array
    for (var i = 0; i < arr.length; i++) {
      // if element i of the current array is a non-array value push it
      if (Array.isArray(arr[i]) === false) {
        newArr.push(arr[i]);
      }
      // else the element is an array, so unwrap it
      else {
        flatten(arr[i], newArr);
      }
    }
  }

  flatten(arr, newArr);

  return newArr;
}

答案 24 :(得分:0)

如果你有一个无限嵌套数组,如下面的a,这就是我要做的。

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

Array.prototype.flatten = (array) => {
  const newArray = []
  const flattenHelper = (array) => {
    array.map(i => {
      Array.isArray(i) ? flattenHelper(i) : newArray.push(i)
    })
  }
  flattenHelper(a)
  return newArray
}

const newArray = a.flatten()
console.log(newArray);

答案 25 :(得分:0)

前几天我遇到了这个问题并发现了一些小问题...实际上我刚刚意识到它与上一个答案非常相似,并且对上述答案的评论表明,它不适用于对象,但对于简单数字等。它运作得很好。

如果将嵌套数组作为字符串,则用逗号分隔值。然后你可以用逗号分割它以形成一个字符串。如果您需要将字符串转换为int或float,则可以运行转换每个值的新数组。

stringArray = [[0, 1], [2, 3], [4, 5]].toString().split(',');
stringArray.forEach((v,i,a) => a[i] = parseFloat(a[i]));