Fusionner / aplatir un tableau de tableaux

1105

J'ai un tableau JavaScript comme:

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

Comment pourrais-je fusionner les tableaux internes séparés en un seul comme:

["$6", "$12", "$25", ...]
Andy
la source
gist.github.com/Nishchit14/4c6a7349b3c778f7f97b912629a9f228 Ce lien décrit les ES5 et ES6 aplatis
Nishchit Dhanani
18
Toutes les solutions qui utilisent reduce+ concatsont O ((N ^ 2) / 2) où comme réponse acceptée (un seul appel à concat) serait au plus O (N * 2) sur un mauvais navigateur et O (N) sur un bon. La solution Denys est également optimisée pour la question réelle et jusqu'à 2 fois plus rapide que la seule concat. Pour les reducegens, c'est amusant de se sentir cool d'écrire du code minuscule, mais par exemple, si le tableau avait 1000 sous-tableaux à un élément, toutes les solutions de réduction + concat feraient 500500 opérations alors que la simple concat ou boucle simple ferait 1000 opérations.
gman
12
Opérateur de diffusion simplement par l'utilisateur[].concat(...array)
Oleg Dater
@gman comment est la solution de réduction + concaturation O ((N ^ 2) / 2)? stackoverflow.com/questions/52752666/… mentionne une complexité différente.
Usman
4
Avec les derniers navigateurs prenant en charge ES2019 : array.flat(Infinity)Infinityest la profondeur maximale à aplatir.
Timothy Gu

Réponses:

1860

Vous pouvez utiliser concatpour fusionner des tableaux:

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

console.log(merged);

L'utilisation de la applyméthode de concatprendra simplement le deuxième paramètre comme un tableau, donc la dernière ligne est identique à ceci:

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

Il existe également la Array.prototype.flat()méthode (introduite dans ES2019) que vous pouvez utiliser pour aplatir les tableaux, bien qu'elle ne soit disponible que dans Node.js à partir de la version 11, et pas du tout dans 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);
    

Gombo
la source
10
Notez que concatcela ne modifie pas le tableau source, donc le mergedtableau restera vide après l'appel à concat. Mieux vaut dire quelque chose comme:merged = merged.concat.apply(merged, arrays);
Nate
65
var merged = [].concat.apply([], arrays);semble bien fonctionner pour le mettre sur une seule ligne. edit: comme le montre déjà la réponse de Nikita.
Sean
44
Ou Array.prototype.concat.apply([], arrays).
danhbear
30
Remarque: cette réponse n'aplatit que d'un niveau. Pour un aplatissement récursif, voir la réponse de @Trindaz.
Phrogz
209
Suite au commentaire de @ Sean: la syntaxe ES6 rend ce super concis:var merged = [].concat(...arrays)
Sethi
505

Voici une fonction courte qui utilise certaines des nouvelles méthodes de tableau JavaScript pour aplatir un tableau à n dimensions.

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

Usage:

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]
Noah Freitas
la source
17
J'aime cette approche. C'est beaucoup plus général et prend en charge les tableaux imbriqués
alfredocambera
10
Quel est le profil d'utilisation de la mémoire pour cette solution? On dirait que cela crée beaucoup de tableaux intermédiaires pendant la récursivité de la queue ....
JBRWilkinson
7
@ayjay, c'est la valeur de départ de l'accumulateur pour la fonction de réduction, ce que mdn appelle la valeur initiale. Dans ce cas, c'est la valeur de flatlors du premier appel à la fonction anonyme passée à reduce. S'il n'est pas spécifié, le premier appel à reducelie la première valeur du tableau à flat, ce qui entraînerait éventuellement la 1liaison flatdans les deux exemples. 1.concatn'est pas une fonction.
Noah Freitas
19
Ou sous une forme plus courte et plus sexy: const flatten = (arr) => arr.reduce((flat, next) => flat.concat(next), []);
Tsvetomir Tsonev
12
Riffing sur les solutions de @TsvetomirTsonev et Noah pour l'imbrication arbitraire:const flatten = (arr) => arr.reduce((flat, next) => flat.concat(Array.isArray(next) ? flatten(next) : next), []);
Will
314

