Comment puis-je compter le nombre d'occurrences d'un caractère dans une chaîne?

547

J'ai la ficelle

a.b.c.d

Je veux compter les occurrences de '.' de manière idiomatique, de préférence une doublure.

(Auparavant, j'avais exprimé cette contrainte comme "sans boucle", au cas où vous vous demanderiez pourquoi tout le monde essaie de répondre sans utiliser de boucle).

Bart
la source
1
Devoirs? Sinon, je ne vois pas la nécessité d'éviter la boucle.
PhiLho
22
Pas opposé à une boucle autant que la recherche d'une doublure idiomatique.
Bart
2
Des boucles ont été faites pour un problème comme celui-ci, écrivez la boucle dans une classe Utility commune, puis appelez votre doublure fraîchement fabriquée.
che javara
Question similaire pour les chaînes: stackoverflow.com/questions/767759/…
koppor
Juste pour souligner - j'apprécie de trouver les one-liners, c'est amusant et (comme un véritable avantage) souvent facile à retenir, mais je voudrais souligner qu'une méthode séparée et une boucle sont meilleures à presque tous les égards - lisibilité et même performance. La plupart des solutions "élégantes" ci-dessous ne fonctionneront pas très bien car elles impliquent de reformer les chaînes / copier la mémoire, alors qu'une boucle qui vient de balayer la chaîne et de compter les occurrences serait rapide et simple. Ce n'est pas que les performances devraient généralement être un facteur, mais ne regardez pas la ligne sur une boucle et supposez qu'elle fonctionnera mieux.
Bill K

Réponses:

722

Mon «one-liner idiomatique» pour cela est:

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

Pourquoi l'écrire vous-même quand il est déjà en langage commun ?

Oneliner de Spring Framework pour cela est:

int occurance = StringUtils.countOccurrencesOf("a.b.c.d", ".");
Cowan
la source
44
Équivalent goyave : int count = CharMatcher.is('.').countIn("a.b.c.d");... Comme l'a répondu Dogbane dans une question en double.
Jonik du
25
Bien que je ne décote pas cela, cela (a) nécessite des bibliothèques tierces et (b) est cher.
javadba
Cela ne fonctionne qu'avec un cadre à ressort.
Isuru Madusanka
19
Ce qui a coûté cher, dans chaque entreprise dans laquelle j'ai travaillé, est d'avoir beaucoup de cours "* Utils" mal écrits et mal entretenus. Une partie de votre travail consiste à savoir ce qui est disponible dans Apache Commons.
AbuNassar
1016

Que dis-tu de ça. Il n'utilise pas regexp en dessous, donc devrait être plus rapide que certaines des autres solutions et n'utilisera pas de boucle.

int count = line.length() - line.replace(".", "").length();
Andreas Wederbrand
la source
122
Manière la plus simple. Intelligent. Et cela fonctionne sur Android, où il n'y a pas de classe
StringUtils
43
C'est la meilleure réponse. La raison pour laquelle c'est le meilleur est que vous n'avez pas besoin d'importer une autre bibliothèque.
Alex Spencer
27
Très pratique mais moche comme l'enfer. Je ne le recommande pas car cela conduit à confondre le code.
Daniel San
32
Le code laid peut être minimisé en en faisant une méthode dans votre propre classe "StringUtils". Ensuite, le code laid est exactement à un endroit, et partout ailleurs est bien lisible.
RonR
30
La méthode de boucle est beaucoup plus rapide que cela. Surtout lorsque vous voulez compter un caractère au lieu d'une chaîne (car il n'y a pas de méthode String.replace (char, char)). Sur une chaîne de 15 caractères, j'obtiens une différence de 6049 ns contre 26739 ns (en moyenne sur 100 tours). Les nombres bruts sont une énorme différence, mais en termes de pourcentage ... cela s'additionne. Évitez les allocations de mémoire - utilisez une boucle!
Ben
282

Résumez les autres réponses et ce que je sais de toutes les façons de le faire en utilisant une ligne:

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

1) Utiliser Apache Commons

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

2) Utilisation de Spring Framework

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

3) Utilisation de replace

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

4) Utilisation de replaceAll (cas 1)

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

5) Utilisation de replaceAll (cas 2)

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

6) Utilisation du split

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

7) Utilisation de Java8 (cas 1)

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

8) Utiliser Java8 (cas 2), peut être meilleur pour unicode que le cas 1

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

9) Utilisation de StringTokenizer

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

