Não tinha pensado desse modo, ótima sua implementação! Eu reescrevi ela de uma forma mais concisa, mas acho que não perderia muita coisa de performance:
const isLetter = char => /[a-z]/i.test(char)
const isNumber = char => /[0-9]/.test(char)
const isSpecial = char => /[!@#$%]/.test(char)
function filterChars(text, filters) {
const result = text.split('').filter(char => {
for (const filter of filters) {
if (filter(char)) return true
}
return false
})
return result.join('')
}
const alphabet = 'abcde123456!@#$%'
console.log(filterChars(alphabet, [isLetter, isNumber])) // abcde123456
console.log(filterChars(alphabet, [isLetter, isSpecial])) // abcde!@#$%
console.log(filterChars(alphabet, [isNumber, isSpecial])) // 123456!@#$%
tipado:
type FilterFn = (char: string) => boolean
const isLetter: FilterFn = (char: string) => /[a-z]/i.test(char)
const isNumber: FilterFn = (char: string) => /[0-9]/.test(char)
const isSpecial: FilterFn = (char: string) => /[!@#$%]/.test(char)
function filterChars(text: string, filters: FilterFn[]) {
const result = text.split('').filter(char => {
for (const filter of filters) {
if (filter(char)) return true
}
return false
})
return result.join('')
}
O que você acha?
Além disso, o nome não está bom. Eu entendi que se letter for true, vc não quer remover as letras, e sim mantê-las. Por isso o remove no início dos nomes dos filtros não me parece uma boa.
Aqui foi erro meu na hora de digitar, o certo seria:
.filter(removeLetters) // Filtro deve ser aplicado apenas se `options.letters` for `false`
Eu só não vejo a necessidade de se fazer o split
(que transforma a string em um array), para depois usar o filter
(que cria outro array) e por fim o join
(que junta tudo em uma string). Acho uma volta muito grande, sendo que dá pra fazer apenas com um loop simples pelos caracteres.
Tem outra diferença importante, caso a string tenha caracteres "diferentões", como emojis, veja:
function filterComSplit(text, filters) {
const result = text.split('').filter(char => {
for (const filter of filters) {
if (filter(char))
return true;
}
return false;
});
return result.join('');
}
function filterSemSplit(text, filters) {
var result = '';
for (const char of text) { // para cada caractere do texto
// verifica se ele satisfaz algum filtro
for (const filter of filters) {
if (filter(char)) {
result += char;
break; // se já satisfaz um dos filtros, não preciso verificar os outros
}
}
}
return result;
}
// verifica se é um dos emojis: 💩 ou 😀
function isEmoji(char) {
const codepoint = char.codePointAt(0);
return codepoint == 0x1f4a9 || codepoint == 0x1f600;
}
// sim, pode colocar emoji direto na string (se o editor suportar, claro)
const alphabet = 'olá 💩 etc 😀!';
// usando "for..of" funciona
console.log(filterSemSplit(alphabet, [ isEmoji ])); // 💩😀
// usando split, não funciona
console.log(filterComSplit(alphabet, [ isEmoji ])); // string vazia (não imprime nada)
Se ficou curioso, tem uma explicação detalhada sobre este problema aqui. Claro que se a string só tiver texto "normal", isso não ocorre. Mas vale lembrar que há vários caracteres de outros idiomas que podem dar este mesmo problema.
Por fim, fiz alguns testes com o Benchmark.js e a versão com split
ficou cerca de 40% mais lenta. Isso porque o filter
recebe como parâmetro uma função de callback que é executada para cada um dos caracteres. Claro que para poucas strings pequenas, a diferença será imperceptível (o Benchmark.js roda milhões de casos para ter uma comparação melhor). Mas enfim, o problema nem era "deixar as funções mais concisas", e sim o fato de criar um array, filtrá-lo (criando outro array) e depois juntar tudo de novo.
Quanto a trocar function
por arrow function, a questão nem é "ficar mais conciso". Tem que considerar as diferenças entre uma e outra (ver aqui). Tem casos que não faz diferença, mas tem casos que faz, e esse deveria ser o critério (ser "mais curto" é mero detalhe, e o menos importante neste caso).