¿Por qué no hay Array.prototype.flatMap en javascript?

81

flatMapes increíblemente útil en colecciones, pero javascript no proporciona uno mientras tiene Array.prototype.map. ¿Por qué?

¿Hay alguna forma de emular flatMapen javascript de manera fácil y eficiente sin definir flatMapmanualmente?

Sergey Alaev
fuente
10
"¿Por qué?" ¿Porque nadie ha hecho una propuesta todavía? A continuación se explica cómo proponer una nueva función . "¿Hay alguna forma de emular flatMap ... sin definir flatMap manualmente?" ¿Eh? No entiendo. ¿Te refieres a como arr.reduce((arr, v) => (arr.push(...v), arr), [])?
Felix Kling
(^ eso es solo la parte de aplanar, así que supongo que debería ser arr.map(...).reduce(...)).
Felix Kling
Puede aplanar la matriz después de hacer .mapping.
Kennytm
1
Mmm, quieres "emularlo" pero no "definirlo". ¿Qué podría significar eso?
8
Pronto será parte de JS. tc39.github.io/proposal-flatMap
Vijaiendran

Respuestas:

95

Actualización: Array.prototype.flatMap está en camino a ECMAScript nativo. Actualmente se encuentra en la etapa 4 .

Es ampliamente compatible en muchos entornos. Vea si funciona en su navegador usando este fragmento a continuación:

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 ]


¿Por qué no hay Array.prototype.flatMap en javascript?

Porque la programación no es mágica y todos los lenguajes no tienen características / primitivas que todos los demás lenguajes tienen

Lo que importa es

JavaScript te da la posibilidad de definirlo por tu cuenta

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

O una reescritura que colapsa los dos bucles en uno

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

Si lo quieres Array.prototype, nada te detiene

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

Gracias
fuente
1
Se considera mejor Array.prototype._mylib_flatMapusar el espacio de nombres para cualquier adición a los prototipos (por ejemplo ), o incluso mejor usar los símbolos ES6, en lugar de simplemente definir Array.prototype.flatMap.
Fengyang Wang
11
@FengyangWang "considerado mejor" es muy subjetivo. Si esta es su propia aplicación y tiene una razón para ampliar sus prototipos, no tiene nada de malo. Si es una biblioteca / módulo / marco que está distribuyendo para el uso de otros, entonces estaría de acuerdo con usted. Sin embargo, los comentarios no son el lugar para discutir esto; el asunto merece más explicación y detalle de lo que debería proporcionarse en los comentarios.
Gracias
Lo primero es feo: flatMap definido como función hará que el código sea un desastre - ¡imagina un código funcional con mapuna función definida como global! La segunda es solo una mala práctica
Sergey Alaev
3
@SergeyAlaev re: "imagina el mapa como una función global" jaja. Hago exactamente eso en muchas de mis funciones de utilidad. Los curro también usando secuencias de funciones de flecha. "Feo" es subjetivo. Y no ha presentado ningún argumento para apoyar su comentario de "mala práctica". Sus comentarios me indican que tiene poca experiencia con la programación funcional. Tome un lenguaje como Haskell donde el preludio incluye montones de "globales" ... nadie dice que sea "feo" o una "mala práctica". Simplemente no está familiarizado con él.
Gracias
5
@SergeyAlaev o considerar que tiene la raqueta append, map, filter, foldl, foldrentre muchos otros en el espacio de nombres predeterminado. No lo convierte en una mala práctica porque escuchaste a alguien decir "los globales son malos" o "extender los nativos es malo"; el raqueta está entre los lenguajes mejor diseñados. Simplemente no sabes cómo / cuándo hacerlo adecuadamente.
Gracias
48

flatMapha sido aprobado por el TC39 como parte de ES2019 (ES10). Puedes usarlo así:

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

Aquí está mi propia implementación del método:

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

Artículo de MDN sobre flatMap

