JavaScript - Como particionar um array?

Como particionar (ou dividir) um array em JavaScript puro baseado em um filtro condicional


TL;DR: Você pode pular diretamente para a função partition que teremos implementado ao fim do artigo, ela será utilizada dessa forma (ou através do Array.prototype.partition):

const [filteredIn, filteredOut] = partition(array, condition);

Motivação

Recentemente uma colega se deparou com o seguinte problema: ela tinha uma função que filtrava um array e utilizava esse resultado para continuar o processamento, mas agora surgiu um novo requisito em que ela também precisaria dos itens que não passavam na condição do filtro.

Ou seja, ela precisava dos resultados de array.filter(x => x) e array.filter(x => !x) divido em dois arrays para processamentos distintos, (semelhante ao que o lodash#partition faz).

Vou compartilhar aqui a solução que compartilhei com ela, sem utilizar mais de um loop ou ficar recriando o array — com JavaScript puro.

Implementação

Como o objetivo aqui é didático, vou optar por nomenclatura e alternativas mais fáceis de compreender, mas não necessariamente as mais performáticas.

A primeira coisa que precisamos é definir o que a nossa função precisa para particionar um array — a função condicional, além da lista propriamente dita.

const partition = (array, condition) => {};

Precisamos iterar a lista e a cada iteração, precisamos aplicar o condicional e decidir se inserimos no array dos que passam no condicional e dos que não. E, após isso, precisamos retornar esses dois arrays resultantes.

// para cada item do array
if (condition(item)) verdadeiros.push(item);
else falsos.push(item);

Nesse caso em listas maiores, o reduce apresenta a melhor relação de legibilidade e performance, então vamos optar por ele em vez de um for-loop. Além disso, vamos fazer uso do operador vírgula, que avalia o valor das expressões da esquerda para a direita e retorna o valor da última expressão.

const partition = (array, condition) => {
return array.reduce(
(result, item) =>
condition(item)
? (result[0].push(item), result)
: (result[1].push(item), result),
[[], []]
);
};

Agora já é possível particionar arrays! 🙂

No entanto, estamos fazendo uma repetição de código: tanto o if quanto o else possuem comportamentos muito similares, alterando somente a posição em que o item é inserido.

Para melhorar essa parte podemos nos aproveitar da natureza dos booleanos e utilizar o próprio resultado do condicional para estabelecer onde o item precisa ser inserido: +!condition(item) vai resultar justamente em 0 ou 1 e podemos utilizar isso para o índice do array.

Refatorando, fica assim:

const partition = (array, condition) => {
return array.reduce(
(result, item) => (result[+!condition(item)].push(item), result),
[[], []]
);
};

Utilizando

Para utilizar a função é bastante simples, passamos a lista a ser particionada e a condição que define a divisão.

Suponha um array de testes com o status de aprovação ou reprovação em uma prova:

const testes = [
{ id: 1, status: "A" },
{ id: 2, status: "A" },
{ id: 3, status: "R" },
{ id: 4, status: "R" },
{ id: 5, status: "A" },
];

Como podemos dividir os alunos entre aprovados e reprovados?

const [aprovados, reprovados] = partition(
testes,
(teste) => teste.status === "A"
);
// 5 testes.length
// 3 aprovados.length
// 2 reprovados.length

E pronto, resolvido. E pode ser passada qualquer condição, assim como ocorre com as outras funções utilitárias do Array.

Resultado

No fim, nossa função utilitária para particionar um array fica assim:

/**
* Particiona um array baseado em uma condição.
* @param {Array} array - O array a ser particionado.
* @param {function} condition - A condição a ser aplicada no array.
* @return {Array} Array contendo outros arrays particionados pela condição.
*/

const partition = (array, condition) =>
array.reduce(
(result, item) => (result[+!condition(item)].push(item), result),
[[], []]
);

Encerramento

Uma alternativa elegante para alguns projetos pode ser estender o próprio Array, adicionando o método partition nele, modificando a API para manter a utilização exatamente como nas outras funções (map, filter, reduce, ...).

Você também pode ler mais sobre o Operador Vírgula, Array.prototype.reduce e Array.prototype.filter na MDN.

Obrigada por chegar até aqui! Espero que tenha sido uma leitura agradável e qualquer dúvida estou a disposição.

A imagem da capa é cortesia do Pankaj Patel no Unsplash.

Até a próxima!