Come contare il numero di occorrenze di un carattere in una stringa?


547

Ho la corda

a.b.c.d

Voglio contare le occorrenze di "." in modo idiomatico, preferibilmente un liner.

(In precedenza avevo espresso questo vincolo come "senza loop", nel caso ti stia chiedendo perché tutti stiano cercando di rispondere senza usare un loop).


1
Compiti a casa? Perché altrimenti non vedo il requisito per evitare il ciclo.
PhiLho,

22
Non avverso a un ciclo quanto alla ricerca di un idiomatico one-liner.
Bart,

2
I loop sono stati creati per un problema come questo, scrivi il loop in una classe Utility comune quindi chiama il tuo liner appena coniato.
che javara,

Domanda simile per le stringhe: stackoverflow.com/questions/767759/...
koppor

Solo per sottolineare: apprezzo trovare le linee singole, è divertente e (come un vero vantaggio) spesso facile da ricordare, ma vorrei sottolineare che un metodo separato e un ciclo sono migliori in quasi ogni modo - leggibilità e prestazioni uniformi. La maggior parte delle soluzioni "Eleganti" di seguito non funzioneranno molto bene perché comportano il reforming delle stringhe / la copia della memoria, mentre un ciclo che ha appena scansionato la stringa e contato le occorrenze sarebbe rapido e semplice. Non che le prestazioni dovrebbero generalmente essere un fattore, ma non guardare la linea su un loop e presumere che funzionerà meglio.
Bill K,

Risposte:


722

Il mio "idiomatico monolinea" per questo è:

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

Perché scriverlo da solo quando è già in lang comune ?

L'eleliner di Spring Framework per questo è:

int occurance = StringUtils.countOccurrencesOf("a.b.c.d", ".");

44
Equivalente di guava : int count = CharMatcher.is('.').countIn("a.b.c.d");... Come ha risposto dogbane in una domanda duplicata.
Jonik,

25
Anche se non lo declasserò, è (a) richiedere librerie di terze parti e (b) costoso.
Javavba,

Questo funziona solo con il lavoro con telaio a molla deve essere importato.
Isuru Madusanka,


19
Ciò che è stato costoso, in ogni azienda in cui ho lavorato, sta avendo un sacco di classi "* Utils" male scritte e scarsamente mantenute. Parte del tuo lavoro è sapere cosa è disponibile in Apache Commons.
AbuNassar,

1016

Cosa ne pensi di questo. Non utilizza regexp al di sotto, quindi dovrebbe essere più veloce di alcune delle altre soluzioni e non utilizzerà un ciclo.

int count = line.length() - line.replace(".", "").length();

122
Il modo più semplice Uno intelligente. E funziona su Android, dove non esiste una classe
StringUtils

43
Questa è la risposta migliore Il motivo è il migliore perché non devi importare un'altra libreria.
Alex Spencer,

27
Molto pratico ma brutto da morire. Non lo consiglio perché porta a confondere il codice.
Daniel San

32
Il codice brutto può essere minimizzato rendendolo un metodo nella tua classe "StringUtils". Quindi il brutto codice si trova esattamente in un punto, e ovunque è ben leggibile.
RonR

30
Il metodo loop è molto più veloce di questo. Soprattutto quando si desidera contare un carattere anziché un carattere String (poiché non esiste un metodo String.replace (char, char)). Su una stringa di 15 caratteri, ottengo una differenza di 6049 ns contro 26.739 ns (in media oltre 100 run). I numeri grezzi sono un'enorme differenza, ma la percentuale è saggia ... si somma. Evita le allocazioni di memoria: usa un loop!
Ben

282

Riassumi le altre risposte e ciò che conosco in tutti i modi per farlo utilizzando un one-liner:

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

1) Utilizzo di Apache Commons

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

2) Utilizzo di Spring Framework

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

3) Utilizzo di sostituire

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

4) Utilizzo di ReplaceAll (caso 1)

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

5) Utilizzo di ReplaceAll (caso 2)

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

6) Utilizzo della divisione

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

7) Utilizzo di Java8 (caso 1)

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

8) L'uso di Java8 (caso 2) potrebbe essere migliore per Unicode rispetto al caso 1

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

9) Utilizzo di StringTokenizer

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

