javascript - Dividir un arreglo por la función filter
Tengo un array de Javascript que me gustaría dividir en dos según si una función llamada en cada elemento devuelve true o false. Esencialmente, esto es un array.filter, pero también me gustaría tener a mano los elementos que fueron filtrados fuera.
Actualmente, mi plan es usar array.forEach y llamar a la función predicado en cada elemento. Dependiendo de si esto es verdadero o falso, agregaré el elemento actual a uno de los dos nuevos arreglos. ¿Hay una manera más elegante o de alguna otra forma mejor de hacer esto? Por ejemplo, un array.filter que empuje el elemento a otro arreglo antes de que devuelva false?
Respuesta #1
Con ES6 puedes hacer uso de la sintaxis spread con reduce:
function partition(array, isValid) {
return array.reduce(([pass, fail], elem) => {
return isValid(elem) ? [[...pass, elem], fail] : [pass, [...fail, elem]];
}, [[], []]);
}
const [pass, fail] = partition(myArray, (e) => e > 5);
O en una sola línea:
const [pass, fail] = a.reduce(([p, f], e) => (e > 5 ? [[...p, e], f] : [p, [...f, e]]), [[], []]);
Respuesta #2
Puedes usar lodash.partition
var users = [
{ 'user': 'barney', 'age': 36, 'active': false },
{ 'user': 'fred', 'age': 40, 'active': true },
{ 'user': 'pebbles', 'age': 1, 'active': false }
];
_.partition(users, function(o) { return o.active; });
// → objects for [['fred'], ['barney', 'pebbles']]
// The `_.matches` iteratee shorthand.
_.partition(users, { 'age': 1, 'active': false });
// → objects for [['pebbles'], ['barney', 'fred']]
// The `_.matchesProperty` iteratee shorthand.
_.partition(users, ['active', false]);
// → objects for [['barney', 'pebbles'], ['fred']]
// The `_.property` iteratee shorthand.
_.partition(users, 'active');
// → objects for [['fred'], ['barney', 'pebbles']]
o ramda.partition
R.partition(R.contains('s'), ['sss', 'ttt', 'foo', 'bars']);
// => [ [ 'sss', 'bars' ], [ 'ttt', 'foo' ] ]
R.partition(R.contains('s'), { a: 'sss', b: 'ttt', foo: 'bars' });
// => [ { a: 'sss', foo: 'bars' }, { b: 'ttt' } ]
Respuesta #3
Se me ocurrió este pequeño. Usa 'for each' y todo eso como describiste, pero en mi opinión se ve limpio y conciso.
//Partition function
function partition(array, filter) {
let pass = [], fail = [];
array.forEach((e, idx, arr) => (filter(e, idx, arr) ? pass : fail).push(e));
return [pass, fail];
}
//Run it with some dummy data and filter
const [lessThan5, greaterThanEqual5] = partition([0,1,4,3,5,7,9,2,4,6,8,9,0,1,2,4,6], e => e < 5);
//Output
console.log(lessThan5);
console.log(greaterThanEqual5);Respuesta #4
Puedes usar reduce para eso:
function partition(array, callback){
return array.reduce(function(result, element, i) {
callback(element, i, array)
? result[0].push(element)
: result[1].push(element);
return result;
}, [[],[]]
);
};
O si se utiliza Typescript:
const partition = <T,>(
array: T[],
callback: (element: T, index: number, array: T[]) => boolean
) => {
return array.reduce(function(result, element, i) {
callback(element, i, array)
? result[0].push(element)
: result[1].push(element);
return result;
}, [[],[]]);
};
Ejemplo :
const groceries = [
{ type: "apple" },
{ type: "pear" },
{ type: "banana" }
]
const [apples, others] = partition(
groceries,
(item) => item.type === "apple",
);
// => apples: [{ type: "apple" }]
// => others: [{ type: "pear" }, { type: "banana" }]
Usando la sintaxis de ES6, también puedes hacer eso utilizando recursión (actualizado para evitar crear nuevos arrays en cada iteración):
function partition([current, ...tail], f, left = [], right = []) {
if(current === undefined) {
return [left, right];
}
if(f(current)) {
left.push(current);
return partition(tail, f, left, right);
}
right.push(current);
return partition(tail, f, left, right);
}
Respuesta #5
En la función filter puedes enviar tus elementos falsos a otra variable fuera de la función:
var bad = [], good = [1,2,3,4,5];
good = good.filter(function (value) { if (value === false) { bad.push(value) } else { return true});
Por supuesto, value === false necesita ser una comparación real ;)
Pero hace casi la misma operación que forEach. Creo que deberías usar forEach para una mejor legibilidad del código.
Respuesta #6
Esto suena muy similar al método Enumerable#partition de Ruby.
Si la función no puede tener efectos secundarios (es decir, no puede alterar la matriz original), entonces no hay una manera más eficiente de particionar la matriz que iterar sobre cada elemento y empujar el elemento a uno de tus dos arreglos.
Dicho esto, es discutiblemente más "elegante" crear un método en Array para realizar esta función. En este ejemplo, la función filter se ejecuta en el contexto del array original (es decir, this será el array original), y recibe como argumentos el elemento y el índice del elemento (similar al método each de jQuery):
Array.prototype.partition = function (f){
var matched = [],
unmatched = [],
i = 0,
j = this.length;
for (; i < j; i++){
(f.call(this, this[i], i) ? matched : unmatched).push(this[i]);
}
return [matched, unmatched];
};
console.log([1, 2, 3, 4, 5].partition(function (n, i){
return n % 2 == 0;
}));
//=> [ [ 2, 4 ], [ 1, 3, 5 ] ]
Respuesta #7
Muchas respuestas aquí usan Array.prototype.reduce para construir un acumulador mutable, y señalan correctamente que para arreglos grandes, esto es más eficiente que, por ejemplo, usar un operador de propagación para copiar un nuevo arreglo en cada iteración. La desventaja es que no es tan bonito como una expresión "pura" usando la sintaxis corta de lambda.
Pero una forma de evitar eso es usar el operador coma. En lenguajes similares a C, la coma es un operador que siempre devuelve el operando de la derecha. Puedes usar esto para crear una expresión que llame a una función void y devuelva un valor.
function partition(array, predicate) {
return array.reduce((acc, item) => predicate(item)
? (acc[0].push(item), acc)
: (acc[1].push(item), acc), [[], []]);
}
Si aprovechas el hecho de que una expresión booleana se convierte implícitamente en un número como 0 y 1, puedes hacerlo aún más conciso, aunque no creo que sea tan legible:
function partition(array, predicate) {
return array.reduce((acc, item) => (acc[+!predicate(item)].push(item), acc), [[], []]);
}
Uso :
const [trues, falses] = partition(['aardvark', 'cat', 'apple'], i => i.startsWith('a'));
console.log(trues); // ['aardvark', 'apple']
console.log(falses); // ['cat']
Respuesta #8
¿Qué hay de esto?
[1,4,3,5,3,2].reduce( (s, x) => { s[ x > 3 ].push(x); return s;} , {true: [], false:[]} )
Probablemente esto sea más eficiente que el operador de propagación
O un poco más corto, pero más feo
[1,4,3,5,3,2].reduce( (s, x) => s[ x > 3 ].push(x)?s:s , {true: [], false:[]} )
