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


1105

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

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

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

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

gist.github.com/Nishchit14/4c6a7349b3c778f7f97b912629a9f228 Эта ссылка описывает ES5 и ES6 flatten
Nishchit Dhanani

18
Все решения, использующие reduce+, concatпредставляют собой O ((N ^ 2) / 2), где в качестве принятого ответа (всего один вызов concat) будет не более O (N * 2) в плохом браузере и O (N) в Неплохо. Также решение Denys оптимизировано для актуального вопроса и в 2 раза быстрее, чем одиночное concat. Для reduceлюдей забавно чувствовать себя клевым, когда пишу крошечный код, но, например, если бы в массиве было 1000 одноэлементных подмассивов, все решения Redu + Concat выполняли бы 500500 операций, в то время как один конкат или простой цикл выполнял бы 1000 операций.
мужчина

12
Просто пользователь, распространяющий оператор[].concat(...array)
Олег Датер

@ gman как редуцирует + конкатное решение O ((N ^ 2) / 2)? stackoverflow.com/questions/52752666/… упоминает другую сложность.
Усман

4
С последними браузерами, которые поддерживают ES2019 : array.flat(Infinity)где Infinityмаксимальная глубина для выравнивания.
Тимоти Гу

Ответы:


1860

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

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

console.log(merged);

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

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

Существует также Array.prototype.flat()метод (представленный в ES2019), который можно использовать для выравнивания массивов, хотя он доступен только в 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);
    


10
Обратите внимание, что concatэто не изменяет исходный массив, поэтому mergedмассив останется пустым после вызова concat. Лучше сказать что-то вроде:merged = merged.concat.apply(merged, arrays);
Нейт

65
var merged = [].concat.apply([], arrays);кажется, работает нормально, чтобы получить его на одной строке. Отредактируйте: как уже показывает ответ Никиты.
Шон

44
Или Array.prototype.concat.apply([], arrays).
danhbear

30
Примечание: этот ответ выравнивает только один уровень. Для рекурсивного выравнивания см. Ответ @Trindaz.
Phrogz

209
В дополнение к комментарию @ Шона: синтаксис ES6 делает это очень лаконичным:var merged = [].concat(...arrays)
Sethi

505

Вот короткая функция, которая использует некоторые из новых методов массива 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]

17
Мне нравится этот подход. Он гораздо более общий и поддерживает вложенные массивы
alfredocambera

10
Каков профиль использования памяти для этого решения? Похоже, это создает много промежуточных массивов во время рекурсии хвоста ....
Дж. Б. Уилкинсон,

7
@ayjay, это начальное значение аккумулятора для функции Reduce , которое MDN называет начальным значением. В данном случае это значение flatпервого вызова анонимной функции, переданного в reduce. Если он не указан, то первый вызов reduceсвязывает первое значение из массива flat, что в конечном итоге приведет к тому , что он будет 1связан flatв обоих примерах. 1.concatэто не функция.
Ной Фрейтас

19
Или в более короткой, более сексуальной форме: const flatten = (arr) => arr.reduce((flat, next) => flat.concat(next), []);
Цветомир Цонев

12
Обращаясь к @ЦветомирЦоневу и решениям Ноа для произвольного вложения:const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
Будет ли

314

Существует запутанно скрытый метод, который создает новый массив без изменения исходного:

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


11
В CoffeeScript это[].concat([[1],[2,3],[4]]...)
amoebe

8
@amoebe ваш ответ дает [[1],[2,3],[4]]в результате. Решение, которое дает @Nikita, является правильным для CoffeeScript и JS.
Райан Кеннеди

5
Ааа, я нашел вашу ошибку. У вас есть лишняя пара квадратных скобок в вашей записи, должно быть [].concat([1],[2,3],[4],...).
Райан Кеннеди

21
Мне кажется, я тоже нашел твою ошибку. ...Являются фактическим кодом, а не некоторые многоточие точки.
Amoebe

3
Использование библиотечной функции не означает, что есть какой-то менее императивный «беспорядок». Просто беспорядок спрятан внутри библиотеки. Очевидно, что использование библиотеки лучше, чем развертывание вашей собственной функции, но утверждать, что эта техника уменьшает «императивный беспорядок», довольно глупо.
paldepind