Du commentaire : Attention au StringTokenizer, pour abcd ça marchera mais pour a ... bc ... d ou ... abcd ou a .... b ...... c ..... d ... ou etc. cela ne fonctionnera pas. Ça va juste compter. entre les personnages juste une fois

Plus d'infos sur github

Test de performance (en utilisant JMH , mode = AverageTime, 0.010mieux score alors 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
la source
Les chaînes imprimées ne correspondent pas à celles ci-dessus, et l'ordre est le plus rapide en premier, ce qui rend la recherche délicate au moins. Belle réponse sinon!
Maarten Bodewes
cas 2, généralisé pour les points de code qui nécessitent plus d'une unité de code UTF-16:"1🚲2🚲3 has 2".codePoints().filter((c) -> c == "🚲".codePointAt(0)).count()
Tom Blodget
174

Tôt ou tard, quelque chose doit boucler. Il est beaucoup plus simple d'écrire la boucle (très simple) que d'utiliser quelque chose commesplit qui est beaucoup plus puissant que nécessaire.

Par tous les moyens encapsuler la boucle dans une méthode distincte, par exemple

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

Ensuite, vous n'avez pas besoin d'avoir la boucle dans votre code principal - mais la boucle doit être là quelque part.

Jon Skeet
la source
5
pour (int i = 0, l = haystack.length (); i <l; i ++) soyez gentil avec votre pile
Chris
12
(Je ne sais même pas d'où vient le bit "stack" du commentaire. Ce n'est pas comme si cette réponse était ma réponse récursive, qui est en effet désagréable pour la pile.)
Jon Skeet
2
non seulement cela, mais c'est peut-être une anti-optimisation sans regarder ce que fait le jit. Si vous avez fait ce qui précède sur un tableau pour une boucle par exemple, vous pourriez aggraver les choses.
ShuggyCoUk
4
@sulai: l'inquiétude de Chris est sans fondement, OMI, face à une optimisation JIT triviale . Y a-t-il une raison pour laquelle ce commentaire a attiré votre attention en ce moment, plus de trois ans plus tard? Juste intéressé.
Jon Skeet
1
Probablement @sulai est juste tombé sur la question comme je l'ai fait (tout en me demandant si Java avait une méthode intégrée pour cela) et n'a pas remarqué les dates. Cependant, je suis curieux de voir comment le fait de déplacer l' length()appel en dehors de la boucle pourrait détériorer les performances , comme mentionné par @ShuggyCoUk quelques commentaires plus haut.
JKillian
63

J'avais une idée similaire à Mladen, mais le contraire ...

String s = "a.b.c.d";
int charCount = s.replaceAll("[^.]", "").length();
println(charCount);
PhiLho
la source
Correct. ReplaceAll (".") Remplacerait n'importe quel caractère, pas seulement un point. ReplaceAll ("\\.") Aurait fonctionné. Votre solution est plus simple.
VonC
jjnguy avait en fait suggéré un replaceAll ("[^.]") d'abord, en voyant ma solution "abcd" .split ("\\."). length-1. Mais après avoir été frappé 5 fois, j'ai supprimé ma réponse (et son commentaire).
VonC
"... maintenant vous avez deux problèmes" (oblig.) Quoi qu'il en soit, je parie qu'il y a des dizaines de boucles qui s'exécutent dans replaceAll()et length(). Eh bien, s'il n'est pas visible, il n'existe pas; o)
Piskvor a quitté le bâtiment
2
je ne pense pas que ce soit une bonne idée d'utiliser l'expression régulière et de créer une nouvelle chaîne pour le comptage. je voudrais simplement créer une méthode statique qui boucle chaque caractère de la chaîne pour compter le nombre.
mingfai
1
@mingfai: en effet, mais la question d'origine est de faire un one-liner, et même sans boucle (vous pouvez faire une boucle sur une seule ligne, mais ce sera moche!). Questionnez la question, pas la réponse ... :-)
PhiLho
37
String s = "a.b.c.d";
int charCount = s.length() - s.replaceAll("\\.", "").length();

ReplaceAll (".") Remplacerait tous les caractères.

La solution de PhiLho utilise ReplaceAll ("[^.]", ""), Qui n'a pas besoin d'être échappé, car [.] Représente le caractère 'point', pas 'n'importe quel caractère'.

Mladen Prajdic
la source
J'aime celui la. Il y a toujours une boucle, bien sûr, comme il doit y en avoir.
L'Archétype Paul
NB que vous devrez diviser ce nombre si vous souhaitez rechercher des sous-chaînes de longueur> 1
rogerdpack
30

Ma solution `` one-liner idiomatique '':

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

