Объединить / сгладить массив массивов в JavaScript?

У меня есть массив JavaScript, как:

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

Как бы я мог объединить отдельные внутренние массивы в один, например:

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

104 ответа

Решение

Ты можешь использовать concat объединить массивы:

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

С использованием apply метод concat просто примет второй параметр в качестве массива, поэтому последняя строка идентична этой:

var merged2 = [].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 result = [].concat.apply([], [[1],[2,3],[4]]);
console.log(result); // [ 1, 2, 3, 4 ]

Нет императивного беспорядка.

Лучше всего это сделать с помощью функции сокращения JavaScript.

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

Для этого существует новый собственный метод ECMA 2018, который называется flat.

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]]

Большинство ответов здесь не работают с огромными (например, 200 000 элементов) массивами, и даже если они работают, они медленные. Ответ polkovnikov.ph имеет лучшую производительность, но он не работает для глубокого выравнивания.

Вот самое быстрое решение, которое работает также для массивов с несколькими уровнями вложенности:

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)
}

Это просто расширяется 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.

Вы можете использовать Underscore:

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,

Может быть, вы можете назвать это obliterate или же nuke если тебе не нравится имя 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 уровня глубины (т.е. массивы внутри массивов)

Если вы хотите также выровнять трехмерные или даже многомерные массивы, вы просто вызываете плоский метод несколько раз. Например (3 измерения):

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

console.log(arr);

Быть осторожен!

Array.Flat()метод относительно новый. Старые браузеры, такие как ie, могли не реализовывать этот метод. Если вы хотите, чтобы ваш код работал во всех браузерах, возможно, вам придется перенастроить JS в более старую версию. Проверьте совместимость с текущим браузером в веб-документах MD.

Решение для более общего случая, когда в вашем массиве могут быть некоторые элементы, не являющиеся массивами.

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

Еще одно решение 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]

ОБНОВИТЬ:

В последних выпусках ES6 лучше использовать встроенную функцию Array.prototype.flat ().

Благодаря @K._ упомянул об этом в комментариях.

Как насчет использования 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)

проверьте здесь для совместимости браузера

Пожалуйста, обратите внимание: когда 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));

Как только вы привыкнете к функциям с небольшими стрелками в форме карри, композиции функций и функциям более высокого порядка, этот код будет выглядеть как проза. Программирование тогда просто состоит из объединения небольших строительных блоков, которые всегда работают как положено, потому что они не содержат побочных эффектов.

ES6 One Line Flatten

См. 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 One Line Deep Flatten

См. 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* 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 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);

Есть 2 способа сгладить ваш массив

* 1-Первый, который вы можете использовать, метод Array.prototype.flat

      const veryDeep = [[1, [2, 2, [3, [4, [5, [6]]]]], 1]];

veryDeep.flat(Infinity)
 // [1, 2, 2, 3, 4, 5, 6, 1]
 /**  If you don't know the depth of the array, 
simply pass Infinity **\

Или вы можете привести аргумент, если знаете, насколько много вложен в ваш массив

      const twoLevelsDeep = [[1, [2, 2], 1]];
//depth = 1
twoLevelsDeep.flat();
[1, [2, 2], 1]

//depth = 2
twoLevelsDeep.flat(2);
// [1, 2, 2, 1]

*2-секундный способ сгладить ваш массив, используя оператор распространения в ES6 и Concat, он сгладит ваш массив независимо от того, сколько в нем вложено

       const multidimension = [🥗, [🍌, 🍎, 🍇], 🍣, [🐟, 🍚]];

   [].concat(...multidimension); // This will return: [🥗, 🍌, 🍎, 🍇, 🍣, 🐟, 
   🍚]

   const multidimension = [1, [2, 3, 4], 5, [6, 7]];

   [].concat(...multidimension); // This will return: [1, 2, 3, 4, 5, 6, 7]

Я сделал это с помощью рекурсии и замыканий

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;
}

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)

Простое решение

      const result = [
  ["$6"],
  ["$12"],
  ["$25"],
  ["$25"],
  ["$18"],
  ["$22"],
  ["$10"]
].flat()

console.log(result)
["$6", "$12", "$25", "$25", "$18", "$22", "$10"]
var arrays = [["a"], ["b", "c"]];
Array.prototype.concat.apply([], arrays);

// gives ["a", "b", "c"]

(Я просто пишу это как отдельный ответ, основанный на комментарии @danhbear.)

На днях я баловался с ES6 Generators и написал эту суть. Который содержит...

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 *.

Но опять же, я был просто дураком, так что я уверен, что есть более эффективные способы сделать это.

Другие вопросы по тегам