Jak policzyć liczbę wystąpień znaku w ciągu?


547

Mam sznurek

a.b.c.d

Chcę policzyć wystąpienia „.” w sposób idiomatyczny, najlepiej jednowarstwowy.

(Wcześniej wyrażałem to ograniczenie jako „bez pętli”, na wypadek, gdy zastanawiasz się, dlaczego wszyscy próbują odpowiedzieć bez użycia pętli).


1
Zadanie domowe? Ponieważ w przeciwnym razie nie widzę wymogu unikania pętli.
PhiLho,

22
Nie niechęć do pętli, ale szukanie idiomatycznej jedno-liniowej.
Bart

2
Pętle zostały stworzone z myślą o takim problemie, napisz pętlę we wspólnej klasie Utility, a następnie wywołaj świeżo wybitą jedną linijkę.
che javara

Podobne pytanie do ciągów znaków: stackoverflow.com/questions/767759/...
koppor

Wystarczy wskazać - doceniam znalezienie liniałów jednorzędowych, jest to zabawne i (jako prawdziwa zaleta) często łatwe do zapamiętania, ale chciałbym zauważyć, że osobna metoda i pętla są lepsze pod każdym względem - czytelność, a nawet wydajność. Większość opisanych poniżej „eleganckich” rozwiązań nie będzie działać bardzo dobrze, ponieważ wymagają one reformowania łańcuchów / kopiowania pamięci, podczas gdy pętla, która skanowała łańcuch i liczone wystąpienia, byłaby szybka i prosta. Nie to, że wydajność powinna być na ogół czynnikiem, ale nie patrz na jedną linię w pętli i nie zakładaj, że będzie działać lepiej.
Bill K

Odpowiedzi:


722

Mój „idiomatyczny jedno-liniowiec” do tego to:

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

Po co pisać, gdy jest już w języku wspólnym ?

Oneliner firmy Spring Framework to:

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

44
Odpowiednik Guava : int count = CharMatcher.is('.').countIn("a.b.c.d");... Jak odpowiedział dogbane w duplikacie pytania.
Jonik,

25
Chociaż nie będę głosować za tym, jest to (a) wymagające bibliotek stron trzecich i (b) drogie.
javadba

To tylko praca z ramą sprężynową musi zostać zaimportowana.
Isuru Madusanka


19
To, co było drogie, w każdej firmie, w której pracowałem, ma wiele źle napisanych i źle utrzymanych klas „* Utils”. Częścią twojego zadania jest wiedzieć, co jest dostępne w Apache Commons.
AbuNassar,

1016

Co powiesz na to. Nie używa wyrażeń regularnych pod spodem, więc powinno być szybsze niż niektóre inne rozwiązania i nie będzie używać pętli.

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

122
Najprostszy sposób. Sprytny. I działa na Androidzie, gdzie nie ma klasy
StringUtils

43
To najlepsza odpowiedź. Najlepszym powodem jest to, że nie musisz importować innej biblioteki.
Alex Spencer,

27
Bardzo praktyczne, ale brzydkie jak diabli. Nie polecam tego, ponieważ prowadzi to do mylącego kodu.
Daniel San

32
Brzydki kod można zminimalizować, czyniąc go metodą we własnej klasie „StringUtils”. Wtedy brzydki kod jest dokładnie w jednym miejscu i wszędzie indziej jest dobrze czytelny.
RonR

30
Metoda pętli jest znacznie szybsza. Zwłaszcza gdy chcesz policzyć znak zamiast ciągu znaków (ponieważ nie ma metody String.replace (char, char)). W ciągu 15 znaków otrzymuję różnicę 6049 ns vs 26.739 ns (uśrednione po 100 biegach). Surowe liczby są ogromną różnicą, ale pod względem procentowym ... sumuje się. Unikaj przydziału pamięci - użyj pętli!
Ben

282

Podsumuj inną odpowiedź i to, co wiem na wszystkie sposoby, aby to zrobić za pomocą jednej linijki:

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

1) Korzystanie z Apache Commons

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

2) Korzystanie z Spring Framework

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

3) Za pomocą zamiany

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

4) Korzystanie z replaceAll (przypadek 1)

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

5) Korzystanie z replaceAll (przypadek 2)

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

6) Korzystanie z podziału

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

7) Korzystanie z Java8 (przypadek 1)

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

8) Korzystanie z Java8 (przypadek 2) może być lepsze dla Unicode niż przypadek 1

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

