Asigne y filtre una matriz al mismo tiempo


155

Tengo una matriz de objetos que quiero iterar para producir una nueva matriz filtrada. Pero también, necesito filtrar algunos de los objetos de la nueva matriz dependiendo de un parámetro. Estoy intentando esto:

function renderOptions(options) {
    return options.map(function (option) {
        if (!option.assigned) {
            return (someNewObject);
        }
    });   
}

¿Es ese un buen enfoque? hay algun metodo mejor? Estoy abierto a usar cualquier biblioteca como lodash.


¿Qué pasa con un enfoque "object.keys"? developer.mozilla.org/fr/docs/Web/JavaScript/Reference/…
Julo0sS


3
.reduce()es definitivamente más rápido que hacer .filter(...).map(...)lo que he visto sugerido en otra parte. Configuré una prueba JSPerf para demostrar stackoverflow.com/a/47877054/2379922
Justin L.

Respuestas:


218

Deberías usar Array.reducepara esto.

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = options.reduce(function(filtered, option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     filtered.push(someNewValue);
  }
  return filtered;
}, []);

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>


Alternativamente, el reductor puede ser una función pura, como esta

var reduced = options.reduce(function(result, option) {
  if (option.assigned) {
    return result.concat({
      name: option.name,
      newProperty: 'Foo'
    });
  }
  return result;
}, []);

2
Para mí, el primer argumento filteredes un objeto. así filtered.pushque no está definido para mí.
Rahmathullah M

44
También tuve un problema con filteredser un objeto. Esto se debió a que no estaba pasando el "valor inicial": la matriz vacía ( []) después de la función de reducción. ej. incorrecto var reduced = options.reduce(function(filtered, option) { ... }); correcto var reduced = options.reduce(function(filtered, option) { ... }, []);
Jon Higgins

No hay ninguna razón para reduceesto, también podría usarlo forEachya que en cada iteración cambia la matriz filteredy, por lo tanto, no es puramente funcional. Sería un evento más legible y compacto.
Marko

1
@Marko También introduje un reductor puro. PTAL.
thefourtheye

Yap eso es puro ahora. +1 por tu esfuerzo. No es que sea un defensor de la programación funcional pura, lejos de eso, simplemente no podía ver el punto :) Pero en realidad he usado tu truco después de verlo, porque fue muy útil en la situación dada. Pero para esta tarea es posible que desee echar un vistazo a la función flatMap, creo que se convirtió en estándar después de que respondiera (también por esa razón puede que algunos navegadores no lo admitan). Debería ser más eficiente ya que concatenar matrices como esta hace que la tarea O (n ^ 2) salga de la tarea O (n).
Marko

43

¡Usa reducir, Luke!

function renderOptions(options) {
    return options.reduce(function (res, option) {
        if (!option.assigned) {
            res.push(someNewObject);
        }
        return res;
    }, []);   
}

30

Desde 2019, Array.prototype.flatMap es una buena opción.

options.flatMap(o => o.assigned ? [o.name] : []);

Desde la página MDN vinculada anteriormente:

flatMapse puede usar como una forma de agregar y eliminar elementos (modificar el número de elementos) durante un mapa. En otras palabras, le permite asignar muchos elementos a muchos elementos (manejando cada elemento de entrada por separado), en lugar de siempre uno a uno. En este sentido, funciona como lo opuesto al filtro. Simplemente devuelva una matriz de 1 elemento para mantener el elemento, una matriz de múltiples elementos para agregar elementos o una matriz de 0 elementos para eliminar el elemento.


55
Gran solución! Pero, los lectores tienen en cuenta que flatMapse introdujo recientemente y su soporte de navegador es limitado . Es posible que desee utilizar los polyfill / shims oficiales: flatMap y flat .
Arel

24

Con ES6 puedes hacerlo muy corto:

options.filter(opt => !opt.assigned).map(opt => someNewObject)


25
Esto hará dos bucles, dependiendo de qué filtro devuelva todavía puede consumir memoria.
hogan el

1
@hogan pero esta es la respuesta correcta (puede que no sea la solución óptima) a la consulta original
vikramvi

1
@vikramvi gracias por tu nota. La cuestión es que podemos lograr lo mismo de muchas maneras y preferiría la mejor.
hogan

