Eu tenho uma matriz Javascript que gostaria de dividir em duas com base em se uma função chamada em cada elemento retorna true
ou false
. Essencialmente, este é um array.filter
, mas eu gostaria de também ter em mãos os elementos que foram filtrados para fora .
Atualmente, meu plano é usar array.forEach
e chamar a função de predicado em cada elemento. Dependendo se isso é verdadeiro ou falso, colocarei o elemento atual em um dos dois novos arrays. Existe uma maneira mais elegante ou melhor de fazer isso? Uma array.filter
em que o vai empurrar o elemento para uma outra matriz antes de retornar false
, por exemplo?
javascript
Mike Chen
fonte
fonte
.filter
mas esses efeitos colaterais são difíceis de rastrear e entender. Basta iterar sobre o array e enviar para um array ou outro.Respostas:
Com ES6, você pode usar a sintaxe de propagação com reduzir:
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);
Ou em uma única linha:
const [pass, fail] = a.reduce(([p, f], e) => (e > 5 ? [[...p, e], f] : [p, [...f, e]]), [[], []]);
fonte
Você pode 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']]
ou 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' } ]
fonte
Você pode usar reduzir para isso:
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; }, [[],[]] ); };
Atualizar. Usando a sintaxe ES6, você também pode fazer isso usando recursão:
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]]); }
fonte
[...left, current]
ou[...right, current]
- para cada elemento. Não sei exatamente o que é interno, mas tenho certeza de que a construção é mais cara do que simplesmente inserir um elemento em um array. Além disso, como regra geral, a recursão é mais cara do que iterar , porque envolve a criação de um "quadro de pilha" a cada vez.Isso soa muito semelhante ao método de Ruby
Enumerable#partition
.Se a função não pode ter efeitos colaterais (ou seja, não pode alterar o array original), então não há maneira mais eficiente de particionar o array do que iterar cada elemento e enviar o elemento para um de seus dois arrays.
Dito isso, é indiscutivelmente mais "elegante" criar um método
Array
para executar essa função. Neste exemplo, a função de filtro é executada no contexto do array original (ou seja,this
será o array original) e recebe o elemento e o índice do elemento como argumentos (semelhante ao método 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 ] ]
fonte
Eu vim com esse carinha. Ele usa para todos e cada um como você descreveu, mas parece limpo e sucinto na minha opinião.
//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);
fonte
for
loop simples, como em uma resposta antiga de @qwertymk. Por exemplo, para um array contendo 100.000 elementos, era duas vezes mais lento no meu sistema.Na função de filtro, você pode enviar seus itens falsos para outra variável fora da função:
var bad = [], good = [1,2,3,4,5]; good = good.filter(function (value) { if (value === false) { bad.push(value) } else { return true});
Claro que
value === false
precisa ser uma comparação real;)Mas ele faz quase a mesma operação como
forEach
. Eu acho que você deve usarforEach
para melhor legibilidade do código.fonte
Fácil de ler.
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)
fonte
Experimente isto:
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; }
DEMO
fonte
Que tal isso?
[1,4,3,5,3,2].reduce( (s, x) => { s[ x > 3 ].push(x); return s;} , {true: [], false:[]} )
Provavelmente, isso é mais eficiente que o operador de espalhamento
Ou um pouco mais curto, mas mais feio
[1,4,3,5,3,2].reduce( (s, x) => s[ x > 3 ].push(x)?s:s , {true: [], false:[]} )
fonte
Muitas respostas aqui são usadas
Array.prototype.reduce
para construir um acumulador mutável e, com razão, apontam que, para grandes arrays, isso é mais eficiente do que, digamos, usar um operador de propagação para copiar um novo array a cada iteração. A desvantagem é que não é tão bonito quanto uma expressão "pura" usando a sintaxe lambda curta.Mas uma maneira de contornar isso é usar o operador vírgula. Em linguagens como o C, a vírgula é um operador que sempre retorna o operando à direita. Você pode usar isso para criar uma expressão que chama uma função void e retorna um valor.
function partition(array, predicate) { return array.reduce((acc, item) => predicate(item) ? (acc[0].push(item), acc) : (acc[1].push(item), acc), [[], []]); }
Se você tirar vantagem do fato de que uma expressão booleana é convertida implicitamente em um número como 0 e 1, pode torná-la ainda mais concisa, embora eu não ache que seja tão legível:
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']
fonte
Partição ONE-LINER
const partition = (a,f)=>a.reduce((p,q)=>(p[+!f(q)].push(q),p),[[],[]]);
DEMO
// 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 texto datilografado
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), [[], []]);
fonte
Acabei fazendo isso porque é fácil de entender (e totalmente digitado com texto digitado).
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)
fonte