¿Cómo cuento el número de ocurrencias de un char en una cadena?

547

Tengo la cuerda

a.b.c.d

Quiero contar las ocurrencias de '.' de manera idiomática, preferiblemente de una sola línea.

(Anteriormente había expresado esta restricción como "sin bucle", en caso de que se pregunte por qué todos intentan responder sin usar un bucle).

Bart
fuente
1
¿Deberes? Porque de lo contrario no veo el requisito para evitar el bucle.
PhiLho
22
No es contrario a un bucle tanto como buscar una frase idiomática.
Bart
2
Los bucles se crearon para un problema como este, escríbalo en una clase de utilidad común y luego llame a su nuevo revestimiento.
che javara
Pregunta similar para cadenas: stackoverflow.com/questions/767759/…
koppor
Solo para señalar: aprecio encontrar las frases ingeniosas, es divertido y (como una verdadera ventaja) a menudo fácil de recordar, pero me gustaría señalar que un método separado y un bucle es mejor en casi todos los sentidos. - facilidad de lectura e incluso rendimiento. La mayoría de las soluciones "elegantes" a continuación no funcionarán muy bien porque involucran reformar cadenas / copiar memoria, mientras que un bucle que simplemente escanea la cadena y cuenta las ocurrencias sería rápido y simple. No es que el rendimiento en general sea un factor, pero no mire la línea en un bucle y suponga que funcionará mejor.
Bill K

Respuestas:

722

Mi 'frase idiomática' para esto es:

int count = StringUtils.countMatches("a.b.c.d", ".");

¿Por qué escribirlo usted mismo cuando ya está en commons lang ?

El marco de Spring Framework para esto es:

int occurance = StringUtils.countOccurrencesOf("a.b.c.d", ".");
Cowan
fuente
44
Equivalente a la guayaba : int count = CharMatcher.is('.').countIn("a.b.c.d");... Como respondió dogbane en una pregunta duplicada.
Jonik
25
Aunque no voy a rechazar esto, (a) requiere libs de terceros y (b) es costoso.
javadba
Este solo trabajo con marco de primavera tiene que importar.
Isuru Madusanka
19
Lo que ha sido costoso, en cada compañía en la que he trabajado, es tener muchas clases "* Utils" mal escritas y mal mantenidas. Parte de su trabajo es saber qué hay disponible en Apache Commons.
AbuNassar
1016

Qué tal esto. No usa regexp debajo, por lo que debería ser más rápido que algunas de las otras soluciones y no usará un bucle.

int count = line.length() - line.replace(".", "").length();
Andreas Wederbrand
fuente
122
La manera más fácil. Uno inteligente. Y funciona en Android, donde no hay clase
StringUtils
43
Esta es la mejor respuesta. La razón por la que es la mejor es porque no tiene que importar otra biblioteca.
Alex Spencer
27
Muy práctico pero feo como el infierno. No lo recomiendo, ya que conduce a un código confuso.
Daniel San
32
El código feo puede minimizarse convirtiéndolo en un método en su propia clase "StringUtils". Entonces el código feo está exactamente en un lugar, y en cualquier otro lugar es muy fácil de leer.
RonR
30
El método de bucle es mucho más rápido que esto. Especialmente cuando se quiere contar un char en lugar de un String (ya que no existe el método String.replace (char, char)). En una cadena de 15 caracteres, obtengo una diferencia de 6049 ns frente a 26,739 ns (con un promedio de más de 100 ejecuciones). Los números sin procesar son una gran diferencia, pero el porcentaje sabio ... se suma. Evite las asignaciones de memoria: ¡use un bucle!
Ben
282

Resuma otra respuesta y lo que sé todas las formas de hacer esto usando una línea:

   String testString = "a.b.c.d";

1) Uso de Apache Commons

int apache = StringUtils.countMatches(testString, ".");
System.out.println("apache = " + apache);

2) Usando Spring Framework's

int spring = org.springframework.util.StringUtils.countOccurrencesOf(testString, ".");
System.out.println("spring = " + spring);