204

Лучше всего это сделать с помощью функции сокращения 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


1
@JohnS На самом деле уменьшить подачу на конкатат по одному (массиву) значению за ход. Доказательство? Выньте метод Reduce () и посмотрите, работает ли он.
Reduce

3
Я также использовал бы уменьшение, но одна интересная вещь, которую я узнал из этого фрагмента, состоит в том, что большую часть времени вам не нужно передавать значение Initial :)
José F. Romaniello

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

4
@calbertts Просто передайте начальное значение, []и никакие дальнейшие проверки не требуются .

8
Поскольку вы используете ES6, вы также можете использовать оператор распространения в качестве литерала массива . arrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San

109

Для этого существует новый нативный метод 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];

4
Обидно, это даже не на первой странице ответов. Эта функция доступна в Chrome 69 и Firefox 62 (и Node 11 для тех, кто работает в бэкэнде)
Мэтт М.


2
-1; Нет, это не часть ECMAScript 2018 . Это все еще просто предложение, которое не попало ни в одну спецификацию ECMAScript.
Марк Амери

1
Я думаю, что теперь мы можем рассмотреть это .. потому что теперь это часть стандарта (2019) .. можем ли мы вернуться к части производительности этого раза?
Юварадж

Кажется, он еще не поддерживается ни одним браузером Microsoft (по крайней мере, в то время, когда я пишу этот комментарий)
Laurent S.

78

Большинство ответов здесь не работают с огромными (например, 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]]]);

У него нет проблем с такими массивами, как этот.


За исключением того, что ваш огромный массив довольно плоский. Это решение не будет работать для глубоко вложенных массивов. Никакого рекурсивного решения не будет. На самом деле нет браузера, но Safari не имеет TCO, поэтому никакой рекурсивный алгоритм не будет работать хорошо.
бесконечно

@nittle Но в какой реальной ситуации у вас будут массивы с несколькими уровнями вложенности?
Михал Перлаковский

2
Обычно, когда массив генерируется из пользовательского контента.
бесконечно

@ 0xcaff В Chrome он вообще не работает с массивом из 200 000 элементов (как вы понимаете RangeError: Maximum call stack size exceeded). Для массива из 20 000 элементов это занимает 2-5 миллисекунд.
Михал Перлаковский

3
что такое сложность обозначений O?
Джордж Кацанос

58

Обновление: оказалось, что это решение не работает с большими массивами. Если вы ищете лучшее, более быстрое решение, проверьте этот ответ .


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.


Поскольку почти все ответы здесь используются не по назначению apply, я удалил свои комментарии из вашего. Я все еще думаю, что использование apply/ распространение таким способом - плохой совет, но так как никто не заботится ...

@ LUH3417 Это не так, я очень ценю ваши комментарии. Оказалось, что вы правы - это решение действительно не работает с большими массивами. Я опубликовал другой ответ, который отлично работает даже с массивами из 200 000 элементов.
Михал Перлаковский

Если вы используете ES6, вы можете еще больше:const flatten = arr => [].concat(...arr)
Eruant

Что значит "не работает с большими массивами"? На сколько огромен? Что просходит?
GEMI

4
@GEMI Например, попытка сгладить массив из 500000 элементов с помощью этого метода дает «RangeError: Превышен максимальный размер стека вызовов».
Михал Перлаковский

51

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

var x = [[1], [2], [3, 4]];

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

20
Кто-то добавил Perl в JS? :-)
JBRWilkinson

9
@JBRWilkinson Может быть, JS хочет научить вас Хаскеллу за великое благо !?
Styfle

1+ - Вы также можете указать, что вы хотите мелкий плоский массив , указав trueвторой аргумент .
Джош Крозье

7
Ради полноты относительно комментария @ styfle: learnyouahaskell.com
Саймон А. Эугстер,

41

Общие процедуры означают, что нам не нужно переписывать сложность каждый раз, когда нам нужно использовать определенное поведение.

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последующего вызова to .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 ]