Il existe une méthode cachée, qui crée un nouveau tableau sans muter l'original:

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

Nikita Volkov
la source
11
Dans CoffeeScript, c'est[].concat([[1],[2,3],[4]]...)
amoebe
8
@amoebe votre réponse donne [[1],[2,3],[4]]comme résultat. La solution proposée par @Nikita est correcte pour CoffeeScript ainsi que pour JS.
Ryan Kennedy
5
Ahh, j'ai trouvé ton erreur. Vous avez une paire supplémentaire de crochets dans votre notation, devrait l'être [].concat([1],[2,3],[4],...).
Ryan Kennedy
21
Je pense que j'ai aussi trouvé votre erreur. Ce ...sont du code réel, pas des points de suspension.
amoebe
3
L'utilisation d'une fonction de bibliothèque ne signifie pas qu'il y ait un "désordre" moins impératif. Juste que le désordre est caché dans une bibliothèque. Évidemment, utiliser une bibliothèque est mieux que de rouler votre propre fonction, mais prétendre que cette technique réduit le "désordre impératif" est assez idiot.
paldepind
204

Il peut être mieux fait par la fonction de réduction 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);
}, []);

Ou, avec ES2015:

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

js-fiddle

Documents Mozilla

user2668376
la source
1
@JohnS Réduisez en fait les flux sur la concaténation d'une seule valeur (tableau) par tour. Preuve? sortir réduire () et voir si cela fonctionne. réduire () voici une alternative à Function.prototype.apply ()
André Werlang
3
J'aurais également utilisé la réduction, mais une chose intéressante que j'ai apprise de cet extrait est que la plupart du temps, vous n'avez pas besoin de passer une valeur initiale :)
José F. Romaniello
2
Le problème avec réduire est que le tableau ne peut pas être vide, vous devrez donc ajouter une validation supplémentaire.
calbertts
4
@calbertts Passez juste la valeur initiale []et aucune autre validation n'est nécessaire.
8
Puisque vous utilisez ES6, vous pouvez également utiliser l'opérateur spread comme littéral de tableau . arrays.reduce((flatten, arr) => [...flatten, ...arr])
Putzi San
109

Il existe une nouvelle méthode native appelée flat pour faire exactement cela.

