Como posso contar o número de ocorrências de um caractere em uma String?

547

Eu tenho a corda

a.b.c.d

Quero contar as ocorrências de '.' de uma maneira idiomática, preferencialmente de uma linha.

(Anteriormente, eu havia expressado essa restrição como "sem loop", caso você esteja se perguntando por que todo mundo está tentando responder sem usar um loop).

Bart
fonte
1
Dever de casa? Porque, caso contrário, não vejo o requisito de evitar o loop.
PhiLho
22
Não é avesso a um loop, mas procura uma linha idiomática.
226 Bart Bart:
2
Foram feitos loops para um problema como esse, escreva o loop em uma classe Utility comum e chame seu liner recém-cunhado.
JavaRa che
Pergunta semelhante para strings: stackoverflow.com/questions/767759/…
koppor:
Apenas para salientar - eu aprecio encontrar as frases únicas, é divertido e (como uma verdadeira vantagem) geralmente fácil de lembrar, mas eu gostaria de salientar que um método separado e um loop são melhores em quase todos os aspectos - legibilidade e até desempenho. A maioria das soluções "Elegantes" abaixo não terá um desempenho muito bom, pois envolve a reforma de cadeias / cópia de memória, enquanto um loop que apenas digitalizou a cadeia e contou as ocorrências seria rápido e simples. Não que o desempenho geralmente seja um fator, mas não olhe para a linha em um loop e assuma que ele terá um desempenho melhor.
Bill K

Respostas:

722

Meu 'one-liner idiomático' para isso é:

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

Por que você mesmo escrever quando já está em comum ?

O oneliner do Spring Framework para isso é:

int occurance = StringUtils.countOccurrencesOf("a.b.c.d", ".");
Cowan
fonte
44
Equivalente de goiaba : int count = CharMatcher.is('.').countIn("a.b.c.d");... Conforme respondida por dogbane em uma pergunta duplicada.
Jonik 12/08
25
Embora eu não reduza esse voto, é (a) exigindo bibliotecas de terceiros e (b) caro.
Javadba
Este trabalho apenas com o quadro de mola precisa ser importado.
Isuru Madusanka
19
O que tem sido caro, em todas as empresas em que eu trabalhei, é ter muitas aulas "* Utils" mal escritas e com pouca manutenção. Parte do seu trabalho é saber o que está disponível no Apache Commons.
AbuNassar 5/10
1016

Que tal agora. Ele não usa regexp por baixo, portanto deve ser mais rápido que algumas das outras soluções e não usará um loop.

int count = line.length() - line.replace(".", "").length();
Andreas Wederbrand
fonte
122
Caminho mais fácil. Inteligente. E funciona no Android, onde não há classe
StringUtils
43
Esta é a melhor resposta. O motivo é o melhor porque você não precisa importar outra biblioteca.
Alex Spencer
27
Muito prático, mas feio como o inferno. Eu não o recomendo, pois leva a um código confuso.
Daniel San
32
O código feio pode ser minimizado, tornando-o um método em sua própria classe "StringUtils". Então o código feio está exatamente em um ponto e em qualquer outro lugar é bem legível.
RonR
30
O método de loop é muito mais rápido que isso. Especialmente quando se deseja contar um caractere em vez de um String (já que não existe um método String.replace (char, char)). Em uma sequência de 15 caracteres, obtenho uma diferença de 6049 ns vs 26.739 ns (média de 100 execuções). Os números brutos são uma enorme diferença, mas em termos de penetração ... soma-se. Evite as alocações de memória - use um loop!
Ben
282

Resuma outra resposta e o que eu sei de todas as maneiras de fazer isso usando uma única linha:

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

1) Usando o Apache Commons

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

2) Usando o Spring Framework

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

3) Usando substituir

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

4) Usando replaceAll (caso 1)

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

5) Usando 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) Usando Java8 (caso 1)

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

8) Usando Java8 (caso 2), pode ser melhor para unicode do que para o 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);

Do comentário : Tenha cuidado com o StringTokenizer, pois abcd funcionará, mas para um ... bc ... d ou ... abcd ou a .... b ...... c ..... d ... ou etc. não funcionará. Apenas conta para. entre caracteres apenas uma vez

Mais informações no github