Je ne sais pas pourquoi une solution qui utilise StringUtils est acceptée.

mlchen850622
la source
4
Il existe une ancienne solution similaire à celle-ci dans cet article.
JCalcines
7
Parce que cette solution est vraiment inefficace
András
Cela crée une chaîne supplémentaire juste pour produire un compte. Aucune idée pourquoi quelqu'un préférerait cela à StringUtils si StringUtils est une option. Si ce n'est pas une option, ils devraient simplement créer une boucle for simple dans une classe utilitaire.
écraser
28
String s = "a.b.c.d";
long result = s.chars().filter(ch -> ch == '.').count();
fubo
la source
1
Votez + pour une solution native.
Scadge
24

Un exemple plus court est

String text = "a.b.c.d";
int count = text.split("\\.",-1).length-1;
Peter Lawrey
la source
3
Celui-ci semble avoir une surcharge relativement importante, sachez qu'il peut créer beaucoup de petites chaînes. Normalement, cela n'a pas beaucoup d'importance mais à utiliser avec soin.
Maarten Bodewes
19

voici une solution sans boucle:

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

eh bien, il y a une boucle, mais elle est invisible :-)

- Yonatan

Yonatan Maman
la source
2
À moins que votre chaîne ne soit si longue, vous obtenez une OutOfMemoryError.
Spencer Kormos
Le problème semble suffisamment artificiel pour être des devoirs, et si c'est le cas, cette récursivité est probablement la réponse qu'on vous demande de trouver.
erickson
Cela utilise indexOf, qui bouclera ... mais une bonne idée. Publier une solution vraiment "juste récursive" en une minute ...
Jon Skeet
S'il y a plus d'occurrences que vos emplacements de pile disponibles, vous aurez une exception de dépassement de pile;)
Luca C.
15

Je n'aime pas l'idée d'allouer une nouvelle chaîne à cet effet. Et comme la chaîne a déjà un tableau de caractères à l'arrière où elle stocke sa valeur, String.charAt () est pratiquement libre.

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

fait l'affaire, sans allocations supplémentaires qui doivent être collectées, en 1 ligne ou moins, avec seulement J2SE.

0xCAFEBABE
la source
Donner de l'amour à celui-ci car c'est le seul à faire un seul passage sur la corde. JE VEUX la performance.
javadba
1
charAtitère à travers des points de code 16 bits et non des caractères! A charen Java n'est pas un personnage. Cette réponse implique donc qu'il ne doit pas y avoir de symbole Unicode avec un substitut élevé égal au point de code de delim. Je ne sais pas si c'est correct pour le point, mais en général ce n'est peut-être pas correct.
ceving
14

D'accord, inspiré par la solution de Yonatan, en voici une qui est purement récursive - les seules méthodes de bibliothèque utilisées sont length()et charAt(), aucune ne fait de boucle:

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

Le fait que la récursivité compte comme une boucle dépend de la définition exacte que vous utilisez, mais elle est probablement aussi proche que possible.

Je ne sais pas si la plupart des JVM font une récursion de queue ces jours-ci ... sinon vous obtiendrez le débordement de pile éponyme pour des chaînes convenablement longues, bien sûr.

Jon Skeet
la source
Non, la récursivité de queue sera probablement en Java 7, mais elle n'est pas encore répandue. Cette récursion de queue simple et directe pourrait être traduite en boucle au moment de la compilation, mais le truc Java 7 est en fait intégré à la JVM pour gérer le chaînage via différentes méthodes.
erickson
3
Vous seriez plus susceptible d'obtenir une récursivité de queue si votre méthode se renvoyait un appel (y compris un paramètre total en cours d'exécution), plutôt que de renvoyer le résultat de l'exécution d'un ajout.
Stephen Denne
12

Inspiré par Jon Skeet, une version sans boucle qui ne soufflera pas votre pile. Point de départ également utile si vous souhaitez utiliser le 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);
    }
}

(Avertissement: non testé, non compilé, non raisonnable.)

Peut-être la meilleure façon (monothread, pas de support de paire de substitution) de l'écrire:

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

Je ne suis pas sûr de l'efficacité de cela, mais c'est le code le plus court que j'ai pu écrire sans apporter de bibliothèques tierces:

public static int numberOf(String target, String content)
{
    return (content.split(target).length - 1);
}
KannedFarU
la source
4
Pour compter également occurences à la fin de la chaîne que vous devrez appeler split avec un argument de limite négatif comme ceci: return (content.split(target, -1).length - 1);. Par défaut, les occurrences à la fin de la chaîne sont omises dans le tableau résultant de split (). Voir le Doku
vlz
10