(À la fin de 2019, flatest maintenant publié dans la norme ECMA 2019 et core-js@3(la bibliothèque de babel) l'inclut dans leur bibliothèque 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];
Alister
la source
4
C'est dommage que ce ne soit même pas sur la première page de réponses. Cette fonctionnalité est disponible dans Chrome 69 et Firefox 62 (et Node 11 pour ceux qui travaillent dans le backend)
Matt M.
2
-1; non, cela ne fait pas partie d' ECMAScript 2018 . Ce n'est encore qu'une proposition qui n'a fait l'objet d'aucune spécification ECMAScript.
Mark Amery
1
Je pense que maintenant nous pouvons considérer cela .. parce que maintenant cela fait partie de la norme (2019) .. pouvons-nous revoir la partie performance de cela une fois?
Yuvaraj
Il semble que ce ne soit pas encore pris en charge par un navigateur Microsoft (au moins au moment où j'écris ce commentaire)
Laurent S.
78

La plupart des réponses ici ne fonctionnent pas sur des tableaux énormes (par exemple 200 000 éléments), et même s'ils le font, ils sont lents. La réponse de polkovnikov.ph a les meilleures performances, mais elle ne fonctionne pas pour l'aplatissement en profondeur.

Voici la solution la plus rapide, qui fonctionne également sur des tableaux avec plusieurs niveaux d'imbrication :

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

Exemples

D'énormes tableaux

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

Il gère très bien les énormes tableaux. Sur ma machine, ce code prend environ 14 ms à exécuter.

Tableaux imbriqués

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

Il fonctionne avec des tableaux imbriqués. Ce code produit [1, 1, 1, 1, 1, 1, 1, 1].

Tableaux avec différents niveaux d'imbrication

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

Il n'a aucun problème avec les tableaux d'aplatissement comme celui-ci.

Michał Perłakowski
la source
Sauf que votre immense réseau est assez plat. Cette solution ne fonctionnera pas pour les tableaux profondément imbriqués. Aucune solution récursive ne le fera. En fait, pas de navigateur, mais Safari a un TCO en ce moment, donc aucun algorithme récursif ne fonctionnera bien.
nitely
@nitely Mais dans quelle situation réelle auriez-vous des tableaux avec plus de quelques niveaux d'imbrication?
Michał Perłakowski
2
Habituellement, lorsque le tableau est généré à partir de contenu généré par l'utilisateur.
nitely
@ 0xcaff Dans Chrome, cela ne fonctionne pas du tout avec un tableau de 200 000 éléments (vous obtenez RangeError: Maximum call stack size exceeded). Pour un tableau de 20 000 éléments, cela prend 2 à 5 millisecondes.
Michał Perłakowski
3
quelle est la complexité de la notation O de cela?
George Katsanos
58

Mise à jour: il s'est avéré que cette solution ne fonctionne pas avec les grands tableaux. Si vous cherchez une solution meilleure et plus rapide, consultez cette réponse .


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

Is se développe simplement arret le passe comme arguments à concat(), ce qui fusionne tous les tableaux en un seul. C'est équivalent à [].concat.apply([], arr).

Vous pouvez également essayer ceci pour un aplatissement profond:

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

Voir la démo sur JSBin .

Références pour les éléments ECMAScript 6 utilisés dans cette réponse:


Remarque: les méthodes telles find()que les fonctions de flèche et ne sont pas prises en charge par tous les navigateurs, mais cela ne signifie pas que vous ne pouvez pas utiliser ces fonctionnalités pour le moment. Utilisez simplement Babel - il transforme le code ES6 en ES5.

Michał Perłakowski
la source
Parce que presque toutes les réponses ici abusent applyde cette manière, j'ai supprimé mes commentaires des vôtres. Je pense toujours que l'utilisation apply/ la diffusion de cette façon est un mauvais conseil, mais comme personne ne s'en soucie ...
@ LUH3417 Ce n'est pas comme ça, j'apprécie vraiment vos commentaires. Il s'est avéré que vous aviez raison - cette solution ne fonctionne pas avec les grandes baies. J'ai posté une autre réponse qui fonctionne bien même avec des tableaux de 200 000 éléments.
Michał Perłakowski
Si vous utilisez ES6, vous pouvez réduire davantage à:const flatten = arr => [].concat(...arr)
Eruant
Que voulez-vous dire "ne fonctionne pas avec de grands tableaux"? Large comment? Ce qui se produit?
GEMI
4
@GEMI Par exemple, essayer d'aplatir un tableau de 500 000 éléments en utilisant cette méthode donne "RangeError: dépassement de la taille maximale de la pile d'appels".
Michał Perłakowski
51

Vous pouvez utiliser Underscore :

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

_.flatten(x); // => [1, 2, 3, 4]
Todd Yandell
la source
20
Whoah, quelqu'un a-t-il ajouté Perl à JS? :-)
JBRWilkinson
9
@JBRWilkinson Peut-être que JS veut vous apprendre un Haskell pour le bien !?
styfle
1+ - Vous pouvez également spécifier que vous voulez un tableau aplati peu profond en spécifiant truepour le deuxième argument .
Josh Crozier
7
Par souci d'exhaustivité concernant le commentaire de @ styfle: Learnyouahaskell.com
Simon A. Eugster
41

Les procédures génériques signifient que nous n'avons pas à réécrire la complexité chaque fois que nous devons utiliser un comportement spécifique.

concatMap(ou flatMap) est exactement ce dont nous avons besoin dans cette situation.

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

prévoyance

Et oui, vous l'avez deviné correctement, cela n'aplatit qu'un niveau, ce qui est exactement comme cela devrait fonctionner

Imaginez un ensemble de données comme celui-ci

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

Ok, disons maintenant que nous voulons imprimer une liste qui montre tous les joueurs qui participeront à game

const gamePlayers = game =>
  flatten (game)

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

Si notre flattenprocédure aplatissait également les tableaux imbriqués, nous finirions avec ce résultat poubelle…

const gamePlayers = game =>
  badGenericFlatten(game)

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

rollin 'deep, baby

Cela ne veut pas dire que parfois vous ne voulez pas non plus aplatir les tableaux imbriqués - seulement cela ne devrait pas être le comportement par défaut.

Nous pouvons faire une deepFlattenprocédure en toute simplicité…

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

Là. Vous disposez maintenant d'un outil pour chaque tâche - un pour écraser un niveau d'imbrication flattenet un pour effacer toute imbrication deepFlatten.

Vous pouvez peut-être l'appeler obliterateou nukesi vous n'aimez pas le nom deepFlatten.


Ne répétez pas deux fois!

Bien sûr, les implémentations ci-dessus sont intelligentes et concises, mais en utilisant un .mapsuivi d'un appel à .reducesignifie que nous faisons en fait plus d'itérations que nécessaire

L'utilisation d'un combinateur fiable que j'appelle mapReduceaide à garder les itérations à un minimum; il prend une fonction de cartographie m :: a -> b, une fonction de réduction r :: (b,a) ->bet renvoie une nouvelle fonction de réduction - ce combinateur est au cœur des transducteurs ; si vous êtes intéressé, j'ai écrit d'autres réponses à leur sujet

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

Je vous remercie
la source
Souvent, quand je vois vos réponses, je veux retirer les miennes, car elles sont devenues sans valeur. Très bonne réponse! concatlui-même ne fait pas exploser la pile, ...et le applyfait (avec de très grands tableaux). Je ne l'ai pas vu. Je me sens juste mal en ce moment.
@ LUH3417, vous ne devriez pas vous sentir mal. Vous fournissez également de bonnes réponses avec des explications bien écrites. Tout ici est une expérience d'apprentissage - je fais souvent des erreurs et j'écris aussi de mauvaises réponses. Je les revisite dans quelques mois et pense "wtf pensais-je?" et finir par réviser complètement les choses. Aucune réponse n'est parfaite. Nous sommes tous à un moment donné sur une courbe d'apprentissage ^ _ ^
Merci
1
Veuillez noter que concatJavascript a une signification différente de celle de Haskell. Haskell concat( [[a]] -> [a]) serait appelé flattenen Javascript et est implémenté comme foldr (++) [](Javascript: en foldr(concat) ([])supposant des fonctions curry). Javascript concatest un appendice étrange ( (++)en Haskell), qui peut gérer à la fois [a] -> [a] -> [a]et a -> [a] -> [a].
Je suppose qu'un meilleur nom était flatMap, car c'est exactement ce qui concatMapest: l' bindinstance de la listmonade. concatpMapest implémenté en tant que foldr ((++) . f) []. Traduit en Javascript: const flatMap = f => foldr(comp(concat) (f)) ([]). Ceci est bien sûr similaire à votre implémentation sans comp.
quelle est la complexité de cet algorithme?
George Katsanos
32

Une solution pour le cas plus général, lorsque vous pouvez avoir des éléments non-tableau dans votre tableau.

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;
}
Trindaz
la source
4
Cette approche a été très efficace pour aplatir la forme de tableau imbriqué des jeux de résultats que vous obtenez à partir d'une requête JsonPath .
kevinjansz
1
Ajouté comme méthode de tableaux: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
Cela se cassera si nous passons manuellement le deuxième argument. Par exemple, essayez ceci: flattenArrayOfArrays (arr, 10)ou ceci flattenArrayOfArrays(arr, [1,[3]]);- ces seconds arguments sont ajoutés à la sortie.
Om Shankar
2
cette réponse n'est pas complète! le résultat de la récursivité n'est jamais attribué nulle part, donc le résultat des récursions est perdu
r3wt
1
@ r3wt est allé de l'avant et a ajouté un correctif. Ce que vous devez faire est de vous assurer que le tableau actuel que vous maintenez, rconcaténera réellement les résultats de la récursivité.
août
29

Pour aplatir un tableau de tableaux à élément unique, vous n'avez pas besoin d'importer une bibliothèque, une simple boucle est à la fois la solution la plus simple et la plus efficace :

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

Aux downvoters: veuillez lire la question, ne downvote pas car elle ne convient pas à votre problème très différent. Cette solution est à la fois la plus rapide et la plus simple pour la question posée.

Denys Séguret
la source
4
Je dirais: "Ne cherchez pas des choses plus cryptiques." ^^
4
Je veux dire ... quand je vois des gens conseiller d'utiliser une bibliothèque pour ça ... c'est dingue ...
Denys Séguret
3
Ahh, utiliser une bibliothèque juste pour ça serait idiot ... mais si elle est déjà disponible.
9
@dystroy La partie conditionnelle de la boucle for n'est pas si lisible. Ou est-ce juste moi: D
Andreas
4
Peu importe à quel point c'est cryptique. Ce code « aplatit » ce ['foo', ['bar']]à ['f', 'bar'].
jonschlinkert
29

Qu'en est-il de l'utilisation de la reduce(callback[, initialValue])méthode deJavaScript 1.8

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

Ferait le travail.

rab
la source
8
[[1], [2,3]].reduce( (a,b) => a.concat(b), [] )est plus sexy.
golopot
5
Pas besoin d'un deuxième argument dans notre cas simple[[1], [2,3]].reduce( (a,b) => a.concat(b))
Umair Ahmed
29

Une autre solution ECMAScript 6 au style fonctionnel:

Déclarez une fonction:

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

et l'utiliser:

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

Considérez également une fonction native Array.prototype.flat () (proposition pour ES6) disponible dans les dernières versions des navigateurs modernes. Merci à @ (Константин Ван) et @ (Mark Amery) l'ont mentionné dans les commentaires.

La flatfonction possède un paramètre, spécifiant la profondeur attendue d'imbrication des tableaux, qui est égale 1par défaut.

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

diziaq
la source
3
C'est sympa et soigné mais je pense que vous avez fait une overdose ES6. Il n'est pas nécessaire que la fonction externe soit une fonction flèche. Je m'en tiendrai à la fonction flèche pour réduire le rappel, mais l'aplatissement devrait être une fonction normale.
Stephen Simpson
3
@StephenSimpson, mais la fonction extérieure doit-elle être une fonction non étroite? "s'aplatir lui-même devrait être une fonction normale" - par "normal" vous voulez dire "sans flèche", mais pourquoi? Pourquoi utiliser une fonction flèche dans l'appel pour réduire ensuite? Pouvez-vous fournir votre raisonnement?
Merci
@naomik Mon raisonnement est que ce n'est pas nécessaire. C'est principalement une question de style; J'aurais dû être beaucoup plus clair dans mon commentaire. Il n'y a aucune raison majeure de codage d'utiliser l'un ou l'autre. Cependant, la fonction est plus facile à voir et à lire comme non fléchée. La fonction interne est utile comme fonction de flèche car elle est plus compacte (et aucun contexte n'est créé bien sûr). Les fonctions fléchées sont idéales pour créer une fonction compacte facile à lire et éviter cette confusion. Cependant, ils peuvent en fait rendre la lecture plus difficile lorsqu'une non-flèche suffit. D'autres peuvent être en désaccord cependant!
Stephen Simpson
Obtenir unRangeError: Maximum call stack size exceeded
Matt Westlake
@Matt, veuillez partager l'environnement que vous utilisez pour reproduire l'erreur
diziaq
22
const common = arr.reduce((a, b) => [...a, ...b], [])
YairTawil
la source
Voici comment je le fais tout au long de mon code, mais je ne sais pas comment la vitesse se compare à la réponse acceptée si vous avez un très long tableau
Michael Aaron Wilson
14

