Contar el número de coincidencias de una expresión regular en Javascript

98

Quería escribir una expresión regular para contar la cantidad de espacios / pestañas / nueva línea en un fragmento de texto. Así que ingenuamente escribí lo siguiente:

numSpaces : function(text) { 
    return text.match(/\s/).length; 
}

Por alguna razón desconocida, siempre vuelve 1. ¿Cuál es el problema con la declaración anterior? Desde entonces he resuelto el problema con lo siguiente: -

numSpaces : function(text) { 
    return (text.split(/\s/).length -1); 
}
wai
fuente

Respuestas:

191

tl; dr: Contador de patrones genéricos

// THIS IS WHAT YOU NEED
const count = (str) => {
  const re = /YOUR_PATTERN_HERE/g
  return ((str || '').match(re) || []).length
}

Para aquellos que llegaron aquí buscando una forma genérica de contar el número de ocurrencias de un patrón de expresión regular en una cadena, y no quieren que falle si hay cero ocurrencias, este código es lo que necesita. He aquí una demostración:

/*
 *  Example
 */

const count = (str) => {
  const re = /[a-z]{3}/g
  return ((str || '').match(re) || []).length
}

const str1 = 'abc, def, ghi'
const str2 = 'ABC, DEF, GHI'

console.log(`'${str1}' has ${count(str1)} occurrences of pattern '/[a-z]{3}/g'`)
console.log(`'${str2}' has ${count(str2)} occurrences of pattern '/[a-z]{3}/g'`)

Respuesta original

El problema con su código inicial es que le falta el identificador global :

>>> 'hi there how are you'.match(/\s/g).length;
4

Sin la gparte de la expresión regular, solo coincidirá con la primera aparición y se detendrá allí.

También tenga en cuenta que su expresión regular contará los espacios sucesivos dos veces:

>>> 'hi  there'.match(/\s/g).length;
2

Si eso no es deseable, puede hacer esto:

>>> 'hi  there'.match(/\s+/g).length;
1
Paolo Bergantino
fuente
5
Esto funciona siempre que tenga al menos un espacio en su entrada. De lo contrario, match () devuelve molestamente null.
sfink
3
sfink tiene razón, definitivamente desea verificar si match () devolvió nulo:var result = text.match(/\s/g); return result ? result.length : 0;
Gras Double
37
También puede protegerse contra el nulo utilizando esta construcción:( str.match(...) || [] ).length
a'r
11

Como mencioné en mi respuesta anterior , puede usar RegExp.exec()para iterar sobre todas las coincidencias y contar cada ocurrencia; la ventaja se limita solo a la memoria, porque en general es aproximadamente un 20% más lento que el uso String.match().

var re = /\s/g,
count = 0;

while (re.exec(text) !== null) {
    ++count;
}

return count;
Jack
fuente
2

('my string'.match(/\s/g) || []).length;

Weston Ganger
fuente
1
Creo que pusiste el || []en el lugar equivocado, debería ser('my string'.match(/\s/g) || []).length
woojoo666
0

Sin duda, esto es algo que tiene muchas trampas. Estaba trabajando con la respuesta de Paolo Bergantino y me di cuenta de que incluso eso tiene algunas limitaciones. Trabajar con representaciones de cadenas de fechas me pareció un buen lugar para encontrar rápidamente algunos de los problemas principales. Comience con una cadena de entrada como esta: '12-2-2019 5:1:48.670'

y configura la función de Paolo así:

function count(re, str) {
    if (typeof re !== "string") {
        return 0;
    }
    re = (re === '.') ? ('\\' + re) : re;
    var cre = new RegExp(re, 'g');
    return ((str || '').match(cre) || []).length;
}

Quería que se pasara la expresión regular, para que la función sea más reutilizable, en segundo lugar, quería que el parámetro fuera una cadena, para que el cliente no tenga que hacer la expresión regular, sino que simplemente coincida con la cadena, como un método de clase de utilidad de cadena estándar.

Ahora, aquí puede ver que estoy lidiando con problemas con la entrada. Con lo siguiente:

if (typeof re !== "string") {
    return 0;
}

Estoy asegurando que la entrada no es nada parecido a lo literal 0, false, undefined, o null, ninguno de los cuales son cadenas. Dado que estos literales no están en la cadena de entrada, no debería haber coincidencias, pero debería coincidir '0', que es una cadena.

Con lo siguiente:

re = (re === '.') ? ('\\' + re) : re;

Estoy lidiando con el hecho de que el constructor de RegExp interpretará (creo que incorrectamente) la cadena '.'como el coincidente de todos los caracteres\.\

Finalmente, debido a que estoy usando el constructor RegExp, necesito darle la 'g'bandera global para que cuente todas las coincidencias, no solo la primera, similar a las sugerencias en otras publicaciones.

Me doy cuenta de que esta es una respuesta extremadamente tardía, pero podría ser útil para alguien que se tropieza por aquí. Por cierto, aquí está la versión de TypeScript:

function count(re: string, str: string): number {
    if (typeof re !== 'string') {
        return 0;
    }
    re = (re === '.') ? ('\\' + re) : re;
    const cre = new RegExp(re, 'g');    
    return ((str || '').match(cre) || []).length;
}
Michael Coxon
fuente
-2

¿Qué tal esto?

function isint(str){
    if(str.match(/\d/g).length==str.length){
        return true;
    }
    else {
         return false
    }
}
anders
fuente