Об'єднати / вирівняти масив масивів


1104

У мене є масив JavaScript на зразок:

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

Як би я міг об'єднати окремі внутрішні масиви в один такий:

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

gist.github.com/Nishchit14/4c6a7349b3c778f7f97b912629a9f228 Це посилання описує ES5 & ES6 flatten
Nishchit Dhanani

17
Усі рішення, які використовують reduce+, - concatце O ((N ^ 2) / 2), де як прийнята відповідь (лише один дзвінок на concat) було б не більше O (N * 2) на поганому браузері, а O (N) на a хороший. Також рішення Denys оптимізовано під власне питання і до 2 разів швидше, ніж сингл concat. Для reduceлюдей цікаво почувати себе при написанні крихітного коду, але, наприклад, якби масив мав 1000 один елемент підматриці, всі рішення для зменшення + стислість виконували б 500500 операцій, коли як один конкомат, так і простий цикл буде робити 1000 операцій.
gman

12
Просто оператор розповсюдження користувачів[].concat(...array)
Олег Датер

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

3
З останніми браузерами, які підтримують 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.
Фрогз

209
Далі до коментаря @ Sean: Синтаксис 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
Який профіль використання пам'яті для цього рішення? Схоже, це створює багато проміжних масивів під час хвостової рекурсії ....
JBRWilkinson

7
@ayjay, це початкове значення акумулятора для функції зменшення, що mdn називає початковим значенням. У цьому випадку це значення flatв першому дзвінку до переданої анонімній функції reduce. Якщо це не вказано, то перший виклик reduceприв'язує перше значення з масиву до flat, що в кінцевому підсумку призведе до 1прив’язки до flatобох прикладів. 1.concatне є функцією.
Ной Фрейтас

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

12
Рифів на @TsvetomirTsonev і Ноа рішень для довільної вкладеності: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]]...)
амеба

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 Насправді зменшіть канали на набір одного значення (масиву) за оборот. Доказ? вийміть зменшити () і подивіться, чи працює він. зменшити () тут є альтернатива Function.prototype.apply ()
Андре Верланг

3
Я б використав також зменшення, але одне цікаве, що я дізнався з цього фрагмента, - це те, що більшу частину часу вам не потрібно пройти початкову оцінку :)
Хосе Ф. Романьєлло,

2
Проблема зменшення полягає в тому, що масив не може бути порожнім, тому вам потрібно буде додати додаткову перевірку.
calbertts

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

7
Оскільки ви використовуєте ES6, ви також можете використовувати оператор спред як буквений масив . arrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San

108

Існує новий власний метод, який називається плоский, щоб зробити це саме.

(Станом на кінець 2019 року, flatтепер опубліковано у стандарті ECMA 2019, і core-js@3(бібліотека бабеля) включає його у свою бібліотеку для полів заповнення )

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 (і у Вузлі 11 для тих, хто працює в бекенде)
Метт М.


2
-1; ні, це не є частиною ECMAScript 2018 . Це все ще лише пропозиція, яка не внесла його до жодної специфікації ECMAScript.
Марк Амері

1
Я думаю, зараз ми можемо це врахувати .. тому що зараз це частина стандарту (2019) .. чи можемо ми переглянути його частину продуктивності колись?
Yuvaraj

Здається, він ще не підтримується жодним браузером 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 зараз немає, тому жоден рекурсивний алгоритм не буде добре працювати.
пристойно

@nitely Але в якій ситуації в реальному світі ви мали б масиви з більш ніж кількома рівнями гніздування?
Michał Perłakowski

2
Зазвичай, коли масив генерується із вмісту, створеного користувачем.
примхливо

@ 0xcaff У Chrome він зовсім не працює з 200 000-елементним масивом (ви отримуєте RangeError: Maximum call stack size exceeded). Для масиву 20 000 елементів потрібно 2-5 мілісекунд.
Michał Perłakowski

3
у чому полягає складність цього позначення O?
Джордж Кацанос

58