Veuillez noter: quand Function.prototype.apply( [].concat.apply([], arrays)) ou l'opérateur d'étalement ([].concat(...arrays) ) est utilisé pour aplatir un tableau, les deux peuvent provoquer des débordements de pile pour les grands tableaux, car chaque argument d'une fonction est stocké sur la pile.

Voici une implémentation empilable dans un style fonctionnel qui compare les exigences les plus importantes les unes aux autres:

  • réutilisabilité
  • lisibilité
  • concision
  • performance

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

Dès que vous vous habituez aux petites fonctions flèches au curry, à la composition des fonctions et aux fonctions d'ordre supérieur, ce code se lit comme de la prose. La programmation consiste alors simplement à assembler de petits blocs de construction qui fonctionnent toujours comme prévu, car ils ne contiennent aucun effet secondaire.


la source
1
Haha. Respectez totalement votre réponse, bien que lire une programmation fonctionnelle comme celle-ci soit toujours comme pour moi lire un caractère japonais (anglophone).
Tarwin Stroh-Spijer
2
Si vous vous retrouvez à implémenter des fonctionnalités de la langue A dans la langue B pas dans le cadre d'un projet dans le seul but de faire exactement cela, alors quelqu'un quelque part a pris un mauvais virage. Serait-ce toi? Aller simplement avec const flatten = (arr) => arr.reduce((a, b) => a.concat(b), []);vous permet d' économiser des déchets visuels et d' expliquer à vos coéquipiers pourquoi vous avez besoin de 3 fonctions supplémentaires et de certains appels de fonction.
Daerdemandt
3
@Daerdemandt Mais si vous l'écrivez en tant que fonctions distinctes, vous pourrez probablement les réutiliser dans un autre code.
Michał Perłakowski
@ MichałPerłakowski Si vous devez les utiliser à plusieurs endroits, ne réinventez pas la roue et choisissez un package parmi ceux - ci - documenté et pris en charge par d'autres personnes.
Daerdemandt
12