Teste de desempenho (usando JMH , mode = AverageTime, com 0.010melhor pontuação 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
fonte
As seqüências impressas não coincidem com as acima, e o pedido é mais rápido primeiro, o que torna a pesquisa pelo menos complicada. Resposta agradável de outra maneira!
Maarten Bodewes
caso 2, generalizado para pontos de código que precisam de mais de uma unidade de código UTF-16:"1🚲2🚲3 has 2".codePoints().filter((c) -> c == "🚲".codePointAt(0)).count()
Tom Blodget
174

Mais cedo ou mais tarde, algo precisa se repetir. É muito mais simples escrever o loop (muito simples) do que usar algo como o splitque é muito mais poderoso do que você precisa.

Por todos os meios, encapsule o loop em um método separado, por exemplo

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

Então você não precisa ter o loop no seu código principal - mas o loop precisa estar em algum lugar.

Jon Skeet
fonte
5
for (int i = 0, l = haystack.length (); i <l; i ++) ser gentil com sua pilha
Chris
12
(Eu não sei mesmo onde o bit "pilha" do comentário vem Não é como. Esta resposta é meu único recursiva, o que é realmente desagradável para a pilha.)
Jon Skeet
2
não apenas isso, mas isso é possivelmente uma anti otimização sem dar uma olhada no que o jit faz. Se você fez o acima em uma matriz para loop, por exemplo, pode piorar as coisas.
ShuggyCoUk
4
@sulai: A preocupação de Chris é infundada, IMO, diante de uma otimização trivial do JIT. Existe alguma razão para esse comentário chamar sua atenção no momento, mais de três anos depois? Apenas interessado.
Jon Skeet
1
Provavelmente o @sulai acabou de me deparar com a pergunta como eu fiz (enquanto me perguntava se o Java tinha um método interno para isso) e não percebi as datas. No entanto, estou curioso como mover o length()exterior chamada do loop poderia fazer desempenho pior , como mencionado por @ShuggyCoUk alguns comentários acima.
JKillian
63

Tive uma ideia parecida com a de Mladen, mas o oposto ...

String s = "a.b.c.d";
int charCount = s.replaceAll("[^.]", "").length();
println(charCount);
PhiLho
fonte
Corrigir. ReplaceAll (".") Substituiria qualquer caractere, não apenas um ponto. ReplaceAll ("\\.") Teria funcionado. Sua solução é mais direta.
VonC 9/11/08
jjnguy realmente sugeriu um replaceAll ("[^.]") primeiro, ao ver minha solução "abcd" .split ("\\."). length-1. Mas depois de ser atingido 5 vezes, apaguei minha resposta (e seu comentário).
VonC 9/11/08
"... agora você tem dois problemas" (obrigatório.) De qualquer forma, aposto que existem dezenas de loops sendo executados em replaceAll()e length(). Bem, se não é visível, não existe; o)
Piskvor deixou o prédio em 25/08/10
2
não acho uma boa ideia usar regex e criar uma nova string para a contagem. Gostaria apenas de criar um método estático que loop cada caractere na seqüência de caracteres para contar o número.
Mingfai
1
@mingfai: na verdade, mas a pergunta original é sobre fazer uma linha e até mesmo sem um loop (você pode fazer um loop em uma linha, mas será feio!). Questionar a questão, não é a resposta ... :-)
PhiLho
37
String s = "a.b.c.d";
int charCount = s.length() - s.replaceAll("\\.", "").length();

ReplaceAll (".") Substituiria todos os caracteres.

A solução da PhiLho usa ReplaceAll ("[^.]", ""), Que não precisa ser escapado, pois [.] Representa o caractere 'ponto', não 'qualquer caractere'.

Mladen Prajdic
fonte
Eu gosto deste. Ainda há um ciclo lá, é claro, como deve haver.
The Archetypal Paul
NB que você precisaria dividir esse número se você queria olhar para substrings de comprimento> 1
rogerdpack
30

Minha solução 'idiomática de uma linha':

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

Não faço ideia por que uma solução que usa StringUtils é aceita.