Оновлення: виявилося, що це рішення не працює з великими масивами. Ви шукаєте кращого, швидшого рішення, ознайомтеся з цією відповіддю .


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.


Оскільки майже всі відповіді тут неправильно використовуються apply, я видалив свої коментарі з ваших. Я все ще думаю, що використовувати apply/ поширювати цей спосіб погано радимо, але так як ніхто не переймається ...

@ LUH3417 Це не так, я дуже ціную ваші коментарі. Виявилось, ви праві - це рішення справді не працює з великими масивами. Я розмістив ще одну відповідь, яка добре працює навіть з масивами 200 000 елементів.
Michał Perłakowski

Якщо ви використовуєте ES6, ви можете зменшити подальше значення:const flatten = arr => [].concat(...arr)
Eruant

Що ви маєте на увазі "не працює з великими масивами"? Як великі? Що сталося?
GEMI

4
@GEMI Наприклад, спроба вирівняти масив 500000 елементів за допомогою цього методу дає "RangeError: Максимальний розмір стека викликів перевищений".
Michał Perłakowski

51

Ви можете використовувати підкреслення :

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

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

20
Хто-небудь, хтось додав Perl до JS? :-)
JBRWilkinson

9
@JBRWilkinson Можливо, JS хоче навчитися вам Haskell для великого блага !?
стайф

1+ - Ви також можете вказати, що потрібно дрібний сплющений масив , вказавши trueдля другого аргументу .
Джош Крозьє

7
Заради повноти щодо коментаря @ styfle: learnnyouahaskell.com
Simon A. Eugster

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наступного дзвінка .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 ви не повинні відчувати себе жахливо. Ви також даєте хороші відповіді з добре написаними поясненнями. Тут все є досвід навчання - я часто помиляюся і пишу погані відповіді. Я переглядаю їх через пару місяців і думаю "wtf я думав?" і врешті решт переглянути речі. Жодна відповідь не ідеальна. Ми всі в якийсь момент на кривій навчання ^ _ ^
Дякую

1
Зауважте, що concatу Javascript є інше значення, ніж у Haskell. Haskell's concat( [[a]] -> [a]) буде викликатись flattenу Javascript та реалізується як foldr (++) [](Javascript: foldr(concat) ([])припускаючи криві функції). Javascript's 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

Як щодо використання 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), доступну в останніх випусках сучасних браузерів. Завдяки @ (Константин Ван) та @ (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) );


3
Це добре і акуратно, але я думаю, ви передозували ES6. Не потрібно, щоб зовнішня функція була функцією зі стрілкою. Я б дотримувався стрілочної функції для зменшення зворотного дзвінка, але вирівнювання самої повинно бути нормальною функцією.
Стівен Сімпсон

3
@StephenSimpson, але чи є необхідність, щоб зовнішня функція була не- стрілою? "згладити себе повинно бути нормальною функцією" - під "нормальним" ви маєте на увазі "не стрілка", але чому? Навіщо тоді використовувати функцію стрілки у виклику для зменшення? Чи можете ви надати свої міркування?
Дякую

@naomik Мої міркування - це непотрібність. В основному це питання стилю; Я мав би бути набагато чіткішим у своєму коментарі. Немає основної причини кодування для використання того чи іншого. Однак функцію легше бачити та читати як не стрілку. Внутрішня функція корисна як функція стрілки, оскільки вона є більш компактною (і, звичайно, не створюється контекст). Функції зі стрілками чудово підходять для створення компактної функції, що легко читається, та уникнення цієї плутанини. Однак вони насправді можуть ускладнити читання, коли не вистачить стрілки. Інші ж можуть не погодитися!
Стівен Сімпсон

ОтриманняRangeError: Maximum call stack size exceeded
Метт Вестлейк

@Matt, будь ласка, поділіться evnironment, який ви використовуєте для відтворення помилки
diziaq

28

Для вирівнювання масиву одноелементних масивів вам не потрібно імпортувати бібліотеку, простий цикл є і найпростішим, і найефективнішим рішенням:

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

