Warum keine Array.prototype.flatMap in Javascript?

81

flatMapist unglaublich nützlich für Sammlungen, aber Javascript bietet keine, während Sie haben Array.prototype.map. Warum?

Gibt es eine Möglichkeit, flatMapJavascript auf einfache und effiziente Weise zu emulieren , ohne es flatMapmanuell zu definieren ?

Sergey Alaev
quelle
10
"Warum?" Weil noch niemand einen Vorschlag gemacht hat? Hier erfahren Sie, wie Sie eine neue Funktion vorschlagen . "Gibt es eine Möglichkeit, flatMap zu emulieren ... ohne flatMap manuell zu definieren?" Äh? Ich verstehe nicht. Du meinst wie arr.reduce((arr, v) => (arr.push(...v), arr), [])?
Felix Kling
(^ das ist nur der abgeflachte Teil, also denke ich, sollte es sein arr.map(...).reduce(...)).
Felix Kling
Sie können das Array einfach reduzieren, nachdem Sie es .mapgepingt haben.
Kennytm
1
Hmm, Sie möchten es "emulieren", aber nicht "definieren". Was könnte das bedeuten?
8
Es wird bald Teil von JS sein. tc39.github.io/proposal-flatMap
Vijaiendran

Antworten:

95

Update: Array.prototype.flatMap ist auf dem Weg zu nativem ECMAScript. Es ist derzeit in Phase 4 .

Es wird in vielen Umgebungen weitgehend unterstützt. Überprüfen Sie anhand dieses Snippets, ob es in Ihrem Browser funktioniert.

const data =
  [ 1, 2, 3, 4 ]
  
console.log(data.flatMap(x => Array(x).fill(x)))
// [ 1, 2, 2, 3, 3, 3, 4, 4, 4, 4 ]


Warum keine Array.prototype.flatMap in Javascript?

Weil Programmieren keine Magie ist und nicht jede Sprache Funktionen / Grundelemente hat, die jede andere Sprache hat

Was zählt, ist

Mit JavaScript können Sie es selbst definieren

const concat = (x,y) =>
  x.concat(y)

const flatMap = (f,xs) =>
  xs.map(f).reduce(concat, [])

const xs = [1,2,3]

console.log(flatMap(x => [x-1, x, x+1], xs))

Oder ein Umschreiben, bei dem die beiden Schleifen zu einer zusammengefasst werden

const flatMap = (f,xs) =>
  xs.reduce((acc,x) =>
    acc.concat(f(x)), [])

const xs = [1,2,3]

console.log(flatMap(x => [x-1, x, x+1], xs))

Wenn Sie es auf der wollen Array.prototype, hält Sie nichts auf

const concat = (x, y) => x.concat(y)

const flatMap = (f, xs) => xs.map(f).reduce(concat, [])

if (Array.prototype.flatMap === undefined) {
  Array.prototype.flatMap = function(f) {
    return flatMap(f, this)
  }
}

const xs = [1, 2, 3]

console.log(xs.flatMap(x => [x-1, x, x+1]))
.as-console-wrapper { top: 0; max-height: 100% !important; }