9) Korzystanie z StringTokenizer

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

Z komentarza : Uważaj na StringTokenizer, dla abcd zadziała, ale dla ... bc ... d lub ... abcd lub a ... b ...... c ..... d ... lub itp. to nie zadziała. To się po prostu liczy. między postaciami tylko raz

Więcej informacji w github

Test wydajności (przy użyciu JMH , mode = AverageTime, 0.010lepiej niż wynik 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

Wydrukowane ciągi nie pasują do powyższych, a kolejność jest najpierw najszybsza, co sprawia, że ​​wyszukiwanie jest co najmniej trudne. Dobra odpowiedź poza tym!
Maarten Bodewes

przypadek 2, uogólniony dla "1🚲2🚲3 has 2".codePoints().filter((c) -> c == "🚲".codePointAt(0)).count()
punktów kodowych

174

Wcześniej czy później coś musi się zapętlić. O wiele łatwiej jest napisać (bardzo prostą) pętlę niż użyć czegoś, splitco jest o wiele potężniejsze niż potrzebujesz.

Za wszelką cenę obuduj pętlę osobną metodą, np

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

Wtedy nie potrzebujesz pętli w głównym kodzie - ale pętla musi gdzieś tam być.


5
for (int i = 0, l = haystack.length (); i <l; i ++) bądź miły dla swojego stosu
Chris,

12
(Nie jestem nawet pewien, skąd pochodzi fragment komentarza „stos”. To nie jest tak, że moja odpowiedź jest rekurencyjna, co jest naprawdę nieprzyjemne dla stosu.)
Jon Skeet

2
nie tylko to, ale prawdopodobnie jest to anty optymalizacja bez spojrzenia na to, co robi jit. Jeśli zrobiłeś powyższe na przykład dla tablicy for loop, możesz pogorszyć sytuację.
ShuggyCoUk

4
@sulai: Troska Chrisa jest bezpodstawna, IMO, w obliczu trywialnej optymalizacji JIT. Czy jest jakiś powód, dla którego ten komentarz zwrócił Twoją uwagę ponad trzy lata później? Po prostu zainteresowany.
Jon Skeet

1
Prawdopodobnie @sulai po prostu natknęłam się na to pytanie, tak jak ja (zastanawiając się, czy Java ma wbudowaną metodę tego) i nie zauważyła dat. Jestem jednak ciekawy, jak przeniesienie length()wywołania poza pętlę może pogorszyć wydajność , o czym wspomniał @ShuggyCoUk w kilku komentarzach.
JKillian

63

Miałem pomysł podobny do Mladena, ale odwrotnie ...

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

Poprawny. ReplaceAll („.”) Zastąpi dowolny znak, nie tylko kropkę. Zastąpiłoby wszystko („\\.”) Działałoby. Twoje rozwiązanie jest prostsze.
VonC,

jjnguy faktycznie zasugerował najpierw replaceAll („[^.]”), po zobaczeniu mojego rozwiązania „abcd” .split („\\.”). length-1. Ale po pięciokrotnym trafieniu usunąłem swoją odpowiedź (i jego komentarz).
VonC

„... teraz masz dwa problemy” (oblig.) W każdym razie, założę się, że istnieją dziesiątki pętli wykonywany w replaceAll()i length(). Cóż, jeśli nie jest widoczny, to nie istnieje; o)
Piskvor opuścił budynek

2
nie sądzę, że dobrym pomysłem jest użycie wyrażenia regularnego i utworzenie nowego ciągu do liczenia. po prostu stworzę metodę statyczną, która zapętli każdy znak w ciągu, aby policzyć liczbę.
mingfai,

1
@mingfai: wprawdzie, ale pierwotne pytanie dotyczy tworzenia jednowierszowej, a nawet bez pętli (możesz zrobić pętlę w jednej linii, ale będzie brzydka!). Pytanie pytanie, a nie odpowiedź ... :-)
PhiLho

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

ReplaceAll („.”) Zastąpi wszystkie znaki.

Rozwiązanie PhiLho wykorzystuje ReplaceAll („[^.]”, „”), Którego nie trzeba usuwać, ponieważ [.] Reprezentuje znak „kropka”, a nie „dowolny znak”.


Ten mi się podoba. Oczywiście nadal istnieje pętla, tak jak musi być.
Archetypal Paul