3) Usando reemplazar

int replace = testString.length() - testString.replace(".", "").length();
System.out.println("replace = " + replace);

4) Uso de replaceAll (caso 1)

int replaceAll = testString.replaceAll("[^.]", "").length();
System.out.println("replaceAll = " + replaceAll);

5) Uso de replaceAll (caso 2)

int replaceAllCase2 = testString.length() - testString.replaceAll("\\.", "").length();
System.out.println("replaceAll (second case) = " + replaceAllCase2);

6) Usando split

int split = testString.split("\\.",-1).length-1;
System.out.println("split = " + split);

7) Uso de Java8 (caso 1)

long java8 = testString.chars().filter(ch -> ch =='.').count();
System.out.println("java8 = " + java8);

8) Usar Java8 (caso 2), puede ser mejor para Unicode que el caso 1

long java8Case2 = testString.codePoints().filter(ch -> ch =='.').count();
System.out.println("java8 (second case) = " + java8Case2);

9) Usando StringTokenizer

int stringTokenizer = new StringTokenizer(" " +testString + " ", ".").countTokens()-1;
System.out.println("stringTokenizer = " + stringTokenizer);

Del comentario : Tenga cuidado con el StringTokenizer, para abcd funcionará pero para un ... bc ... d o ... abcd o a .... b ...... c ..... d ... o etc. no funcionará. Simplemente contará para. entre personajes solo una vez

Más información en github

Prueba de rendimiento (usando JMH , mode = AverageTime, puntaje 0.010mejor entonces 0.351):

Benchmark              Mode  Cnt  Score    Error  Units
1. countMatches        avgt    5  0.010 ±  0.001  us/op
2. countOccurrencesOf  avgt    5  0.010 ±  0.001  us/op
3. stringTokenizer     avgt    5  0.028 ±  0.002  us/op
4. java8_1             avgt    5  0.077 ±  0.005  us/op
5. java8_2             avgt    5  0.078 ±  0.003  us/op
6. split               avgt    5  0.137 ±  0.009  us/op
7. replaceAll_2        avgt    5  0.302 ±  0.047  us/op
8. replace             avgt    5  0.303 ±  0.034  us/op
9. replaceAll_1        avgt    5  0.351 ±  0.045  us/op
Viacheslav Vedenin
fuente
Las cadenas impresas no coinciden con las anteriores, y el orden es más rápido primero, lo que hace que la búsqueda sea difícil al menos. Buena respuesta de todos modos!
Maarten Bodewes
caso 2, generalizado para puntos de código que necesitan más de una unidad de código UTF-16:"1🚲2🚲3 has 2".codePoints().filter((c) -> c == "🚲".codePointAt(0)).count()
Tom Blodget
174

Tarde o temprano, algo tiene que repetirse. Es mucho más simple para usted escribir el ciclo (muy simple) que usar algo como lo splitque es mucho más poderoso de lo que necesita.

Por supuesto, encapsule el bucle en un método separado, p. Ej.

public static int countOccurrences(String haystack, char needle)
{
    int count = 0;
    for (int i=0; i < haystack.length(); i++)
    {
        if (haystack.charAt(i) == needle)
        {
             count++;
        }
    }
    return count;
}

Entonces no necesita tener el bucle en su código principal, pero el bucle debe estar allí en alguna parte.

Jon Skeet
fuente
55
for (int i = 0, l = haystack.length (); i <l; i ++) se amable con tu stack
Chris
12
(Ni siquiera estoy seguro de dónde viene el bit "stack" del comentario. No es como si esta respuesta fuera mi recursiva, lo que de hecho es desagradable para la pila).
Jon Skeet
2
no solo eso, sino que posiblemente sea una anti optimización sin echar un vistazo a lo que hace el jit. Si hiciste lo anterior en una matriz de bucle, por ejemplo, podrías empeorar las cosas.
ShuggyCoUk
44
@sulai: La preocupación de Chris no tiene fundamento, en mi opinión, frente a una optimización trivial de JIT. ¿Hay alguna razón por la que ese comentario llamó su atención en este momento, más de tres años después? Solo interesado.
Jon Skeet
1
Probablemente @sulai acaba de encontrar la pregunta como lo hice yo (mientras me preguntaba si Java tenía un método incorporado para esto) y no notó las fechas. Sin embargo, tengo curiosidad de cómo mover la length()llamada exterior del bucle podría hacer que el rendimiento es peor , como se ha mencionado por @ShuggyCoUk algunos comentarios hacia arriba.
JKillian
63