Vielen Dank
quelle
1
Es wird als besser angesehen, Ergänzungen zu Prototypen (z. B. Array.prototype._mylib_flatMap) mit einem Namespace zu versehen oder sogar ES6-Symbole zu verwenden, anstatt sie einfach zu definieren Array.prototype.flatMap.
Fengyang Wang
11
@FengyangWang "als besser angesehen" ist sehr subjektiv. Wenn dies Ihre eigene App ist und Sie einen Grund haben, Ihre Prototypen zu erweitern, ist daran nichts auszusetzen. Wenn es sich um eine Bibliothek / ein Modul / ein Framework handelt, die Sie für die Verwendung durch andere verteilen, würde ich Ihnen zustimmen. Kommentare sind jedoch nicht der Ort, um dies zu diskutieren - die Angelegenheit verdient mehr Erklärung und Details, als in Kommentaren angegeben werden sollte.
Vielen Dank, dass Sie
Das erste ist hässlich: flatMap, definiert als Funktion, macht Code zu einem Chaos - stellen Sie sich Funktionscode mit mapdefinierter globaler Funktion vor! Zweitens ist nur schlechte Übung
Sergey Alaev
3
@SergeyAlaev re: "Stellen Sie sich eine Karte als globale Funktion vor" haha, genau das mache ich bei vielen meiner Dienstprogrammfunktionen. Ich curry sie auch mit Sequenzen von Pfeilfunktionen. "Hässlich" ist subjektiv. Und Sie haben kein Argument vorgebracht, um Ihren Kommentar zu "schlechten Praktiken" zu unterstützen. Ihre Bemerkungen zeigen mir, dass Sie wenig Erfahrung mit funktionaler Programmierung haben. Nehmen Sie eine Sprache wie Haskell, in der zum Auftakt jede Menge "Globals" enthalten sind ... niemand sagt, es sei "hässlich" oder eine "schlechte Praxis". Du bist einfach nicht damit vertraut.
Danke
5
@SergeyAlaev oder zu prüfen , Schläger, der hat append, map, filter, foldl, foldrunter unzählig anderen in dem Standard - Namespace. Es macht es nicht zu einer schlechten Praxis, weil Sie jemanden sagen hörten "Globale sind schlecht" oder "Eingeborene zu erweitern ist schlecht" - Schläger gehört zu den am besten gestalteten Sprachen. Sie wissen einfach nicht, wie / wann Sie es richtig machen sollen.
Danke
48

flatMapwurde vom TC39 als Teil von ES2019 (ES10) genehmigt. Sie können es so verwenden:

[1, 3].flatMap(x => [x, x + 1]) // > [1, 2, 3, 4]

Hier ist meine eigene Implementierung der Methode:

const flatMap = (f, arr) => arr.reduce((x, y) => [...x, ...f(y)], [])

MDN Artikel auf flatMap

Kutyel
quelle
Insbesondere ist flapMap laut MDN offiziell in Node 11.0.0: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Carl Walsh
3
@ CarlWalsh Es tut mir leid, ich kann nicht widerstehen. Was zum Teufel ist das flapMap? Ich möchte meine Karte platt machen, nicht klappen!
ErikE
Bei einem anderen Thema verwandelt dies JavaScript-Arrays schließlich in Monads ™ K 🙃
Kutyel
Dies ist eine alte Antwort, aber dies ist ziemlich ineffizient, insbesondere bei großen Arrays (erstellt ein neues Array und kopiert bei jeder Iteration das gesamte vorherige Element). Ein anderer Ansatz wäre die Verwendung von concat const flatMap = (arr, ...args) => [].concat(...arr.map(...args));(oder applywenn Sie den Spread-Operator nicht verwenden können) - obwohl diese concat-Version Probleme mit der Größe des Aufrufstapels für große Arrays aufweist, wie in einer niedrigeren Lösung erwähnt
Bali Balo
10

Ich weiß, dass Sie gesagt haben, Sie wollten es nicht selbst definieren, aber diese Implementierung ist eine ziemlich triviale Definition.

Es gibt auch das von derselben Github-Seite:

Hier ist ein etwas kürzerer Weg mit es6-Spread, ähnlich wie bei renaudtertrais - aber mit es6 und ohne Hinzufügen zum Prototyp.

var flatMap = (a, cb) => [].concat(...a.map(cb))

const s = (v) => v.split(',')
const arr = ['cat,dog', 'fish,bird']

flatMap(arr, s)

Würde einer dieser beiden helfen?

Es sollte beachtet werden (dank @ftor), dass diese letztere "Lösung" unter "Maximale Größe des Aufrufstapels überschritten" leidet, wenn sie auf einem wirklich großen Array (z. B. 300.000 Elementen) aufgerufen wird a.

Alan Mimms
quelle
2
Diese Implementierung kann den Stapel für große Arrays in die Luft sprengen.
Um ganz klar zu sein, wenn ich Sie richtig verstehe, @ftor, sprechen Sie über die rekursive Implementierung in dem Link, den ich gegeben habe, nicht über die, die ich im Codeblock am Ende meiner Antwort zeige, oder?
Alan Mimms
2
Versuchen Sie es [].concat(...(new Array(300000).fill("foo").map(x => x.toUpperCase()))). Sicher, es ist ein Eckfall. Aber du solltest es zumindest erwähnen.
Vielen Dank. Ich habe diesem Effekt eine Notiz hinzugefügt.
Alan Mimms
7