Dal commento : fai attenzione a StringTokenizer, per abcd funzionerà ma per un ... bc ... d o ... abcd o un .... b ...... c ..... d ... o ecc. non funzionerà. Conterà solo per. tra i personaggi solo una volta

Maggiori informazioni in github

Test delle prestazioni (usando JMH , mode = AverageTime, punteggio 0.010migliore allora 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

Le stringhe stampate non corrispondono a quelle sopra e l'ordine è il più veloce per primo, il che rende la ricerca almeno complicata. Bella risposta altrove!
Maarten Bodewes,

caso 2, generalizzato per punti di codice che richiedono più di un'unità di codice UTF-16:"1🚲2🚲3 has 2".codePoints().filter((c) -> c == "🚲".codePointAt(0)).count()
Tom Blodget,

174

Prima o poi qualcosa deve essere ripetuto. È molto più semplice per te scrivere il ciclo (molto semplice) che usare qualcosa del genere splitche è molto più potente del necessario.

In ogni caso incapsulare il loop in un metodo separato, ad es

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;
}

Quindi non è necessario avere il ciclo nel codice principale, ma il ciclo deve essere lì da qualche parte.


5
per (int i = 0, l = haystack.length (); i <l; i ++) sii gentile con il tuo stack
Chris,

12
(Non sono nemmeno sicuro da dove provenga il bit "stack" del commento. Non è che questa risposta sia la mia ricorsiva, che è davvero brutta per lo stack.)
Jon Skeet,

2
non solo, ma questa è probabilmente un'anti-ottimizzazione senza dare un'occhiata a ciò che fa il jit. Se hai fatto quanto sopra su un array per loop, ad esempio, potresti peggiorare le cose.
ShuggyCoUk,

4
@sulai: la preoccupazione di Chris è priva di fondamento, IMO, di fronte a una banale ottimizzazione JIT. C'è qualche motivo per cui questo commento ha attirato la tua attenzione al momento, oltre tre anni dopo? Sono solo interessato.
Jon Skeet,

1
Probabilmente @sulai si è appena imbattuto nella domanda (mentre mi chiedevo se Java avesse un metodo incorporato per questo) e non ha notato le date. Tuttavia, sono curioso di sapere come spostare la length()chiamata al di fuori del loop potrebbe peggiorare le prestazioni , come menzionato da @ShuggyCoUk alcuni commenti.
JKillian,

63

Ho avuto un'idea simile a Mladen, ma al contrario ...

String s = "a.b.c.d";
int charCount = s.replaceAll("[^.]", "").length();
println(charCount);

Corretta. ReplaceAll (".") Sostituirà qualsiasi carattere, non solo un punto. ReplaceAll ("\\.") Avrebbe funzionato. La tua soluzione è più semplice.
VonC

jjnguy aveva in realtà suggerito prima un sostituto All ("[^.]"), dopo aver visto la mia soluzione "abcd" .split ("\\."). length-1. Ma dopo essere stato colpito 5 volte, ho eliminato la mia risposta (e il suo commento).
VonC

"... ora hai due problemi" (obbligatorio.) Comunque, scommetto che ci sono decine di loop in esecuzione in replaceAll()e length(). Bene, se non è visibile, non esiste; o)
Piskvor ha lasciato l'edificio il

2
non penso sia una buona idea usare regex e creare una nuova stringa per il conteggio. vorrei solo creare un metodo statico che esegua il ciclo di ogni carattere nella stringa per contare il numero.
mingfai,

1
@mingfai: in effetti, ma la domanda originale è di creare un one-liner, e persino, senza un loop (puoi fare un loop in una riga, ma sarà brutto!).
Metti in

37
String s = "a.b.c.d";
int charCount = s.length() - s.replaceAll("\\.", "").length();

ReplaceAll (".") Sostituirà tutti i caratteri.

La soluzione di PhiLho usa ReplaceAll ("[^.]", ""), Che non ha bisogno di essere sfuggito, poiché [.] Rappresenta il carattere 'punto', non 'qualsiasi carattere'.


Mi piace questa. C'è ancora un ciclo lì, ovviamente, come deve esserci.
L'archetipo Paolo,

Nota che dovresti dividere questo numero se vuoi cercare sottostringhe di lunghezza> 1
rogerdpack

30

La mia soluzione "idiomatica one-liner":

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

Non ho idea del perché venga accettata una soluzione che utilizza StringUtils.