Tuve una idea similar a Mladen, pero todo lo contrario ...

String s = "a.b.c.d";
int charCount = s.replaceAll("[^.]", "").length();
println(charCount);
PhiLho
fuente
Correcto. Reemplazar todo (".") Reemplazaría cualquier carácter, no solo punto. Reemplazar todo ("\\.") Habría funcionado. Su solución es más sencilla.
VonC
jjnguy realmente había sugerido un replaceAll ("[^.]") primero, al ver mi solución "abcd" .split ("\\."). length-1. Pero después de ser golpeado 5 veces, borré mi respuesta (y su comentario).
VonC
"... ahora tienes dos problemas" (oblig.) De todos modos, apuesto a que hay decenas de bucles ejecutándose replaceAll()y length(). Bueno, si no es visible, no existe; o)
Piskvor salió del edificio el
2
No creo que sea una buena idea usar expresiones regulares y crear una nueva cadena para el conteo. Simplemente crearía un método estático que repita cada carácter en la cadena para contar el número.
mingfai
1
@mingfai: de hecho, pero la pregunta original es sobre hacer una línea, e incluso, sin un bucle (puedes hacer un bucle en una línea, ¡pero será feo!). Pregunta la pregunta, no la respuesta ... :-)
PhiLho
37
String s = "a.b.c.d";
int charCount = s.length() - s.replaceAll("\\.", "").length();

Reemplazar todo (".") Reemplazaría todos los caracteres.

La solución de PhiLho utiliza Reemplazar todo ("[^.]", ""), Que no necesita ser escapado, ya que [.] Representa el carácter 'punto', no 'ningún carácter'.

Mladen Prajdic
fuente
Me gusta este. Todavía hay un bucle allí, por supuesto, ya que tiene que haberlo.
El arquetípico Paul
NB que necesitaría dividir este número si desea buscar subcadenas de longitud> 1
rogerdpack
30

Mi solución 'idiomática de una línea':

int count = "a.b.c.d".length() - "a.b.c.d".replace(".", "").length();

No tengo idea de por qué se acepta una solución que utiliza StringUtils.

mlchen850622
fuente
44
Hay una solución más antigua similar a esta en esta publicación.
JCalcines
77
Porque esta solución es realmente ineficiente
András
Esto crea una cadena adicional solo para producir un recuento. No tengo idea de por qué alguien preferiría esto sobre StringUtils si StringUtils es una opción. Si no es una opción, deberían crear un bucle for simple en una clase de utilidad.
enamorado
28
String s = "a.b.c.d";
long result = s.chars().filter(ch -> ch == '.').count();
fubo
fuente
1
Vote + por una solución nativa.
Scadge
24

Un ejemplo más corto es

String text = "a.b.c.d";
int count = text.split("\\.",-1).length-1;
Peter Lawrey
fuente
3
Este parece tener una sobrecarga relativamente grande, tenga en cuenta que puede crear muchas cadenas pequeñas. Normalmente eso no importa mucho, pero úsalo con cuidado.
Maarten Bodewes
19

Aquí hay una solución sin bucle:

public static int countOccurrences(String haystack, char needle, int i){
    return ((i=haystack.indexOf(needle, i)) == -1)?0:1+countOccurrences(haystack, needle, i+1);}


System.out.println("num of dots is "+countOccurrences("a.b.c.d",'.',0));

bueno, hay un bucle, pero es invisible :-)

- Yonatan