Для людей, що переживають низку супроводу: прочитайте запитання, не зверніть увагу, оскільки воно не відповідає вашій зовсім іншій проблемі. Це рішення є і найшвидшим і найпростішим для заданого питання.


4
Я б сказав: "Не варто шукати речі більш кричущі". ^^

4
Я маю на увазі ... коли я бачу людей, які радять використовувати для цього бібліотеку ... це божевільно ...
Denys Séguret

3
Ах, використовувати бібліотеку саме для цього було б нерозумно ... але якщо вона вже є.

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

4
Насправді не важливо, наскільки це криптично. Цей код «згладжує» це ['foo', ['bar']]в ['f', 'bar'].
jonschlinkert

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

@ MichałPerłakowski Якщо вам потрібно використовувати їх у декількох місцях, тоді не винаходити колесо та вибирати пакет із них - документально підтверджений та підтримуваний іншими людьми.
Daerdemandt

12

ES6 Однорядне вирівнювання

Дивіться lodash flatten , підкреслення flatten (неглибокий 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 , підкреслення вирівнювання

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функції. Виконання може або не може оптимізувати його під час його запуску, але доступ до функції на прототипі не виглядає більш химерним, ніж це вже є в будь-якому випадку.
Mörre

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. Вручає найелегантніше рішення в цілій нитці.
Nicolás Fantone

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
@Anonymous Я не заявив, що технічно відповідає вимогам питання, але це, мабуть, тому, що це досить погане рішення, яке не є корисним у загальному випадку. З огляду на те, скільки тут є кращих рішень, я б ніколи не рекомендував комусь перейти з цим, оскільки він порушує момент, коли у вас є більше одного елемента, або коли вони не є рядками.
Thor84no

2
Я люблю це рішення =)
Huei Tan

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(',')
Sudhansu Choudhary

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 (за винятком факту, це, звичайно, виглядає трохи
хакітно

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

6

Ви також можете спробувати новий Array.Flat()метод. Це працює наступним чином:

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

console.log(arr);

The flat()Метод створює новий масив з усіма елементами суб-масиву об'єднуються в нього рекурсивно до 1 шару глибини (тобто масиви всередині масивів)

Якщо ви також хочете вирівняти тривимірні або навіть більш високі розмірні масиви, ви просто викликаєте плоский метод кілька разів. Наприклад (3 розміри):

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

console.log(arr);

Будь обережний!

Array.Flat()метод порівняно новий. Старіші веб-переглядачі, такі як, можливо, не реалізували метод. Якщо ви хочете, щоб код працював у всіх браузерах, можливо, доведеться перевести 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і заморожує мою розвідку на 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 вірна - різниця полягає в тому, що якщо б це було закриття, ви повернете внутрішню функцію назовні, і коли зовнішня функція буде завершена, ви все одно можете використовувати внутрішню функцію для доступу до її сфери. Тут термін служби зовнішньої функції довший, ніж у внутрішньої функції, тому "закриття" ніколи не створюється.
Mörre

5

Днями я гуляв з генераторами 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);

В основному я створюю генератор, який перетинає вихідний вхідний масив, якщо він знаходить масив, він використовує оператор урожайності * у поєднанні з рекурсією для постійного сплощення внутрішніх масивів. Якщо елемент не є масивом, він дає лише один елемент. Потім за допомогою оператора ES6 Spread (він же оператор splat) я вирівнюю генератор у новий екземпляр масиву.

Я не перевіряв продуктивність цього, але вважаю, що це приємний простий приклад використання генераторів та оператора * віддачі.

Але знову ж таки, я просто гнукав, тому впевнений, що є більш вдалі способи зробити це.


1
Аналогічна відповідь (за допомогою генератора та делегування врожаю), але у форматі PHP
Червоний горох

5

просто найкраще рішення без лодашу

let flatten = arr => [].concat.apply([], arr.map(item => Array.isArray(item) ? flatten(item) : item))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.