Pamiętaj, że musisz podzielić tę liczbę, jeśli chcesz szukać podciągów o długości> 1
rogerdpack

30

Moje rozwiązanie „idiomatic one-liner”:

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

Nie mam pojęcia, dlaczego akceptowane jest rozwiązanie korzystające z StringUtils.


4
W tym poście jest starsze rozwiązanie podobne do tego.
JCalcines

7
Ponieważ to rozwiązanie jest naprawdę nieefektywne
András

To tworzy dodatkowy ciąg znaków, aby wygenerować liczbę. Nie mam pojęcia, dlaczego ktokolwiek wolałby to od StringUtils, jeśli StringUtils jest opcją. Jeśli nie jest to opcja, powinni po prostu stworzyć prostą pętlę for w klasie narzędzi.
zmiażdżyć

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

1
Głosuj + na natywne rozwiązanie.
Scadge

24

Krótszy przykład to

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

3
Ten wydaje się mieć stosunkowo duży narzut, ostrzegam, że może tworzyć wiele małych łańcuchów. Zwykle nie ma to większego znaczenia, ale należy zachować ostrożność.
Maarten Bodewes

19

oto rozwiązanie bez pętli:

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

Cóż, jest pętla, ale jest niewidoczna :-)

- Yonatan


2
O ile łańcuch nie jest tak długi, otrzymasz OutOfMemoryError.
Spencer Kormos

Wydaje się, że problem jest na tyle zmyślny, że może być pracą domową, a jeśli tak, to prawdopodobnie rekursja jest odpowiedzią, którą należy znaleźć.
erickson,

Używa indexOf, który zapętli ... ale fajny pomysł. Publikowanie naprawdę „tylko rekurencyjnego” rozwiązania za minutę ...
Jon Skeet,

Jeśli ma więcej wystąpień niż twoje dostępne miejsca na stosie, będziesz miał wyjątek przepełnienia stosu;)
Luca C.

15

Nie podoba mi się pomysł przydzielenia nowego ciągu do tego celu. A ponieważ ciąg znaków ma już tablicę char z tyłu, w której przechowuje swoją wartość, String.charAt () jest praktycznie wolny.

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

robi lewę, bez dodatkowych przydziałów, które wymagają zbierania, w 1 linii lub mniej, tylko z J2SE.


Uwielbiam ten, ponieważ jest on jedyną osobą wykonującą jedno przejście po sznurku. Zależy mi na wydajności.
javadba

1
charAtiteruje 16-bitowe punkty kodu, a nie znaki! A charw Javie nie jest znakiem. Tak więc ta odpowiedź sugeruje, że nie może być żadnego symbolu Unicode z wysoką wartością zastępczą równą punktowi kodowemu delim. Nie jestem pewien, czy jest poprawny dla kropki, ale ogólnie może być nieprawidłowy.
ceving

14

Ok, zainspirowany rozwiązaniem Yonatan, oto metoda czysto rekurencyjna - jedynymi używanymi metodami bibliotecznymi są length()i charAt()żadna z nich nie zapętla:

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

To, czy rekurencja liczy się jako zapętlenie, zależy od dokładnej definicji, której używasz, ale prawdopodobnie jest ona tak bliska, jak to tylko możliwe.

Nie wiem, czy obecnie większość maszyn JVM wykonuje rekursję ogona ... jeśli nie, oczywiście otrzymasz przepełnienie stosu dla odpowiednio długich łańcuchów.


Nie, rekurencja ogona prawdopodobnie będzie w Javie 7, ale nie jest jeszcze powszechna. Ta prosta, bezpośrednia rekurencja może być przetłumaczona na pętlę w czasie kompilacji, ale Java 7 jest tak naprawdę wbudowana w JVM, aby obsługiwać łańcuch przy użyciu różnych metod.
erickson,

3
Bardziej prawdopodobne byłoby uzyskanie rekurencji ogona, gdyby metoda zwróciła sobie wywołanie (w tym działający parametr sumaryczny), zamiast zwracać wynik dodawania.
Stephen Denne

12

Zainspirowany przez Jona Skeeta, wersję bez pętli, która nie wysadzi twojego stacka. Przydatny również punkt początkowy, jeśli chcesz korzystać ze szkieletu łączenia widelca.

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

(Zastrzeżenie: Nie przetestowano, nie skompilowano, nie ma sensu.)

Być może najlepszy sposób na napisanie tego (jednowątkowy, bez pary zastępczej):

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

11