Yonatan Maman
fuente
2
A menos que su cadena sea tan larga, obtendrá un OutOfMemoryError.
Spencer Kormos el
El problema parece lo suficientemente artificial como para ser tarea, y si es así, esta recurrencia es probablemente la respuesta que se le pide que encuentre.
Erickson
Eso usa indexOf, que se repetirá ... pero es una buena idea. Publicar una solución verdaderamente "recursiva" en un minuto ...
Jon Skeet
Si tiene más ocurrencias que sus ranuras de pila disponibles, tendrá una excepción de desbordamiento de pila;)
Luca C.
15

No me gusta la idea de asignar una nueva cadena para este propósito. Y como la cadena ya tiene una matriz de caracteres en la parte posterior donde almacena su valor, String.charAt () es prácticamente gratis.

for(int i=0;i<s.length();num+=(s.charAt(i++)==delim?1:0))

hace el truco, sin asignaciones adicionales que necesitan colección, en 1 línea o menos, con solo J2SE.

0xCAFEBABE
fuente
Dando un poco de amor a este porque es el único que hace un solo paso sobre la cuerda. Me importa el rendimiento.
javadba
1
charAtitera a través de puntos de código de 16 bits, no caracteres! A charen Java no es un personaje. Entonces, esta respuesta implica que no debe haber un símbolo Unicode con un sustituto alto que sea igual al punto de código de delim. No estoy seguro de si es correcto para el punto, pero en general podría no ser correcto.
ceving
14

Bien, inspirado en la solución de Yonatan, aquí hay uno que es puramente recursivo: los únicos métodos de biblioteca utilizados son length()y charAt()ninguno de los cuales realiza ningún bucle:

public static int countOccurrences(String haystack, char needle)
{
    return countOccurrences(haystack, needle, 0);
}

private static int countOccurrences(String haystack, char needle, int index)
{
    if (index >= haystack.length())
    {
        return 0;
    }

    int contribution = haystack.charAt(index) == needle ? 1 : 0;
    return contribution + countOccurrences(haystack, needle, index+1);
}

Si la recursividad cuenta como bucle depende de la definición exacta que use, pero probablemente sea lo más cerca que pueda estar.

No sé si la mayoría de las JVM hacen recursión de cola en estos días ... si no, obtendrá el desbordamiento epónimo de pila para cadenas adecuadamente largas, por supuesto.

Jon Skeet
fuente
No, la recursión de cola probablemente estará en Java 7, pero aún no está muy extendida. Esta simple recursión de cola directa podría traducirse en un bucle en tiempo de compilación, pero las cosas de Java 7 están realmente integradas en la JVM para manejar el encadenamiento a través de diferentes métodos.
erickson
3
Es más probable que obtenga una recursión de cola si su método devuelve una llamada a sí mismo (incluido un parámetro de ejecución total), en lugar de devolver el resultado de realizar una adición.
Stephen Denne
12

Inspirado por Jon Skeet, una versión sin bucle que no volará tu stack. También es un punto de partida útil si desea utilizar el framework fork-join.

public static int countOccurrences(CharSequeunce haystack, char needle) {
    return countOccurrences(haystack, needle, 0, haystack.length);
}

// Alternatively String.substring/subsequence use to be relatively efficient
//   on most Java library implementations, but isn't any more [2013].
private static int countOccurrences(
    CharSequence haystack, char needle, int start, int end
) {
    if (start == end) {
        return 0;
    } else if (start+1 == end) {
        return haystack.charAt(start) == needle ? 1 : 0;
    } else {
        int mid = (end+start)>>>1; // Watch for integer overflow...
        return
            countOccurrences(haystack, needle, start, mid) +
            countOccurrences(haystack, needle, mid, end);
    }
}

(Descargo de responsabilidad: no probado, no compilado, no sensible).

Quizás la mejor manera (de un solo subproceso, sin soporte de par sustituto) para escribirlo:

public static int countOccurrences(String haystack, char needle) {
    int count = 0;
    for (char c : haystack.toCharArray()) {
        if (c == needle) {
           ++count;
        }
    }
    return count;
}
Tom Hawtin - tackline
fuente
11