4
C'è una soluzione precedente simile a questa in questo post.
JCalcines,

7
Perché questa soluzione è davvero inefficiente
András

Questo crea una stringa aggiuntiva solo per produrre un conteggio. Non ho idea del perché qualcuno preferirebbe questo a StringUtils se StringUtils è un'opzione. Se non è un'opzione, dovrebbero semplicemente creare un semplice ciclo per una classe di utilità.
cotta

28
String s = "a.b.c.d";
long result = s.chars().filter(ch -> ch == '.').count();

1
Vota + per una soluzione nativa.
Scadge il

24

Un esempio più breve è

String text = "a.b.c.d";
int count = text.split("\\.",-1).length-1;

3
Questo sembra avere un overhead relativamente grande, tieni presente che potrebbe creare molte piccole stringhe. Normalmente non importa molto ma usalo con cura.
Maarten Bodewes,

19

ecco una soluzione senza loop:

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));

bene, c'è un ciclo, ma è invisibile :-)

- Yonatan


2
A meno che la stringa non sia così lunga da ottenere un OutOfMemoryError.
Spencer Kormos,

Il problema sembra abbastanza forzato da essere compiti a casa, e in tal caso, questa ricorsione è probabilmente la risposta che ti viene chiesto di trovare.
Erickson,

Questo utilizza indexOf, che eseguirà il loop ... ma una bella idea. Pubblicazione di una soluzione veramente "solo ricorsiva" in un minuto ...
Jon Skeet,

Se ha più occorrenze rispetto agli slot di stack disponibili, avrai un'eccezione di overflow dello stack;)
Luca C.

15

Non mi piace l'idea di allocare una nuova stringa per questo scopo. E poiché la stringa ha già un array di caratteri nella parte posteriore in cui memorizza il suo valore, String.charAt () è praticamente gratuito.

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

fa il trucco, senza allocazioni aggiuntive che hanno bisogno di raccolta, in 1 riga o meno, con solo J2SE.


Dare un po 'd'amore per questo perché è l'unico che fa un singolo passaggio sulla corda. Mi preoccupo delle prestazioni.
javadba,

1
charAtscorre tra punti di codice a 16 bit e non caratteri! A charin Java non è un personaggio. Quindi questa risposta implica che non ci deve essere un simbolo Unicode con un surrogato elevato uguale al punto di codice di delim. Non sono sicuro che sia corretto per il punto, ma in generale potrebbe non essere corretto.
ceving

14

Ok, ispirato alla soluzione di Yonatan, eccone uno che è puramente ricorsivo: gli unici metodi di libreria utilizzati sono length()e charAt(), nessuno dei quali esegue alcun ciclo:

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);
}

Se la ricorsione conta come loop dipende dalla definizione esatta che usi, ma è probabilmente il più vicino possibile.

Non so se la maggior parte delle JVM eseguono la ricorsione della coda in questi giorni ... in caso contrario otterrai l'omonimo stack overflow per stringhe adeguatamente lunghe, ovviamente.


No, la ricorsione della coda sarà probabilmente in Java 7, ma non è ancora diffusa. Questa semplice ricorsione della coda diretta può essere tradotta in un ciclo in fase di compilazione, ma la roba Java 7 è in realtà integrata nella JVM per gestire il concatenamento attraverso diversi metodi.
Erickson,

3
Sarebbe più probabile che si verifichi una ricorsione della coda se il metodo restituisce una chiamata a se stesso (incluso un parametro totale in esecuzione), anziché restituire il risultato dell'esecuzione di un'aggiunta.
Stephen Denne,

12

Ispirato da Jon Skeet, una versione non-loop che non farà esplodere il tuo stack. Utile anche punto di partenza se si desidera utilizzare il 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);
    }
}

(Dichiarazione di non responsabilità: non testata, non compilata, non ragionevole.)

Forse il modo migliore (single thread, nessun supporto per coppie surrogate) per scriverlo:

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

11

Non sono sicuro dell'efficienza di questo, ma è il codice più breve che potrei scrivere senza introdurre librerie di terze parti:

public static int numberOf(String target, String content)
{
    return (content.split(target).length - 1);
}