Nie jestem pewien co do wydajności tego, ale jest to najkrótszy kod, jaki mógłbym napisać bez korzystania z bibliotek stron trzecich:

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

4
Aby liczyć wystąpień na końcu łańcucha trzeba będzie wywołać rozłam z limitem ujemne argumentu takiego: return (content.split(target, -1).length - 1);. Domyślnie wystąpienia na końcu łańcucha są pomijane w tablicy wynikającej z split (). Zobacz Doku
vlz

10

Z możesz również użyć strumieni, aby to osiągnąć. Oczywiście za kulisami jest iteracja, ale nie musisz pisać tego wprost!

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

Użycie .codePoints()zamiast .chars()tego obsługiwałoby wtedy dowolną wartość Unicode (włączając te wymagające par zastępczych)
Luke Usherwood

10

Możliwe jest również użycie funkcji zmniejszania w Javie 8, aby rozwiązać ten problem:

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

Wynik:

3

8

Pełna próbka:

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

Połączenie:

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

zły kod nie działa, gdy próbuję int wystąpień = CharacterCounter.countOccurrences („1”, „101”); System.out.println (wystąpienia); // 1
jayesh


8

Najprostszym sposobem na uzyskanie odpowiedzi jest:

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
Ten fragment kodu nie zwraca poprawnej liczby kropek dla podanego wejścia „abc”
dekaru,

@dekaru Czy możesz wkleić żądło w komentarzu, abyśmy mogli rzucić okiem.
Amar Magar

5

Jeśli używasz frameworka Spring, możesz również użyć klasy „StringUtils”. Metodą będzie „countOccurrencesOf”.


5

Możesz użyć tej split()funkcji w jednym kodzie linii

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

Split naprawdę tworzy tablicę ciągów, co zajmuje dużo czasu.
Palec

Masz rację, to prawdziwa troska. W inny sposób unika wprowadzania biblioteki osób trzecich do twojego projektu (jeśli jeszcze tego nie zrobiono). To zależy od tego, co chcesz zrobić i jakie są oczekiwania dotyczące wydajności.
Benj

3
To rozwiązanie NIE będzie zawierać końcowych pustych trafień, ponieważ argument limitjest ustawiony na zero w tym przeciążonym wywołaniu metody split. Przykład: "1##2#3#####".split("#")zwróci tylko tablicę o rozmiarze 4 ( [0:"1";1:""; 2:"2"; 3:"3"]) zamiast wielkości 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

Chociaż metody mogą to ukryć, nie ma możliwości liczenia bez pętli (lub rekurencji). Chcesz jednak użyć char [] ze względu na wydajność.

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

Korzystanie z replaceAll (czyli RE) nie brzmi jak najlepsza droga.


Myślę, że to najbardziej eleganckie rozwiązanie. Dlaczego użyłeś toCharArray, a nie charAt bezpośrednio?
Panayotis

Pętla z charAt przynajmniej była wolniejsza. Może zależeć również od platformy. Jedynym sposobem, aby naprawdę się dowiedzieć, jest zmierzenie różnicy.
tcurdt

3

Z całkiem podobnym zadaniem natknąłem się na ten wątek. Nie widziałem żadnych ograniczeń języka programowania, a ponieważ groovy działa na Javie vm: Oto, jak mogłem rozwiązać mój problem za pomocą Groovy.

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

gotowy.


3

Znacznie łatwiejszym rozwiązaniem byłoby podzielenie łańcucha na podstawie znaku, z którym go dopasujesz.

Na przykład,

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

Zwróci 4 w przypadku: getOccurences("o", "something about a quick brown fox");


Problem polega na tym, że tablica musi być przydzielona, ​​co jest strasznie wolne.
Palec

2

Gdzieś w kodzie coś musi zapętlić. Jedynym sposobem na obejście tego jest całkowite rozwinięcie pętli:

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

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


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

... itd., ale to Ty robisz pętlę ręcznie w edytorze źródłowym - zamiast komputera, który go uruchomi. Zobacz pseudokod:

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

Oto nieco inne rozwiązanie rekurencyjne w stylu:

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

Dlaczego nie po prostu podzielić na postać, a następnie uzyskać długość wynikowej tablicy. długość tablicy zawsze będzie liczbą wystąpień + 1. Prawda?


2

Poniższy kod źródłowy poda liczbę wystąpień danego ciągu w słowie wprowadzonym przez użytkownika: -

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();
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.