No estoy seguro de la eficacia de esto, pero es el código más corto que podría escribir sin traer libs de terceros:

public static int numberOf(String target, String content)
{
    return (content.split(target).length - 1);
}
KannedFarU
fuente
44
También a contar las apariciones al final de la cadena que tendrá que llamar dividida con un argumento límite negativo de esta manera: return (content.split(target, -1).length - 1);. Por omisión, las ocurrencias al final de la cadena se omiten en la matriz resultante de split (). Ver el Doku
vlz
10

Con También podría usar transmisiones para lograr esto. Obviamente hay una iteración detrás de escena, ¡pero no tienes que escribirla explícitamente!

public static long countOccurences(String s, char c){
    return s.chars().filter(ch -> ch == c).count();
}

countOccurences("a.b.c.d", '.'); //3
countOccurences("hello world", 'l'); //3
Alexis C.
fuente
Usar en .codePoints()lugar de .chars()sería compatible con cualquier valor Unicode (incluidos los que requieren pares sustitutos)
Luke Usherwood
10

También es posible usar reduce en Java 8 para resolver este problema:

int res = "abdsd3$asda$asasdd$sadas".chars().reduce(0, (a, c) -> a + (c == '$' ? 1 : 0));
System.out.println(res);

Salida:

3
gil.fernandes
fuente
8

Muestra completa:

public class CharacterCounter
{

  public static int countOccurrences(String find, String string)
  {
    int count = 0;
    int indexOf = 0;

    while (indexOf > -1)
    {
      indexOf = string.indexOf(find, indexOf + 1);
      if (indexOf > -1)
        count++;
    }

    return count;
  }
}

Llamada:

int occurrences = CharacterCounter.countOccurrences("l", "Hello World.");
System.out.println(occurrences); // 3
Benny Neugebauer
fuente
el código incorrecto no funciona cuando intento int ocurrencias = CharacterCounter.countOccurrences ("1", "101"); System.out.println (ocurrencias); // 1
jayesh
Cometo una solución para el código que funciona con la misma lógica
MaanooAk
8

La forma más sencilla de obtener la respuesta es la siguiente:

public static void main(String[] args) {
    String string = "a.b.c.d";
    String []splitArray = string.split("\\.",-1);
    System.out.println("No of . chars is : " + (splitArray.length-1));
}
Amar Magar
fuente
2
Este fragmento no devuelve la cantidad correcta de puntos para una entrada dada "abc"
dekaru
@dekaru ¿Podría pegar su aguijón en el comentario para que podamos echar un vistazo?
Amar Magar
5

En caso de que esté usando Spring Framework, también puede usar la clase "StringUtils". El método sería "countOccurrencesOf".

usuario496208
fuente
5

Puede usar la split()función en un solo código de línea

int noOccurence=string.split("#",-1).length-1;
usuario3322553
fuente
Split realmente crea la matriz de cadenas, lo que consume mucho tiempo.
Palec
Tienes razón, esa es una verdadera preocupación. De otra manera, evita traer una biblioteca de terceros en su proyecto (si aún no lo ha hecho). Depende de lo que quiera hacer y de las expectativas de rendimiento.
Benj
3
Esta solución NO incluirá los resultados vacíos finales, porque el argumento limitse establece en cero en esta llamada al método dividido sobrecargado. Un ejemplo: "1##2#3#####".split("#")solo producirá una matriz de tamaño 4 ( [0:"1";1:""; 2:"2"; 3:"3"]) en lugar de tamaño 9 ( [0:"1"; 1:""; 2:"2"; 3:"3"; 4:""; 5:""; 6:""; 7:""; 8:""]).
klaar
4
public static int countOccurrences(String container, String content){
    int lastIndex, currIndex = 0, occurrences = 0;
    while(true) {
        lastIndex = container.indexOf(content, currIndex);
        if(lastIndex == -1) {
            break;
        }
        currIndex = lastIndex + content.length();
        occurrences++;
    }
    return occurrences;
}
Más duro
fuente
4
import java.util.Scanner;