ES6 One Line Flatten

Voir lodash aplatir , souligner aplatir (peu profond true)

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

ou

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

Testé avec

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

Voir lodash aplatir Profond , souligner aplatir

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

Testé avec

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]);
});
zurfyx
la source
Votre 2ème exemple est mieux écrit Array.prototype.concat.apply([], arr)car vous créez un tableau supplémentaire juste pour accéder à la concatfonction. Les runtime peuvent ou non l'optimiser lors de leur exécution, mais accéder à la fonction sur le prototype ne semble pas plus laid que cela ne l'est déjà dans tous les cas.
Mörre
11

Vous pouvez utiliser Array.flat()avec Infinitypour n'importe quelle profondeur de tableau imbriqué.

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)

vérifiez ici la compatibilité du navigateur

Code Maniac
la source
8

Une approche Haskellesque

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

Redu
la source
1
D'accord avec @monners. De loin la solution la plus élégante de tout ce fil.
Nicolás Fantone
8

Manière 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))

Méthode ES5 pour la flattenfonction avec secours ES3 pour les tableaux imbriqués N fois:

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

Artem Gavrysh
la source
7

Si vous n'avez que des tableaux avec 1 élément de chaîne:

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

fera le travail. Bt qui correspond spécifiquement à votre exemple de code.