Часто, когда я вижу ваши ответы, я хочу отозвать свои, потому что они стали бесполезными. Отличный ответ! concatсам по себе не дуют стек, только ...и applyделает (наряду с очень большими массивами). Я этого не видел. Я просто чувствую себя ужасно прямо сейчас.

@ LUH3417 ты не должен чувствовать себя ужасно. Вы также даете хорошие ответы с хорошо написанными объяснениями. Все здесь - опыт обучения - я часто делаю ошибки и пишу плохие ответы. Я возвращаюсь к ним через пару месяцев и думаю, "что я думал?" и в конечном итоге полностью пересматривая вещи. Нет ответа идеально. Мы все в какой-то момент на кривой обучения ^ _ ^
Спасибо

1
Обратите внимание, что concatв Javascript есть другое значение, чем в Haskell. Haskell's concat( [[a]] -> [a]) будет вызван flattenв Javascript и реализован как foldr (++) [](Javascript: foldr(concat) ([])предполагается, что функции карри). Javascript concat- это странное приложение ( (++)в Haskell), которое может обрабатывать и то, [a] -> [a] -> [a]и другое a -> [a] -> [a].

Я думаю, что лучшее имя было flatMap, потому что это именно то, что concatMapесть: bindэкземпляр listмонады. concatpMapреализован как foldr ((++) . f) []. В переводе на Javascript: const flatMap = f => foldr(comp(concat) (f)) ([]). Это, конечно, похоже на вашу реализацию без comp.

в чем сложность этого алгоритма?
Джордж Кацанос

32

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

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

4
Этот подход был очень эффективен при выравнивании форм вложенных массивов наборов результатов, которые вы получаете из запроса JsonPath .
Kevinjansz

1
Добавлено в качестве метода массивов:Object.defineProperty(Array.prototype,'flatten',{value:function(r){for(var a=this,i=0,r=r||[];i<a.length;++i)if(a[i]!=null)a[i] instanceof Array?a[i].flatten(r):r.push(a[i]);return r}});
Phrogz

Это сломается, если мы вручную передадим второй аргумент. Например, попробуйте это: flattenArrayOfArrays (arr, 10)или это flattenArrayOfArrays(arr, [1,[3]]);- эти вторые аргументы добавляются к выводу.
Ом Шанкар

2
этот ответ не полный! результат рекурсии никогда не назначается в любом месте, поэтому результат рекурсии теряется
r3wt

1
@ r3wt пошел дальше и добавил исправление. Что вам нужно сделать, это убедиться, что текущий массив, который вы поддерживаете, на rсамом деле объединит результаты рекурсии.
августу

29

Чтобы сгладить массив массивов из одного элемента, вам не нужно импортировать библиотеку, простой цикл является и самым простым, и наиболее эффективным решением:

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

Для даунвотеров: пожалуйста, прочитайте вопрос, не понизьте голос, потому что это не устраивает вашу совсем другую проблему. Это решение является самым быстрым и простым для задаваемого вопроса.


4
Я бы сказал: «Не ищите более загадочных вещей». ^^

4
Я имею в виду ... когда я вижу людей, которые советуют использовать для этого библиотеку ... это безумие ...
Денис Сегюрет

3
Ах, использовать библиотеку только для этого было бы глупо ... но если она уже доступна.

9
@dystroy Условная часть цикла for не слишком удобочитаема. Или это только у меня: D
Андреас

4
Неважно, насколько это загадочно. Этот код "выравнивает" это ['foo', ['bar']]до ['f', 'bar'].
Jonschlinkert

29

Как насчет использования reduce(callback[, initialValue])методаJavaScript 1.8

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

Сделал бы работу.


8
[[1], [2,3]].reduce( (a,b) => a.concat(b), [] )более сексуально
Голопот

5
Не нужен второй аргумент в нашем простом случае[[1], [2,3]].reduce( (a,b) => a.concat(b))
Умайр Ахмед

29

Еще одно решение 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), доступную в последних выпусках современных браузеров. Благодаря @ (Константин Ван) и @ (Марк Эмери) упомянули это в комментариях.

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


3
Это красиво и аккуратно, но я думаю, что вы сделали передозировку ES6. Нет необходимости, чтобы внешняя функция была функцией стрелки. Я бы придерживался функции стрелки для обратного обратного вызова, но само выравнивание должно быть нормальной функцией.
Стивен Симпсон