class apples {

    public static void main(String args[]) {    
        Scanner bucky = new Scanner(System.in);
        String hello = bucky.nextLine();
        int charCount = hello.length() - hello.replaceAll("e", "").length();
        System.out.println(charCount);
    }
}//      COUNTS NUMBER OF "e" CHAR´s within any string input
kassim
fuente
3

Si bien los métodos pueden ocultarlo, no hay forma de contar sin un bucle (o recursividad). Sin embargo, desea utilizar un char [] por razones de rendimiento.

public static int count( final String s, final char c ) {
  final char[] chars = s.toCharArray();
  int count = 0;
  for(int i=0; i<chars.length; i++) {
    if (chars[i] == c) {
      count++;
    }
  }
  return count;
}

El uso de replaceAll (es decir, RE) no parece ser la mejor manera de hacerlo.

tcurdt
fuente
Creo que esta es la solución más elegante. ¿Por qué usaste toCharArray y no CharAt directamente?
Panayotis
Looping con charAt al menos solía ser más lento. También puede depender de la plataforma. La única forma de averiguarlo realmente sería medir la diferencia.
tcurdt
3

Bueno, con una tarea bastante similar me topé con este hilo. No vi ninguna restricción de lenguaje de programación y desde que groovy se ejecuta en un java vm: Así es como pude resolver mi problema usando Groovy.

"a.b.c.".count(".")

hecho.

Christoph Zabinski
fuente
3

Una solución mucho más fácil sería dividir la cadena en función del carácter con el que coincida.

Por ejemplo,

int getOccurences(String characters, String string) { String[] words = string.split(characters); return words.length - 1; }

Esto devolverá 4 en el caso de: getOccurences("o", "something about a quick brown fox");

Saharcasm
fuente
El problema aquí es que se debe asignar una matriz, que es terriblemente lenta.
Palec
2

En algún lugar del código, algo tiene que repetirse. La única forma de evitar esto es desenrollar completamente el bucle:

int numDots = 0;
if (s.charAt(0) == '.') {
    numDots++;
}

if (s.charAt(1) == '.') {
    numDots++;
}


if (s.charAt(2) == '.') {
    numDots++;
}

... etc, pero entonces usted es el que realiza el bucle, manualmente, en el editor de origen, en lugar de la computadora que lo ejecutará. Ver el pseudocódigo:

create a project
position = 0
while (not end of string) {
    write check for character at position "position" (see above)
}
write code to output variable "numDots"
compile program
hand in homework
do not think of the loop that your "if"s may have been optimized and compiled to
Piskvor salió del edificio
fuente
2

Aquí hay una solución de recursión de estilo ligeramente diferente:

public static int countOccurrences(String haystack, char needle)
{
    return countOccurrences(haystack, needle, 0);
}

private static int countOccurrences(String haystack, char needle, int accumulator)
{
    if (haystack.length() == 0) return accumulator;
    return countOccurrences(haystack.substring(1), needle, haystack.charAt(0) == needle ? accumulator + 1 : accumulator);
}
Stephen Denne
fuente
2

¿Por qué no simplemente dividir en el personaje y luego obtener la longitud de la matriz resultante? la longitud de la matriz siempre será número de instancias + 1. ¿Correcto?

Darryl Price
fuente
2

El siguiente código fuente le dará no. De ocurrencias de una cadena dada en una palabra ingresada por el usuario: -

import java.util.Scanner;

public class CountingOccurences {

    public static void main(String[] args) {

        Scanner inp= new Scanner(System.in);
        String str;
        char ch;
        int count=0;

        System.out.println("Enter the string:");
        str=inp.nextLine();

        while(str.length()>0)
        {
            ch=str.charAt(0);
            int i=0;

            while(str.charAt(i)==ch)
            {
                count =count+i;
                i++;
            }

            str.substring(count);
            System.out.println(ch);
            System.out.println(count);
        }

    }
}
Shubham
fuente
2
int count = (line.length() - line.replace("str", "").length())/"str".length();
Shaban
fuente