Florian Salihovic
la source
3
Celui qui a voté, veuillez expliquer pourquoi. Je cherchais une solution décente et de toutes les solutions j'ai préféré celle-ci.
Anonyme
2
@Anonymous Je ne l'ai pas downvote car il répond techniquement aux exigences de la question, mais c'est probablement parce que c'est une solution assez médiocre qui n'est pas utile dans le cas général. Compte tenu du nombre de meilleures solutions disponibles, je ne recommanderais jamais à quelqu'un d'aller avec celui-ci car il casse le moment où vous avez plus d'un élément, ou lorsqu'il ne s'agit pas de chaînes.
Thor84no
2
J'adore cette solution =)
Huei Tan
2
Il ne gère pas seulement les tableaux avec 1 élément de chaîne, il gère également ce tableau ['$4', ["$6"], ["$12"], ["$25"], ["$25", "$33", ['$45']]].join(',').split(',')
alucic
J'ai découvert cette méthode par moi-même, mais je savais qu'elle devait déjà être documentée quelque part, ma recherche s'est terminée ici. L'inconvénient de cette solution est qu'elle contraint les nombres, les booléens, etc. aux chaînes, essayez [1,4, [45, 't', ['e3', 6]]].toString().split(',') ---- ou ----- [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"]

(J'écris simplement ceci comme une réponse distincte, basée sur le commentaire de @danhbear.)

VasiliNovikov
la source
7

Je recommande une fonction de générateur peu encombrante :

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

Si vous le souhaitez, créez un tableau de valeurs aplaties comme suit:

let flattened = [...flatten([1,[2,[3,[4]]]])]; // [1, 2, 3, 4]
le_m
la source
J'aime cette approche. Similaire à stackoverflow.com/a/35073573/1175496 , mais utilise l'opérateur de propagation ...pour parcourir le générateur.
The Red Pea
6

Je préfère transformer l'ensemble du tableau, tel quel , en une chaîne, mais contrairement à d'autres réponses, je ferais cela en utilisant JSON.stringifyet non en utilisant la toString()méthode, qui produit un résultat indésirable.

Avec cette JSON.stringifysortie, tout ce qui reste est de supprimer tous les crochets, d'envelopper le résultat avec les crochets de début et de fin encore une fois et de servir le résultat avec JSON.parselequel la chaîne revient à la "vie".

  • Peut gérer des tableaux imbriqués infinis sans aucun coût de vitesse.
  • Peut gérer correctement les éléments de tableau qui sont des chaînes contenant des virgules.

var arr = ["abc",[[[6]]],["3,4"],"2"];

var s = "[" + JSON.stringify(arr).replace(/\[|]/g,'') +"]";
var flattened = JSON.parse(s);

console.log(flattened)

  • Uniquement pour un tableau multidimensionnel de chaînes / nombres (pas d'objets)
vsync
la source
Votre solution est incorrecte. Il contiendra la virgule lors de l'aplatissement des tableaux internes ["345", "2", "3,4", "2"]au lieu de séparer chacune de ces valeurs pour séparer les indices
pizzarob
@realseanp - vous avez mal compris la valeur de cet élément du tableau. J'ai intentionnellement mis cette virgule comme une valeur et non comme une virgule de délimitation de tableau pour souligner la puissance de ma solution au-dessus de toutes les autres, ce qui produirait "3,4".
vsync
1
J'ai mal compris
pizzarob
cela semble certainement la solution la plus rapide que j'ai vue pour cela; êtes-vous au courant des pièges @vsync (sauf le fait qu'il semble un peu hacky bien sûr - traiter les tableaux imbriqués comme des chaînes: D)
George Katsanos
@GeorgeKatsanos - Cette méthode ne fonctionnera pas pour les éléments du tableau (et les éléments imbriqués) qui ne sont pas de valeur primitive, par exemple un élément qui pointe vers un élément DOM
vsync
6

Vous pouvez également essayer la nouvelle Array.Flat()méthode. Cela fonctionne de la manière suivante:

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

console.log(arr);

La flat()méthode crée un nouveau tableau avec tous les éléments de sous-tableau concaténés de façon récursive jusqu'à la couche de profondeur (c'est-à-dire les tableaux à l'intérieur des tableaux)

Si vous souhaitez également aplatir des tableaux tridimensionnels ou même supérieurs, vous appelez simplement la méthode plate plusieurs fois. Par exemple (3 dimensions):

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

console.log(arr);

Faites attention!

Array.Flat()est relativement nouvelle. Les navigateurs plus anciens comme ie pourraient ne pas avoir implémenté la méthode. Si vous voulez que votre code fonctionne sur tous les navigateurs, vous devrez peut-être transpiler votre JS vers une version plus ancienne. Recherchez les documents Web MD pour la compatibilité actuelle du navigateur.

Willem van der Veen
la source
2
pour les tableaux de dimensions supérieures plates, vous pouvez simplement appeler la méthode plate avec Infinityargument. Comme ça:arr.flat(Infinity)
Mikhail
C'est un très bel ajout, merci Mikhail!
Willem van der Veen
6

Utilisation de l'opérateur d'étalement:

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

Poisson-chat
la source
5

Ce n'est pas difficile, il suffit d'itérer sur les tableaux et de les fusionner:

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

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

Il semble que cela ressemble à un travail pour RECURSION!

  • Gère plusieurs niveaux d'imbrication
  • Gère les tableaux vides et les paramètres non-tableau
  • N'a pas de mutation
  • Ne repose pas sur des fonctionnalités de navigateur modernes

Code:

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

Usage:

flatten([1,[2,3],4,[[5,6],7]]);
// Result: [1, 2, 3, 4, 5, 6, 7] 
I have
la source
attention, flatten(new Array(15000).fill([1]))jette Uncaught RangeError: Maximum call stack size exceededet gèle mes devTools pendant 10 secondes
pietrovismara
@pietrovismara, mon test est d'environ 1s.
Ivan Yan
5

Je l'ai fait en utilisant la récursivité et les fermetures

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;
}
balajivijayan
la source
1
Simple et douce, cette réponse fonctionne mieux que la réponse acceptée. Il aplatit les niveaux profondément imbriqués, pas seulement le premier niveau
Om Shankar
1
AFAIK qui est une portée lexicale et non une fermeture
Dashambles
@dashambles est correct - la différence est que s'il s'agissait d'une fermeture, vous renverriez la fonction interne à l'extérieur et lorsque la fonction externe est terminée, vous pouvez toujours utiliser la fonction interne pour accéder à sa portée. Ici, la durée de vie de la fonction extérieure est plus longue que celle de la fonction intérieure, de sorte qu'une "fermeture" n'est jamais créée.
Mörre
5

L' autre jour, je me moquais des générateurs ES6 et j'ai écrit cet essentiel . Qui contient...

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

Fondamentalement, je crée un générateur qui boucle sur le tableau d'entrée d'origine, s'il trouve un tableau, il utilise l' opérateur yield * en combinaison avec la récursivité pour aplatir continuellement les tableaux internes. Si l'article n'est pas un tableau juste donne le seul élément. Ensuite, en utilisant l' opérateur Spread ES6 (ou splat), j'ai aplati le générateur dans une nouvelle instance de tableau.

Je n'ai pas testé les performances de cela, mais je pense que c'est un bel exemple simple d'utilisation de générateurs et de l'opérateur yield *.

Mais encore une fois, je ne faisais que gaffer, donc je suis sûr qu'il existe des façons plus performantes de le faire.

Ashwell
la source
1
Réponse similaire (en utilisant le générateur et la délégation de rendement), mais au format PHP
The Red Pea
5

juste la meilleure solution sans lodash

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