合并/展平数组数组

Merge/flatten an array of arrays

本文关键字:数组 合并      更新时间:2023-09-26

我有一个JavaScript数组,如下所示:

[["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]]

我将如何将单独的内部数组合并为一个,如下所示:

["$6", "$12", "$25", ...]

ES2019

ES2019引入了可用于展平数组的Array.prototype.flat()方法。它与大多数环境兼容,尽管它仅在 Node 中可用.js从版本 11 开始,在 Internet Explorer 中根本不可用。

const arrays = [
      ["$6"],
      ["$12"],
      ["$25"],
      ["$25"],
      ["$18"],
      ["$22"],
      ["$10"]
    ];
const merge3 = arrays.flat(1); //The depth level specifying how deep a nested array structure should be flattened. Defaults to 1.
console.log(merge3);
    

<小时 />

较旧的浏览器

对于较旧的浏览器,您可以使用Array.prototype.concat来合并数组:

var arrays = [
  ["$6"],
  ["$12"],
  ["$25"],
  ["$25"],
  ["$18"],
  ["$22"],
  ["$10"]
];
var merged = [].concat.apply([], arrays);
console.log(merged);

使用 apply 方法concat将只将第二个参数作为数组,因此最后一行与此相同:

var merged = [].concat(["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]);

这是一个简短的函数,它使用一些较新的 JavaScript 数组方法来展平 n 维数组。

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

用法:

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

有一个令人困惑的隐藏方法,它构造一个新数组而不改变原始数组:

var oldArray = [[1],[2,3],[4]];
var newArray = Array.prototype.concat.apply([], oldArray);
console.log(newArray); // [ 1, 2, 3, 4 ]

最好通过javascript reduce函数来完成。

var arrays = [["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"], ["$0"], ["$15"],["$3"], ["$75"], ["$5"], ["$100"], ["$7"], ["$3"], ["$75"], ["$5"]];
arrays = arrays.reduce(function(a, b){
     return a.concat(b);
}, []);

或者,对于 ES2015:

arrays = arrays.reduce((a, b) => a.concat(b), []);

JS-小提琴

Mozilla 文档

有一个名为 flat 的新本机方法来做到这一点。

(截至 2019 年底,flat现已发布在 ECMA 2019 标准中,core-js@3(babel 的库)将其包含在他们的 polyfill 库中)

const arr1 = [1, 2, [3, 4]];
arr1.flat(); 
// [1, 2, 3, 4]
const arr2 = [1, 2, [3, 4, [5, 6]]];
arr2.flat();
// [1, 2, 3, 4, [5, 6]]
// Flatten 2 levels deep
const arr3 = [2, 2, 5, [5, [5, [6]], 7]];
arr3.flat(2);
// [2, 2, 5, 5, 5, [6], 7];
// Flatten all levels
const arr4 = [2, 2, 5, [5, [5, [6]], 7]];
arr4.flat(Infinity);
// [2, 2, 5, 5, 5, 6, 7];

这里的大多数答案都不适用于大型(例如 200 000 个元素)数组,即使它们有效,它们也很慢。

这是最快的解决方案,它也适用于具有多级嵌套的数组

const flatten = function(arr, result = []) {
  for (let i = 0, length = arr.length; i < length; i++) {
    const value = arr[i];
    if (Array.isArray(value)) {
      flatten(value, result);
    } else {
      result.push(value);
    }
  }
  return result;
};

例子

巨大的阵列

flatten(Array(200000).fill([1]));

它可以很好地处理巨大的数组。在我的机器上,执行此代码大约需要 14 毫秒。

嵌套数组

flatten(Array(2).fill(Array(2).fill(Array(2).fill([1]))));

它适用于嵌套数组。此代码生成[1, 1, 1, 1, 1, 1, 1, 1]

具有不同嵌套级别的数组

flatten([1, [1], [[1]]]);

像这样扁平化数组没有任何问题。

更新:事实证明,此解决方案不适用于大型数组。如果您正在寻找更好,更快的解决方案,请查看此答案。


function flatten(arr) {
  return [].concat(...arr)
}

is 只是扩展arr并将其作为参数传递给 concat() ,这会将所有数组合并为一个数组。相当于[].concat.apply([], arr).

您也可以尝试这样做进行深度展平:

function deepFlatten(arr) {
  return flatten(           // return shalowly flattened array
    arr.map(x=>             // with each x in array
      Array.isArray(x)      // is x an array?
        ? deepFlatten(x)    // if yes, return deeply flattened x
        : x                 // if no, return just x
    )
  )
}

请参阅JSBin上的演示。

本答案中使用的 ECMAScript 6 元素的参考资料:

  • 点差运算符
  • 箭头函数

旁注:并非所有浏览器都支持 find() 和箭头函数等方法,但这并不意味着您现在无法使用这些功能。只需使用 Babel — 它将 ES6 代码转换为 ES5。

您可以使用下划线:

var x = [[1], [2], [3, 4]];
_.flatten(x); // => [1, 2, 3, 4]

通用过程意味着我们不必在每次需要使用特定行为时重写复杂性。

concatMap(或flatMap)正是我们在这种情况下所需要的。

// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
  xs.concat (ys)
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
  xs.map(f).reduce(concat, [])
// id :: a -> a
const id = x =>
  x
// flatten :: [[a]] -> [a]
const flatten =
  concatMap (id)
// your sample data
const data =
  [["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]]
console.log (flatten (data))

先见之明

是的,你猜对了,它只扁平化了一个级别,这正是它应该工作的方式

想象一下这样的数据集

// Player :: (String, Number) -> Player
const Player = (name,number) =>
  [ name, number ]
// team :: ( . Player) -> Team
const Team = (...players) =>
  players
// Game :: (Team, Team) -> Game
const Game = (teamA, teamB) =>
  [ teamA, teamB ]
// sample data
const teamA =
  Team (Player ('bob', 5), Player ('alice', 6))
const teamB =
  Team (Player ('ricky', 4), Player ('julian', 2))
const game =
  Game (teamA, teamB)
console.log (game)
// [ [ [ 'bob', 5 ], [ 'alice', 6 ] ],
//   [ [ 'ricky', 4 ], [ 'julian', 2 ] ] ]

好的,现在假设我们要打印一个名单,显示所有将参加game的球员......

const gamePlayers = game =>
  flatten (game)
gamePlayers (game)
// => [ [ 'bob', 5 ], [ 'alice', 6 ], [ 'ricky', 4 ], [ 'julian', 2 ] ]

如果我们的flatten过程也扁平化嵌套数组,我们最终会得到这个垃圾结果......

const gamePlayers = game =>
  badGenericFlatten(game)
gamePlayers (game)
// => [ 'bob', 5, 'alice', 6, 'ricky', 4, 'julian', 2 ]

滚深,宝贝

这并不是说有时你也不想平展嵌套数组——只是这不应该是默认行为。

我们可以轻松制定deepFlatten程序...

// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
  xs.concat (ys)
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
  xs.map(f).reduce(concat, [])
// id :: a -> a
const id = x =>
  x
// flatten :: [[a]] -> [a]
const flatten =
  concatMap (id)
// deepFlatten :: [[a]] -> [a]
const deepFlatten =
  concatMap (x =>
    Array.isArray (x) ? deepFlatten (x) : x)
// your sample data
const data =
  [0, [1, [2, [3, [4, 5], 6]]], [7, [8]], 9]
console.log (flatten (data))
// [ 0, 1, [ 2, [ 3, [ 4, 5 ], 6 ] ], 7, [ 8 ], 9 ]
console.log (deepFlatten (data))
// [ 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 ]

那里。现在,您为每个作业都有一个工具 - 一个用于挤压一个嵌套级别,flatten,另一个用于消除所有嵌套deepFlatten

也许你可以称它为obliteratenuke,如果你不喜欢这个名字deepFlatten.


不要迭代两次!

当然,上述实现是聪明而简洁的,但是使用.map后跟对.reduce的调用意味着我们实际上正在执行比必要的更多的迭代。

使用我调用的可信组合器mapReduce有助于将迭代保持在最小值;它需要一个映射函数m :: a -> b,一个r :: (b,a) ->b的归约函数并返回一个新的归约函数 - 这个组合器是换能器的核心;如果你有兴趣,我已经写了关于它们的其他答案。

// mapReduce = (a -> b, (b,a) -> b, (b,a) -> b)
const mapReduce = (m,r) =>
  (acc,x) => r (acc, m (x))
// concatMap :: (a -> [b]) -> [a] -> [b]
const concatMap = f => xs =>
  xs.reduce (mapReduce (f, concat), [])
// concat :: ([a],[a]) -> [a]
const concat = (xs,ys) =>
  xs.concat (ys)
// id :: a -> a
const id = x =>
  x
// flatten :: [[a]] -> [a]
const flatten =
  concatMap (id)
  
// deepFlatten :: [[a]] -> [a]
const deepFlatten =
  concatMap (x =>
    Array.isArray (x) ? deepFlatten (x) : x)
// your sample data
const data =
  [ [ [ 1, 2 ],
      [ 3, 4 ] ],
    [ [ 5, 6 ],
      [ 7, 8 ] ] ]
console.log (flatten (data))
// [ [ 1. 2 ], [ 3, 4 ], [ 5, 6 ], [ 7, 8 ] ]
console.log (deepFlatten (data))
// [ 1, 2, 3, 4, 5, 6, 7, 8 ]

要扁平化单元素数组的数组,不需要导入库,简单的循环是最简单和最有效的解决方案:

for (var i = 0; i < a.length; i++) {
  a[i] = a[i][0];
}

致反对者:请阅读问题,不要投反对票,因为它不适合您非常不同的问题。对于所问的问题,此解决方案既是最快又最简单的。

您也可以

尝试新的Array.flat()方法。它的工作方式如下:

let arr = [["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]].flat()
console.log(arr);

flat() 方法创建一个新数组,其中所有子数组元素递归地连接到其中,直至 1 层深度(即数组内的数组)

如果还想展平三维甚至更高维的数组,只需多次调用 flat 方法即可。例如(3 个维度):

let arr = [1,2,[3,4,[5,6]]].flat().flat().flat();
console.log(arr);

小心!

Array.flat()方法相对较新。像 ie 这样的旧浏览器可能没有实现该方法。如果您希望代码在所有浏览器上运行,则可能需要将JS转换为旧版本。检查 MDN Web 文档以了解当前的浏览器兼容性。

函数式的另一个 ECMAScript 6 解决方案:

声明一个函数:

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

并使用它:

flatten( [1, [2,3], [4,[5,[6]]]] ) // -> [1,2,3,4,5,6]

 const flatten = arr => arr.reduce(
         (a, b) => a.concat(Array.isArray(b) ? flatten(b) : b), []
       );
console.log( flatten([1, [2,3], [4,[5],[6,[7,8,9],10],11],[12],13]) )

还考虑在现代浏览器的最新版本中可用的本机函数Array.prototype.flat()(ES6提案)。感谢@(Константин Ван)和@(Mark Amery)在评论中提到了它。

flat函数有一个参数,指定数组嵌套的预期深度,默认情况下等于1

[1, 2, [3, 4]].flat();                  // -> [1, 2, 3, 4]
[1, 2, [3, 4, [5, 6]]].flat();          // -> [1, 2, 3, 4, [5, 6]]
[1, 2, [3, 4, [5, 6]]].flat(2);         // -> [1, 2, 3, 4, 5, 6]
[1, 2, [3, 4, [5, 6]]].flat(Infinity);  // -> [1, 2, 3, 4, 5, 6]

let arr = [1, 2, [3, 4]];
console.log( arr.flat() );
arr =  [1, 2, [3, 4, [5, 6]]];
console.log( arr.flat() );
console.log( arr.flat(1) );
console.log( arr.flat(2) );
console.log( arr.flat(Infinity) );

一种针对更一般情况的解决方案,当您的数组中可能有一些非数组元素时。

function flattenArrayOfArrays(a, r){
    if(!r){ r = []}
    for(var i=0; i<a.length; i++){
        if(a[i].constructor == Array){
            flattenArrayOfArrays(a[i], r);
        }else{
            r.push(a[i]);
        }
    }
    return r;
}

使用reduce(callback[, initialValue]) JavaScript 1.8方法怎么样

list.reduce((p,n) => p.concat(n),[]);

会做这项工作。

const common = arr.reduce((a, b) => [...a, ...b], [])
您可以将

Array.flat()Infinity一起使用,用于任何深度的嵌套数组。

var arr = [ [1,2,3,4], [1,2,[1,2,3]], [1,2,3,4,5,[1,2,3,4,[1,2,3,4]]], [[1,2,3,4], [1,2,[1,2,3]], [1,2,3,4,5,[1,2,3,4,[1,2,3,4]]]] ];
let flatten = arr.flat(Infinity)
console.log(flatten)

查看此处了解浏览器兼容性

ES6 单行展平

参见 lodash 扁平化、下划线扁平化(浅true

function flatten(arr) {
  return arr.reduce((acc, e) => acc.concat(e), []);
}

function flatten(arr) {
  return [].concat.apply([], arr);
}

测试

test('already flatted', () => {
  expect(flatten([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]);
});
test('flats first level', () => {
  expect(flatten([1, [2, [3, [4]], 5]])).toEqual([1, 2, [3, [4]], 5]);
});

ES6 单线深扁平

参见 lodash flattenDeep,下划线 flatten

function flattenDeep(arr) {
  return arr.reduce((acc, e) => Array.isArray(e) ? acc.concat(flattenDeep(e)) : acc.concat(e), []);
}

测试

test('already flatted', () => {
  expect(flattenDeep([1, 2, 3, 4, 5])).toEqual([1, 2, 3, 4, 5]);
});
test('flats', () => {
  expect(flattenDeep([1, [2, [3, [4]], 5]])).toEqual([1, 2, 3, 4, 5]);
});

使用展开运算符:

const input = [["$6"], ["$12"], ["$25"], ["$25"], ["$18"], ["$22"], ["$10"]];
const output = [].concat(...input);
console.log(output); // --> ["$6", "$12", "$25", "$25", "$18", "$22", "$10"]

请注意:当使用Function.prototype.apply[].concat.apply([], arrays))或展开运算符([].concat(...arrays))来展平数组时,两者都可能导致大型数组的堆栈溢出,因为函数的每个参数都存储在堆栈上。

下面是一个函数式的堆栈安全实现,它权衡了最重要的需求:

  • 可 重用
  • 可读性
  • 简洁
  • 性能

// small, reusable auxiliary functions:
const foldl = f => acc => xs => xs.reduce(uncurry(f), acc); // aka reduce
const uncurry = f => (a, b) => f(a) (b);
const concat = xs => y => xs.concat(y);
// the actual function to flatten an array - a self-explanatory one-line:
const flatten = xs => foldl(concat) ([]) (xs);
// arbitrary array sizes (until the heap blows up :D)
const xs = [[1,2,3],[4,5,6],[7,8,9]];
console.log(flatten(xs));
// Deriving a recursive solution for deeply nested arrays is trivially now
// yet more small, reusable auxiliary functions:
const map = f => xs => xs.map(apply(f));
const apply = f => a => f(a);
const isArray = Array.isArray;
// the derived recursive function:
const flattenr = xs => flatten(map(x => isArray(x) ? flattenr(x) : x) (xs));
const ys = [1,[2,[3,[4,[5],6,],7],8],9];
console.log(flattenr(ys));

一旦你习惯了柯里形式的小箭头函数、函数组合和高阶函数,这段代码读起来就像散文一样。然后,编程仅包括将始终按预期工作的小构建块放在一起,因为它们不包含任何副作用。

我推荐一个节省空间的生成器函数:

function* flatten(arr) {
  if (!Array.isArray(arr)) yield arr;
  else for (let el of arr) yield* flatten(el);
}
// Example:
console.log(...flatten([1,[2,[3,[4]]]])); // 1 2 3 4

如果需要,请创建一个平展值数组,如下所示:

let flattened = [...flatten([1,[2,[3,[4]]]])]; // [1, 2, 3, 4]

如果你只有包含 1 个字符串元素的数组:

[["$6"], ["$12"], ["$25"], ["$25"]].join(',').split(',');

会做这项工作。与您的代码示例特别匹配的 Bt。

我已经使用递归和闭包完成了它

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

A Haskellesque approach

function flatArray([x,...xs]){
  return x ? [...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);

ES6 方式:

const flatten = arr => arr.reduce((acc, next) => acc.concat(Array.isArray(next) ? flatten(next) : next), [])
const a = [1, [2, [3, [4, [5]]]]]
console.log(flatten(a))

ES5 用于flatten函数的方式,ES3 回退用于 N 次嵌套数组:

var flatten = (function() {
  if (!!Array.prototype.reduce && !!Array.isArray) {
    return function(array) {
      return array.reduce(function(prev, next) {
        return prev.concat(Array.isArray(next) ? flatten(next) : next);
      }, []);
    };
  } else {
    return function(array) {
      var arr = [];
      var i = 0;
      var len = array.length;
      var target;
      for (; i < len; i++) {
        target = array[i];
        arr = arr.concat(
          (Object.prototype.toString.call(target) === '[object Array]') ? flatten(target) : target
        );
      }
      return arr;
    };
  }
}());
var a = [1, [2, [3, [4, [5]]]]];
console.log(flatten(a));

如果你使用 lodash,你可以使用它的flatten方法:https://lodash.com/docs/4.17.14#flatten

lodash 的好处是它还具有扁平化数组的方法:

i) 递归:https://lodash.com/docs/4.17.14#flattenDeep

ii) 最多 n 级嵌套:https://lodash.com/docs/4.17.14#flattenDepth

例如

const _ = require("lodash");
const pancake =  _.flatten(array)

几天我在玩ES6生成器并写了这个要点。其中包含...

function flatten(arrayOfArrays=[]){
  function* flatgen() {
    for( let item of arrayOfArrays ) {
      if ( Array.isArray( item )) {
        yield* flatten(item)
      } else {
        yield item
      }
    }
  }
  return [...flatgen()];
}
var flatArray = flatten([[1, [4]],[2],[3]]);
console.log(flatArray);

基本上,我正在创建一个遍历原始输入数组的生成器,如果它找到一个数组,它会使用 yield* 运算符与递归结合使用来不断展平内部数组。如果项目不是数组,则只生成单个项目。然后使用 ES6 Spread 运算符(又名 splat 运算符),我将生成器展平为一个新的数组实例。

我还没有测试过它的性能,但我认为这是使用生成器和 yield* 运算符的一个很好的简单示例。

但同样,我只是在胡闹,所以我相信有更高性能的方法可以做到这一点。

只是没有 lodash 的最佳解决方案

let flatten = arr => [].concat.apply([], arr.map(item => Array.isArray(item) ? flatten(item) : item))

我宁愿按原样将整个数组转换为字符串,但与其他答案不同,我会使用 JSON.stringify 而不是使用 toString() 方法执行此操作,该方法会产生不需要的结果。

有了JSON.stringify输出,剩下的就是删除所有括号,再次用开始和结束括号包装结果,并使用JSON.parse提供结果,使字符串恢复"生命"。

  • 可以处理无限嵌套数组,没有任何速度成本。
  • 可以正确处理包含逗号的字符串的数组项。

var arr = ["abc",[[[6]]],["3,4"],"2"];
var s = "[" + JSON.stringify(arr).replace(/'[|]/g,'') +"]";
var flattened = JSON.parse(s);
console.log(flattened)

  • 仅适用于字符串/数字的多维数组(不是对象)

制作扁平数组的方法

  • 使用 Es6 flat()
  • 使用 Es6 reduce()
  • 使用递归
  • 使用字符串操作
[1,[2,[3,[4,[5,[6,7],8],

9],10]]] - [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]

// using Es6 flat() 
let arr = [1,[2,[3,[4,[5,[6,7],8],9],10]]]
console.log(arr.flat(Infinity))
// using Es6 reduce()
let flatIt = (array) => array.reduce(
  (x, y) => x.concat(Array.isArray(y) ? flatIt(y) : y), []
)
console.log(flatIt(arr))
// using recursion
function myFlat(array) {
  let flat = [].concat(...array);
  return flat.some(Array.isArray) ? myFlat(flat) : flat;
}
console.log(myFlat(arr));
// using string manipulation
let strArr = arr.toString().split(','); 
for(let i=0;i<strArr.length;i++)
  strArr[i]=parseInt(strArr[i]);
console.log(strArr)

我认为array.flat(Infinity)是一个完美的解决方案。但平面函数是一个相对较新的函数,可能无法在旧版本的浏览器中运行。我们可以使用递归函数来解决这个问题。

const arr = ["A", ["B", [["B11", "B12", ["B131", "B132"]], "B2"]], "C", ["D", "E", "F", ["G", "H", "I"]]]
const flatArray = (arr) => {
    const res = []
    for (const item of arr) {
        if (Array.isArray(item)) {
            const subRes = flatArray(item)
            res.push(...subRes)
        } else {
            res.push(item)
        }
    }
    return res
}
console.log(flatArray(arr))