mlchen850622
fonte
4
Há uma solução mais antiga semelhante a esta neste post.
JCalcines
7
Porque esta solução é realmente ineficiente
András
Isso cria uma sequência extra apenas para produzir uma contagem. Não faço ideia por que alguém preferiria isso a StringUtils se StringUtils é uma opção. Se não for uma opção, eles devem apenas criar um loop for simples em uma classe de utilitário.
esmagar
28
String s = "a.b.c.d";
long result = s.chars().filter(ch -> ch == '.').count();
fubo
fonte
1
Vote + em uma solução nativa.
Scadge 23/03
24

Um exemplo mais curto é

String text = "a.b.c.d";
int count = text.split("\\.",-1).length-1;
Peter Lawrey
fonte
3
Este parece ter uma sobrecarga relativamente grande, esteja avisado de que pode criar muitas seqüências pequenas. Normalmente isso não importa muito, mas use com cuidado.
Maarten Bodewes
19

aqui está uma solução sem 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));

bem, existe um loop, mas é invisível :-)

- Yonatan

Yonatan Maman
fonte
2
A menos que sua string seja tão longa, você obtém um OutOfMemoryError.
Spencer Kormos
O problema parece artificial o suficiente para ser uma lição de casa e, nesse caso, essa recursão provavelmente é a resposta que você está sendo solicitada a encontrar.
22680
Isso usa indexOf, que fará um loop ... mas é uma boa idéia. Publicação de uma solução verdadeiramente "apenas recursiva" em um minuto ...
Jon Skeet
Se ele tem mais ocorrências que seus espaços de pilha disponível, você terá um estouro de pilha de exceção;)
Luca C.
15

Não gosto da idéia de alocar uma nova string para esse fim. E como a string já possui uma matriz de caracteres na parte de trás, onde armazena seu valor, String.charAt () é praticamente gratuito.

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

faz o truque, sem alocações adicionais que precisam de coleta, em 1 linha ou menos, apenas com J2SE.

0xCAFEBABE
fonte
Dar um pouco de amor por este, porque é o único a fazer uma única passagem sobre a corda. Eu me preocupo com desempenho.
Javadba
1
charAtitera através de pontos de código de 16 bits, não caracteres! A charem Java não é um caractere. Portanto, essa resposta implica que não deve haver símbolo Unicode com um substituto alto igual ao ponto de código de delim. Não tenho certeza se está correto para o ponto, mas em geral pode não estar correto.
ceving 22/07/2014
14

Ok, inspirado na solução de Yonatan, aqui está um que é puramente recursivo - os únicos métodos de biblioteca usados ​​são length()e charAt(), nenhum dos quais faz loop:

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 a recursão conta como loop depende de qual definição exata você usa, mas provavelmente é o mais próximo possível.

Não sei se a maioria das JVMs faz recursão de cauda atualmente ... se não, você obterá o estouro de pilha de mesmo nome para seqüências longas apropriadas, é claro.

Jon Skeet
fonte
Não, a recursão da cauda provavelmente estará no Java 7, mas ainda não é generalizada. Essa recursão simples e direta da cauda pode ser traduzida para um loop em tempo de compilação, mas o material do Java 7 é realmente incorporado à JVM para lidar com o encadeamento por diferentes métodos.
22620
3
Você provavelmente obteria recursão final se o método retornasse uma chamada para si mesmo (incluindo um parâmetro total em execução), em vez de retornar o resultado da realização de uma adição.
Stephen Denne
12

Inspirado por Jon Skeet, uma versão sem loop que não estraga sua pilha. Também é um ponto de partida útil se você deseja usar a estrutura de junção de forquilha.

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

(Isenção de responsabilidade: não testado, não compilado, não sensato.)

Talvez a melhor (sem suporte a um par substituto) de thread único:

public static int countOccurrences(String haystack, char needle) {
    int count = 0;
    for (char c : haystack.toCharArray()) {
        if (c == needle) {
           ++count;
        }
    }
    return count;
}
Tom Hawtin - linha de orientação
fonte
11

Não tenho certeza sobre a eficiência disso, mas é o código mais curto que eu poderia escrever sem trazer bibliotecas de terceiros:

public static int numberOf(String target, String content)
{
    return (content.split(target).length - 1);
}
KannedFarU
fonte
4
Para também contam ocorrências no final da corda que você terá que chamar de divisão com um argumento limite negativo como este: return (content.split(target, -1).length - 1);. Por padrão, as ocorrências no final da cadeia de caracteres são omitidas na matriz resultante de split (). Veja o Doku
vlz
10

Com você também pode usar fluxos para conseguir isso. Obviamente, há uma iteração nos bastidores, mas você não precisa escrevê-la explicitamente!

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.
fonte
Usando .codePoints()vez de .chars()suportaria qualquer valor Unicode (incluindo aqueles que requerem pares substitutos)
Luke Usherwood
10

Também é possível usar o reduzir no Java 8 para resolver esse problema:

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

Resultado:

3
gil.fernandes
fonte
8

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

Ligar:

int occurrences = CharacterCounter.countOccurrences("l", "Hello World.");
System.out.println(occurrences); // 3
Benny Neugebauer
fonte
código errado não está funcionando quando tento int ocorrrences = CharacterCounter.countOccurrences ("1", "101"); System.out.println (ocorrências); // 1
jayesh
I cometer uma correção para o código que funciona com a mesma lógica
MaanooAk
8

A maneira mais simples de obter a resposta é a seguinte:

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
fonte
2
Este trecho não retorna a quantidade correta de pontos para uma determinada entrada "abc"
dekaru
@dekaru Você poderia colar sua picada no comentário para que possamos dar uma olhada.
Amar Magar
5

Caso esteja usando o Spring framework, você também pode usar a classe "StringUtils". O método seria "countOccurrencesOf".

user496208
fonte
5

Você pode usar a split()função em apenas um código de linha

int noOccurence=string.split("#",-1).length-1;
user3322553
fonte
Split realmente cria a matriz de strings, que consome muito tempo.
Palec 19/05/19
Você está certo, isso é uma verdadeira preocupação. De outra maneira, evita trazer uma lib de terceiros em seu projeto (se ainda não estiver pronto). Depende do que você deseja fazer e qual é a expectativa de desempenho.
Benj
3
Esta solução NÃO incluirá os resultados vazios à direita, porque o argumento limitestá definido como zero nesta chamada de método de divisão sobrecarregada. Um exemplo: "1##2#3#####".split("#")produzirá apenas uma matriz de tamanho 4 ( [0:"1";1:""; 2:"2"; 3:"3"]) em vez de tamanho 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;
}
Hardest
fonte
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
fonte
3

Embora os métodos possam ocultá-lo, não há como contar sem um loop (ou recursão). Você deseja usar um char [] por razões de desempenho.

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

O uso de replaceAll (ou seja, RE) não parece o melhor caminho a percorrer.

tcurdt
fonte
Eu acho que essa é a solução mais elegante. Por que você usou o toCharArray e não charAt diretamente?
Panayotis
Looping com charAt pelo menos costumava ser mais lento. Também pode depender da plataforma. A única maneira de realmente descobrir seria medir a diferença.
tcurdt 31/05
3

Bem, com uma tarefa bem parecida, me deparei com este Tópico. Eu não vi nenhuma restrição de linguagem de programação e como o groovy é executado em um java vm: Aqui está como eu consegui resolver meu problema usando o Groovy.

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

feito.

Christoph Zabinski
fonte
3

Uma solução muito mais fácil seria dividir a string com base no caractere com o qual você está combinando.

Por exemplo,

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

Isso retornará 4 no caso de: getOccurences("o", "something about a quick brown fox");

Saharcasm
fonte
O problema aqui é que uma matriz precisa ser alocada, o que é terrivelmente lento.
Palec
2

Em algum lugar do código, algo precisa fazer um loop. A única maneira de contornar isso é um desenrolar completo do loop:

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

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


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

... etc, mas é você quem executa o loop manualmente no editor de código-fonte - em vez do computador que o executará. Veja o 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 saiu do prédio
fonte
2

Aqui está uma solução de recursão de estilo um pouco 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
fonte
2

Por que não apenas dividir o caractere e obter o comprimento da matriz resultante. o comprimento da matriz sempre será o número de instâncias + 1. Certo?

Preço de Darryl
fonte
2

O seguinte código-fonte não fornecerá nenhuma ocorrência de uma determinada sequência em uma palavra inserida pelo usuário: -

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