10

¡Una línea reducecon la sintaxis elegante de ES6 está aquí!

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, {name, assigned}) => [...result, ...assigned ? [name] : []], []);

console.log(filtered);


1
Realmente agradable @Maxim! ¡Voté esto! Pero ... en cada elemento agregado, tiene que difundir todos los elementos en result... es como una filter(assigned).map(name)solución
0zkr PM

Buena esa. Me tomó un segundo darme cuenta de lo que estaba pasando ...assigned ? [name] : [], puede ser más legible como...(assigned ? [name] : [])
johansenja

5

Haría un comentario, pero no tengo la reputación requerida. Una pequeña mejora a la muy buena respuesta de Maxim Kuzmin para que sea más eficiente:

const options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

const filtered = options
  .reduce((result, { name, assigned }) => assigned ? result.concat(name) : result, []);

console.log(filtered);

Explicación

En lugar de distribuir el resultado completo una y otra vez para cada iteración, solo agregamos a la matriz, y solo cuando realmente hay un valor para insertar.


3

Utilice Array.prototy.filter en

function renderOptions(options) {
    return options.filter(function(option){
        return !option.assigned;
    }).map(function (option) {
        return (someNewObject);
    });   
}

2

En algún momento, ¿no es más fácil (o igual de fácil) usar un forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];

var reduced = []
options.forEach(function(option) {
  if (option.assigned) {
     var someNewValue = { name: option.name, newProperty: 'Foo' }
     reduced.push(someNewValue);
  }
});

document.getElementById('output').innerHTML = JSON.stringify(reduced);
<h1>Only assigned options</h1>
<pre id="output"> </pre>

Sin embargo, sería bueno si hubiera una función malter()o fap()que combine las funciones mapy filter. Funcionaría como un filtro, excepto que en lugar de devolver verdadero o falso, devolvería cualquier objeto o un valor nulo / indefinido.


Es posible que desee consultar sus memes ... ;-)
Arel

2

Optimicé las respuestas con los siguientes puntos:

  1. Reescribiendo if (cond) { stmt; }comocond && stmt;
  2. Usar funciones de flecha ES6

Presentaré dos soluciones, una usando forEach , la otra usando reduce :

Solución 1: uso de forEach

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = []
options.forEach(o => {
  o.assigned && reduced.push( { name: o.name, newProperty: 'Foo' } );
} );
console.log(reduced);

Solución 2: usando reduce

var options = [
  { name: 'One', assigned: true }, 
  { name: 'Two', assigned: false }, 
  { name: 'Three', assigned: true }, 
];
var reduced = options.reduce((a, o) => {
  o.assigned && a.push( { name: o.name, newProperty: 'Foo' } );
  return a;
}, [ ] );
console.log(reduced);

Te dejo a ti decidir qué solución elegir.


1
¿Por qué demonios usarías cond && stmt;? Esto es mucho más difícil de leer y no ofrece ningún beneficio.
jlh

1

Usando reduce, puede hacer esto en una función Array.prototype. Esto buscará todos los números pares de una matriz.

var arr = [1,2,3,4,5,6,7,8];

var brr = arr.reduce((c, n) => {
  if (n % 2 !== 0) {
    return c;
  }
  c.push(n);
  return c;
}, []);

document.getElementById('mypre').innerHTML = brr.toString();
<h1>Get all even numbers</h1>
<pre id="mypre"> </pre>

Puede usar el mismo método y generalizarlo para sus objetos, de esta manera.

var arr = options.reduce(function(c,n){
  if(somecondition) {return c;}
  c.push(n);
  return c;
}, []);

arr ahora contendrá los objetos filtrados.


0

El uso directo de .reducepuede ser difícil de leer, por lo que recomiendo crear una función que genere el reductor para usted:

function mapfilter(mapper) {
  return (acc, val) => {
    const mapped = mapper(val);
    if (mapped !== false)
      acc.push(mapped);
    return acc;
  };
}

Úselo así:

const words = "Map and filter an array #javascript #arrays";
const tags = words.split(' ')
  .reduce(mapfilter(word => word.startsWith('#') && word.slice(1)), []);
console.log(tags);  // ['javascript', 'arrays'];
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.