3
@StephenSimpson но нужно ли, чтобы внешняя функция была не стрелкой? «Сглаживание должно быть нормальной функцией» - под «нормальным» вы подразумеваете «не стрелка», но почему? Зачем использовать функцию стрелки в вызове, чтобы уменьшить? Можете ли вы представить свою линию рассуждений?
Спасибо,

@naomik Я считаю, что это ненужно. Это в основном вопрос стиля; Я должен быть намного яснее в своем комментарии. Там нет основной причины кодирования, чтобы использовать один или другой. Тем не менее, функция легче увидеть и прочитать как не стрелка. Внутренняя функция полезна в качестве функции стрелки, так как она более компактна (и, конечно, контекст не создается). Функции со стрелками отлично подходят для создания компактной, легко читаемой функции и позволяют избежать этой путаницы. Тем не менее, они могут фактически затруднить чтение, когда недостаточно стрелки. Другие могут не согласиться, хотя!
Стивен Симпсон

ПолучениеRangeError: Maximum call stack size exceeded
Мэтт Уэстлейк

@Matt, поделись, пожалуйста, средой, которую ты используешь для воспроизведения ошибки
diziaq

22
const common = arr.reduce((a, b) => [...a, ...b], [])

Это то, как я делаю это в своем коде, но я не уверен, насколько скорость сравнивается с принятым ответом, если у вас очень длинный массив
Майкл Аарон Уилсон

14

Обратите внимание: когда 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));

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


1
Ха - ха. Я полностью уважаю ваш ответ, хотя читать функциональное программирование, как это, все равно, что читать по-японски по буквам для меня (англоговорящий).
Тарвин Стро-Спийер

2
Если вы обнаружите, что реализуете возможности языка A на языке B не как часть проекта с единственной целью сделать именно это, то кто-то где-то ошибся. Это может быть ты? Простое выполнение const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);избавляет вас от визуального мусора и объясняет вашим товарищам по команде, почему вам нужны 3 дополнительные функции и некоторые вызовы функций.
Daerdemandt

3
@Daerdemandt Но если вы напишите это как отдельные функции, вы, вероятно, сможете использовать их в другом коде.
Михал Перлаковский

@ MichałPerłakowski Если вам нужно использовать их в нескольких местах, не изобретайте велосипед и не выбирайте из них пакет - задокументированный и поддерживаемый другими людьми.
Дардемандт

12

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

Ваш второй пример лучше написан так, Array.prototype.concat.apply([], arr)потому что вы создаете дополнительный массив только для того, чтобы добраться до concatфункции. Среды выполнения могут оптимизировать или не оптимизировать его при запуске, но доступ к функции в прототипе не выглядит уродливее, чем это уже есть в любом случае.
Мёрре

11

Вы можете использовать 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)

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


8

Хаскельский подход

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


1
Согласитесь с @monners. Руки вниз самое элегантное решение во всей этой теме.
Николас

8

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


7

Если у вас есть только массивы с 1 строковым элементом:

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

сделаю работу. Bt, что конкретно соответствует вашему примеру кода.


3
Кто бы ни проголосовал, пожалуйста, объясните почему. Я искал достойное решение, и из всех решений оно мне больше всего понравилось.
Аноним

2
@ Аноним Я не понизил это, так как это технически соответствует требованиям вопроса, но, скорее всего, потому, что это довольно плохое решение, которое бесполезно в общем случае. Учитывая, что здесь есть лучшие решения, я бы никогда не порекомендовал кому-то воспользоваться этим, так как это ломает момент, когда у вас есть более одного элемента, или когда они не являются строками.
Thor84no

2
Я люблю это решение =)
Хуэй Тан

2
Он не обрабатывает только массивы с 1 строковыми элементами, он также обрабатывает этот массив ['$4', ["$6"], ["$12"], ["$25"], ["$25", "$33", ['$45']]].join(',').split(',')
alucic