Kutyel
fuente
En particular, flapMap está oficialmente en el nodo 11.0.0 según MDN: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Carl Walsh
3
@CarlWalsh Lo siento, no puedo resistir. ¿Qué diablos es flapMap? ¡Quiero aplanar mi mapa, no agitarlo!
ErikE
En otro tema, esto finalmente convierte JavaScript Array en Monads ™ ️ 🙃
Kutyel
Esta es una respuesta antigua, pero es bastante ineficiente, especialmente en matrices grandes (crea una nueva matriz y copia todo el elemento anterior en cada iteración). Otro enfoque sería usar concat const flatMap = (arr, ...args) => [].concat(...arr.map(...args));(o usar applysi no puede usar el operador de propagación), aunque esta versión concat tiene problemas de tamaño de pila de llamadas para matrices enormes como se menciona en una solución inferior
Bali Balo
10

Sé que dijiste que no querías definirlo tú mismo, pero esta implementación es una definición bastante trivial.

También hay esto de la misma página de github:

Aquí hay una forma un poco más corta usando es6 spread, similar al de renaudtertrais, pero usando es6 y no agregando al prototipo.

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

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

flatMap(arr, s)

¿Ayudaría alguno de estos?

Cabe señalar (gracias a @ftor) que esta última "solución" sufre del "tamaño máximo de pila de llamadas excedido" si se llama en una matriz realmente grande (por ejemplo, 300k elementos) a.

Alan Mimms
fuente
2
Esta implementación puede hacer explotar la pila para arreglos grandes.
Para que quede claro, si te entiendo correctamente, @ftor, estás hablando de la implementación recursiva en el enlace que di, no la que muestro en el bloque de código al final de mi respuesta, ¿verdad?
Alan Mimms
2
Prueba [].concat(...(new Array(300000).fill("foo").map(x => x.toUpperCase()))). Claro, es un caso de esquina. Pero al menos deberías mencionarlo.
Gracias. Agregué una nota a este efecto.
Alan Mimms
7

Lodash proporciona una función de mapa plano, que para mí es prácticamente equivalente a que Javascript la proporcione de forma nativa. Si no es un usuario de Lodash, el Array.reduce()método de ES6 puede darle el mismo resultado, pero debe mapear y luego aplanar en pasos discretos.

A continuación se muestra un ejemplo de cada método, mapeando una lista de números enteros y devolviendo solo las probabilidades.

Lodash:

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

ES6 Reducir:

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

Un enfoque bastante conciso es hacer uso de Array#concat.apply:

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

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

JLRishe
fuente
1

Hice algo como esto:

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
fuente
Hola serhii, he probado este código, cuando el padre solo un objeto dará un resultado inesperado. `` `Array.prototype.flatMap = function (selector) {if (this.length == 1) {return selector (this [0]); } return this.reduce ((prev, next) => (/ * first * / selector (prev) || / * all after first * / prev) .concat (selector (next)))}; ``
Johnny
0

¡Ahora tenemos flatMap()en Javascript! Y se soporta bastante bien

El método flatMap () primero mapea cada elemento usando una función de mapeo, luego aplana el resultado en una nueva matriz. Es idéntico a un mapa () seguido de un plano () de profundidad 1

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

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

Empezando
fuente
Obteniendo error al ejecutar el código publicado{ "message": "Uncaught TypeError: [1,2,3].flatMap is not a function", "filename": "https://stacksnippets.net/js", "lineno": 15, "colno": 23 }
SolutionMill
¿Qué broser estás usando? Edge, IE y Samsung Internet aún no lo admiten, aunque pronto Edge se ejecutará en V8. Entonces, para apoyarlos, podría usar un polyfil
bigInt
Chrome versión 67.0.3396.87 (compilación oficial) (64 bits)
SolutionMill
Su navegador está desactualizado. Debe actualizarlo o configurarlo para que se actualice automáticamente.
bigInt
0

Array.prototype.flatMap()ha llegado ahora a JS. Sin embargo, es posible que no todos los navegadores los admitan. Compruebe la compatibilidad actual del navegador con los documentos web de Mozilla .

Lo que hace el flatMap()método es primero mapea cada elemento usando una función de devolución de llamada como argumento, luego aplana el resultado en una nueva matriz (la matriz 2d ahora es 1d ya que los elementos se aplanaron).

Aquí también hay un ejemplo de cómo usar la función:

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

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

console.log(newArr);

Willem van der Veen
fuente