4
Per contare le occorrenze alla fine della stringa si dovrà chiamare split con un argomento limite negativo in questo modo: return (content.split(target, -1).length - 1);. Per impostazione predefinita, le occorrenze alla fine della stringa sono omesse nell'array risultante da split (). Vedi il Doku
vlz,

10

Con puoi anche usare i flussi per raggiungere questo obiettivo. Ovviamente c'è un'iterazione dietro le quinte, ma non devi scriverla esplicitamente!

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

Usare .codePoints()invece di .chars()supporterebbe quindi qualsiasi valore Unicode (compresi quelli che richiedono coppie surrogate)
Luke Usherwood

10

È inoltre possibile utilizzare la riduzione in Java 8 per risolvere questo problema:

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

Produzione:

3

8

Campione completo:

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;
  }
}

Chiamata:

int occurrences = CharacterCounter.countOccurrences("l", "Hello World.");
System.out.println(occurrences); // 3

codice errato non funziona quando provo int occorrenze = CharacterCounter.countOccurrences ("1", "101"); System.out.println (occorrenze); // 1
jayesh

Commetto una correzione per il codice che funziona con la stessa logica
MaanooAk

8

Il modo più semplice per ottenere la risposta è il seguente:

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));
}

2
Questo frammento non restituisce la quantità corretta di punti per un dato input "abc"
dekaru

@dekaru Potresti per favore incollare la tua puntura nel commento in modo che possiamo dare un'occhiata.
Amar Magar,

5

Nel caso in cui utilizzi il framework Spring, potresti anche utilizzare la classe "StringUtils". Il metodo sarebbe "countOccurrencesOf".


5

È possibile utilizzare la split()funzione in un solo codice di riga

int noOccurence=string.split("#",-1).length-1;

Split crea davvero la matrice di stringhe, che richiede molto tempo.
Palec,

Hai ragione, questa è una vera preoccupazione. In un altro modo evita di introdurre una lib di terze parti nel tuo progetto (se non ancora fatto). Dipende da cosa vuoi fare e quali sono le aspettative di prestazione.
Benj,

3
Questa soluzione NON includerà gli hit vuoti finali, poiché l'argomento limitè impostato su zero in questa chiamata di metodo split sovraccaricata. Un esempio: "1##2#3#####".split("#")produrrà solo un array di dimensioni 4 ( [0:"1";1:""; 2:"2"; 3:"3"]) invece di dimensioni 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;
}

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

3

Mentre i metodi possono nasconderlo, non c'è modo di contare senza un ciclo (o ricorsione). Tuttavia, si desidera utilizzare un carattere [] per motivi di prestazioni.

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;
}

L'uso di ReplaceAll (ovvero RE) non sembra il modo migliore di procedere.


Penso che questa sia la soluzione più elegante. Perché hai usato toCharArray e non direttamente?
Panayotis,

Il looping con charAt almeno era più lento. Potrebbe dipendere anche dalla piattaforma. L'unico modo per scoprirlo davvero sarebbe misurare la differenza.
tcurdt

3

Bene, con un compito abbastanza simile mi sono imbattuto in questo thread. Non ho visto alcuna limitazione del linguaggio di programmazione e poiché groovy funziona su un java vm: Ecco come sono stato in grado di risolvere il mio problema usando Groovy.

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

fatto.


3

Una soluzione molto più semplice sarebbe quella di dividere la stringa in base al carattere con cui la stai abbinando.

Per esempio,

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

Questo restituirà 4 nel caso di: getOccurences("o", "something about a quick brown fox");


Il problema qui è che un array deve essere allocato, il che è terribilmente lento.
Palec

2

Da qualche parte nel codice, qualcosa deve essere ripetuto. L'unico modo per aggirare questo è un completo srotolamento del ciclo:

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

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


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

... ecc., ma poi sei tu a eseguire il ciclo, manualmente, nell'editor di origine, anziché nel computer che lo eseguirà. Vedi lo pseudocodice:

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

2

Ecco una soluzione di ricorsione di stile leggermente diversa:

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);
}

2

Perché non solo dividere il personaggio e quindi ottenere la lunghezza dell'array risultante. la lunghezza dell'array sarà sempre il numero di istanze + 1. Giusto?


2

Il seguente codice sorgente ti darà il numero di occorrenze di una determinata stringa in una parola inserita dall'utente: -

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);
        }

    }
}

2
int count = (line.length() - line.replace("str", "").length())/"str".length();
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.