Я обнаружил этот метод самостоятельно, однако знал, что он, должно быть, уже где-то задокументирован, мой поиск на этом закончился. Недостаток этого решения в том, что оно приводит числа, логические значения и т. Д. К строкам, попробуйте [1,4, [45, 't', ['e3', 6]]].toString().split(',') ---- или ----- [1,4, [45, 't', ['e3', 6], false]].toString().split(',')
Судхансу Чоудхари

7
var arrays = [["a"], ["b", "c"]];
Array.prototype.concat.apply([], arrays);

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

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


7

Я рекомендую функцию генератора с эффективным использованием пространства :

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]

Мне нравится этот подход. Аналогичен stackoverflow.com/a/35073573/1175496 , но использует оператор распространения ...для перебора генератора.
Красный горох

6

Я бы предпочел преобразовать весь массив, как есть, в строку, но, в отличие от других ответов, сделал бы это, используя, 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)

  • Только для многомерного массива строк / чисел (не объектов)

Ваше решение неверно. Он будет содержать запятую при выравнивании внутренних массивов ["345", "2", "3,4", "2"]вместо разделения каждого из этих значений на отдельные индексы
pizzarob

@realseanp - вы неправильно поняли значение этого элемента массива. Я намеренно ставлю эту запятую в качестве значения, а не запятой в качестве разделителя массива, чтобы подчеркнуть силу моего решения над всеми остальными, которые будут выводиться "3,4".
vsync

1
Я действительно неправильно понял
pizzarob

это определенно самое быстрое решение, которое я видел для этого; Знаете ли вы о каких-либо подводных камнях @vsync (за исключением того факта, что это выглядит немного странно - конечно, рассматривая вложенные массивы как строки: D)
Джордж Кацанос

@GeorgeKatsanos - этот метод не будет работать для элементов массива (и вложенных элементов), которые не имеют примитивного значения, например, элемента, который указывает на элемент DOM
vsync

6

Вы также можете попробовать новый 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 на более старую версию. Проверьте наличие MD-документов в Интернете на предмет текущей совместимости браузера.


2
для плоских массивов более высокой размерности вы можете просто вызвать плоский метод с Infinityаргументом. Как это:arr.flat(Infinity)
Михаил

Это очень хорошее дополнение, спасибо Михаил!
Виллем ван дер Веен

6

Используя оператор распространения:

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


5

Это не сложно, просто переберите массивы и объедините их:

var result = [], input = [["$6"], ["$12"], ["$25"], ["$25"], ["$18"]];

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

5

Похоже, это похоже на работу для рекурсии!

  • Обрабатывает несколько уровней вложенности
  • Обрабатывает пустые массивы и не массив параметров
  • Не имеет мутации
  • Не полагается на современные функции браузера

Код:

var flatten = function(toFlatten) {
  var isArray = Object.prototype.toString.call(toFlatten) === '[object Array]';

  if (isArray && toFlatten.length > 0) {
    var head = toFlatten[0];
    var tail = toFlatten.slice(1);

    return flatten(head).concat(flatten(tail));
  } else {
    return [].concat(toFlatten);
  }
};

Применение:

flatten([1,[2,3],4,[[5,6],7]]);
// Result: [1, 2, 3, 4, 5, 6, 7] 

осторожно, flatten(new Array(15000).fill([1]))выкидывает Uncaught RangeError: Maximum call stack size exceededи замораживает мои devTools на 10 секунд
pietrovismara

@pietrovismara, мой тест около 1 с.
Иван Ян

5

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

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

1
Простой и приятный, этот ответ работает лучше, чем принятый ответ. Это выравнивает глубоко вложенные уровни, а не только первый уровень
Ом Шанкар

1
AFAIK, который является лексическим ограничением и не закрытием
dashambles

@dashambles верна - разница в том, что если бы это было замыкание, вы бы вернули внутреннюю функцию наружу, а когда внешняя функция завершится, вы все равно можете использовать внутреннюю функцию для доступа к ее области действия. Здесь время жизни внешней функции больше, чем у внутренней функции, поэтому «замыкание» никогда не создается.
Мёрре

5

На днях я баловался с 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 *.

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


1
Аналогичный ответ (с использованием генератора и делегирования выхода), но в формате PHP
The Red Pea

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.