Lodash bietet eine Flatmap-Funktion, die für mich praktisch gleichbedeutend ist mit Javascript, das sie nativ bereitstellt. Wenn Sie kein Lodash-Benutzer sind, Array.reduce()kann die Methode von ES6 das gleiche Ergebnis liefern, Sie müssen jedoch in diskreten Schritten abbilden und dann reduzieren.

Unten finden Sie ein Beispiel für jede Methode, bei der eine Liste von Ganzzahlen zugeordnet und nur die Gewinnchancen zurückgegeben werden.

Lodash:

_.flatMap([1,2,3,4,5], i => i%2 !== 0 ? [i] : [])

ES6 Reduzieren:

[1,2,3,4,5].map(i => i%2 !== 0 ? [i] : []).reduce( (a,b) => a.concat(b), [] )
Matthew Marichiba
quelle
4

Ein ziemlich prägnanter Ansatz besteht darin, Folgendes zu verwenden Array#concat.apply:

const flatMap = (arr, f) => [].concat.apply([], arr.map(f))

console.log(flatMap([1, 2, 3], el => [el, el * el]));

JLRishe
quelle
1

Ich habe so etwas gemacht:

Array.prototype.flatMap = function(selector){ 
  return this.reduce((prev, next) => 
    (/*first*/ selector(prev) || /*all after first*/ prev).concat(selector(next))) 
}

[[1,2,3],[4,5,6],[7,8,9]].flatMap(i => i); //[1, 2, 3, 4, 5, 6, 7, 8, 9]

[{subarr:[1,2,3]},{subarr:[4,5,6]},{subarr:[7,8,9]}].flatMap(i => i.subarr); //[1, 2, 3, 4, 5, 6, 7, 8, 9]

Serhii
quelle
Hallo serhii, ich habe diesen Code ausprobiert, wenn nur ein Objekt ein unerwartetes Ergebnis liefert. `` `Array.prototype.flatMap = function (selector) {if (this.length == 1) {return selector (this [0]); } return this.reduce ((prev, next) => (/ * first * / selector (prev) || / * alle nach first * / prev) .concat (selector (next)))}; `` `
Johnny
0

Wir haben jetzt eine flatMap()in Javascript! Und es wird ziemlich gut unterstützt

Die flatMap () -Methode ordnet zuerst jedes Element mithilfe einer Zuordnungsfunktion zu und glättet dann das Ergebnis in ein neues Array. Es ist identisch mit einer Karte (), gefolgt von einer Ebene () mit der Tiefe 1

const dublicate = x => [x, x];

console.log([1, 2, 3].flatMap(dublicate))

bigInt
quelle
Fehler beim Ausführen des veröffentlichten Codes{ "message": "Uncaught TypeError: [1,2,3].flatMap is not a function", "filename": "https://stacksnippets.net/js", "lineno": 15, "colno": 23 }
SolutionMill
Welchen Broser benutzt du? Edge, IE und Samsung Internet unterstützen es noch nicht, obwohl Edge bald auf V8 laufen wird. Um diese zu unterstützen, könnten Sie eine Polyfil verwenden
bigInt
Chrome Version 67.0.3396.87 (Official Build) (64-Bit)
SolutionMill
Ihr Browser ist veraltet. Sie sollten ihn aktualisieren oder so einstellen, dass er sich automatisch aktualisiert.
BigInt
0

Array.prototype.flatMap()ist jetzt in JS angekommen. Möglicherweise unterstützen jedoch nicht alle Browser die Überprüfung der aktuellen Browserkompatibilität der Mozilla-Webdokumente .

Was zum flatMap() Methode ordnet zunächst jedes Element mithilfe einer Rückruffunktion als Argument zu und glättet dann das Ergebnis in ein neues Array (das 2d-Array ist jetzt 1d, da die Elemente abgeflacht sind).

Hier ist auch ein Beispiel für die Verwendung der Funktion:

let arr = [[2], [4], [6], [8]]

let newArr = arr.flatMap(x => [x * 2]);

console.log(newArr);

Willem van der Veen
quelle