Avec vous pouvez également utiliser des flux pour y parvenir. Évidemment, il y a une itération dans les coulisses, mais vous n'avez pas à l'écrire explicitement!

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.
la source
Utiliser .codePoints()au lieu de .chars()prendrait alors en charge toute valeur Unicode (y compris celles nécessitant des paires de substitution)
Luke Usherwood
10

Il est également possible d'utiliser réduire dans Java 8 pour résoudre ce problème:

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

Production:

3
gil.fernandes
la source
8

Échantillon complet:

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

Appel:

int occurrences = CharacterCounter.countOccurrences("l", "Hello World.");
System.out.println(occurrences); // 3
Benny Neugebauer
la source
mauvais code ne fonctionne pas lorsque j'essaie des occurrences int = CharacterCounter.countOccurrences ("1", "101"); System.out.println (occurrences); // 1
jayesh
Je valide un correctif pour le code qui fonctionne avec la même logique
MaanooAk
8

La façon la plus simple d'obtenir la réponse est la suivante:

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
la source
2
Cet extrait ne renvoie pas la quantité correcte de points pour une entrée donnée "abc"
dekaru
@dekaru Pourriez-vous s'il vous plaît coller votre piqûre dans le commentaire afin que nous puissions y jeter un œil.
Amar Magar
5

Si vous utilisez Spring Framework, vous pouvez également utiliser la classe "StringUtils". La méthode serait "countOccurrencesOf".

user496208
la source
5

Vous pouvez utiliser la split()fonction dans un seul code de ligne

int noOccurence=string.split("#",-1).length-1;
user3322553
la source
Split crée vraiment le tableau de chaînes, ce qui prend beaucoup de temps.
Palec
Vous avez raison, c'est une vraie préoccupation. D'une autre manière, cela évite d'apporter une bibliothèque tierce dans votre projet (si ce n'est pas encore fait). Cela dépend de ce que vous voulez faire et de vos attentes en matière de performances.
Benj
3
Cette solution n'inclura PAS les hits vides de fin, car l'argument limitest défini sur zéro dans cet appel de méthode de fractionnement surchargé. Un exemple: "1##2#3#####".split("#")ne donnera qu'un tableau de taille 4 ( [0:"1";1:""; 2:"2"; 3:"3"]) au lieu de la taille 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;
}
Le plus dur
la source
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
la source
3

Bien que les méthodes puissent le masquer, il n'y a aucun moyen de compter sans boucle (ou récursivité). Vous souhaitez cependant utiliser un caractère [] pour des raisons de performances.

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'utilisation de replaceAll (c'est-à-dire RE) ne semble pas être la meilleure solution.

tcurdt
la source
Je pense que c'est la solution la plus élégante. Pourquoi avez-vous utilisé toCharArray et pas charAt directement?
Panayotis
Boucler avec charAt était au moins plus lent. Cela peut aussi dépendre de la plate-forme. La seule façon de le découvrir serait de mesurer la différence.
tcurdt
3

Eh bien, avec une tâche assez similaire, je suis tombé sur ce fil. Je n'ai vu aucune restriction de langage de programmation et puisque groovy s'exécute sur une machine virtuelle java: voici comment j'ai pu résoudre mon problème en utilisant Groovy.

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

terminé.

Christoph Zabinski
la source
3

Une solution beaucoup plus facile serait de simplement diviser la chaîne en fonction du caractère avec lequel vous le faites correspondre.

Par exemple,

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

Cela retournera 4 dans le cas de: getOccurences("o", "something about a quick brown fox");

Saharcasm
la source
Le problème ici est qu'un tableau doit être alloué, ce qui est terriblement lent.
Palec
2

Quelque part dans le code, quelque chose doit boucler. Le seul moyen de contourner cela est un déroulement complet de la boucle:

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

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


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

... etc, mais c'est vous qui faites la boucle, manuellement, dans l'éditeur de source - au lieu de l'ordinateur qui l'exécutera. Voir le pseudocode:

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 a quitté le bâtiment
la source
2

Voici une solution de récursion de style légèrement différente:

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
la source
2

Pourquoi ne pas simplement diviser le caractère, puis obtenir la longueur du tableau résultant. la longueur du tableau sera toujours le nombre d'instances + 1. N'est-ce pas?

Prix ​​Darryl
la source
2

Le code source suivant vous donnera pas d'occurrences d'une chaîne donnée dans un mot entré par l'utilisateur: -

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
la source
2
int count = (line.length() - line.replace("str", "").length())/"str".length();
Shaban
la source