Tengo una matriz de Javascript que me gustaría dividir en dos según si una función llamada en cada elemento devuelve true
o false
. En esencia, se trata de una array.filter
, pero me gustaría tener también a mano los elementos que se filtraron a cabo .
Actualmente, mi plan es usar array.forEach
y llamar a la función de predicado en cada elemento. Dependiendo de si esto es verdadero o falso, insertaré el elemento actual en una de las dos nuevas matrices. ¿Existe una forma más elegante o mejor de hacer esto? ¿ array.filter
Dónde empujará el elemento a otra matriz antes de que regrese false
, por ejemplo?
javascript
Mike Chen
fuente
fuente
.filter
pero estos efectos secundarios son difíciles de rastrear y comprender. Simplemente itere sobre la matriz y empuje a una matriz u otra.Respuestas:
Con ES6 puede hacer uso de la sintaxis de propagación 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]]), [[], []]);
fuente
Puede 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' } ]
fuente
Puedes usar reducir para ello:
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; }, [[],[]] ); };
Actualizar. Usando la sintaxis de ES6 también puede hacerlo usando la recursividad:
function partition([current, ...tail], f, [left, right] = [[], []]) { if(current === undefined) { return [left, right]; } if(f(current)) { return partition(tail, f, [[...left, current], right]); } return partition(tail, f, [left, [...right, current]]); }
fuente
[...left, current]
o[...right, current]
- para cada elemento. No conozco los componentes internos exactos, pero estoy seguro de que la construcción es más cara que simplemente insertar un elemento en una matriz. Además, como regla general, la recursividad es más cara que la iteración , porque implica la creación de un "marco de pila" cada vez.Esto suena muy similar al método de Ruby
Enumerable#partition
.Si la función no puede tener efectos secundarios (es decir, no puede alterar la matriz original), entonces no hay forma más eficiente de particionar la matriz que iterando sobre cada elemento y empujando el elemento a una de sus dos matrices.
Dicho esto, podría decirse que es más "elegante" crear un método
Array
para realizar esta función. En este ejemplo, la función de filtro se ejecuta en el contexto de la matriz original (es decir,this
será la matriz original), y recibe el elemento y el índice del elemento como argumentos (similar al método de jQueryeach
):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 ] ]
fuente
Se me ocurrió este pequeño. Se usa para todos y cada uno de los que 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);
fuente
for
bucle simple como en una respuesta anterior de @qwertymk. Por ejemplo, para una matriz que contiene 100.000 elementos, fue el doble de lento en mi sistema.En la función de filtro, puede insertar sus elementos falsos en 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
debe ser una comparación real;)Pero hace casi la misma operación como
forEach
. Creo que debería usarloforEach
para una mejor legibilidad del código.fuente
Fácil de leer.
const partition = (arr, condition) => { const trues = arr.filter(el => condition(el)); const falses = arr.filter(el => !condition(el)); return [trues, falses]; }; // sample usage const nums = [1,2,3,4,5,6,7] const [evens, odds] = partition(nums, (el) => el%2 == 0)
fuente
Prueba esto:
function filter(a, fun) { var ret = { good: [], bad: [] }; for (var i = 0; i < a.length; i++) if (fun(a[i]) ret.good.push(a[i]); else ret.bad.push(a[i]); return ret; }
MANIFESTACIÓN
fuente
¿Qué pasa con 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:[]} )
fuente
Muchas respuestas aquí se usan
Array.prototype.reduce
para construir un acumulador mutable, y con razón señalan que para arreglos grandes, esto es más eficiente que, digamos, usar un operador de extensión para copiar un nuevo arreglo en cada iteración. La desventaja es que no es tan bonita como una expresión "pura" que utiliza la sintaxis lambda corta.Pero una forma de evitarlo es usar el operador de coma. En lenguajes similares a C, la coma es un operador que siempre devuelve el operando de la derecha. Puede 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 aprovecha el hecho de que una expresión booleana se convierte implícitamente en un número como 0 y 1, y puede 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']
fuente
Partición ONE-LINER
const partition = (a,f)=>a.reduce((p,q)=>(p[+!f(q)].push(q),p),[[],[]]);
MANIFESTACIÓN
// to make it consistent to filter pass index and array as arguments const partition = (a, f) => a.reduce((p, q, i, ar) => (p[+!f(q, i, ar)].push(q), p), [[], []]); console.log(partition([1, 2, 3, 4, 5], x => x % 2 === 0)); console.log(partition([..."ABCD"], (x, i) => i % 2 === 0));
Para mecanografiado
const partition = <T>( a: T[], f: (v: T, i?: number, ar?: T[]) => boolean ): [T[], T[]] => a.reduce((p, q, i, ar) => (p[+!f(q, i, ar)].push(q), p), [[], []]);
fuente
Terminé haciendo esto porque es fácil de entender (y está completamente mecanografiado).
const partition = <T>(array: T[], isValid: (element: T) => boolean): [T[], T[]] => { const pass: T[] = [] const fail: T[] = [] array.forEach(element => { if (isValid(element)) { pass.push(element) } else { fail.push(element) } }) return [pass, fail] } // usage const [pass, fail] = partition([1, 2, 3, 4, 5], (element: number) => element > 3)
fuente