Bir Dizede bir karakterin oluşum sayısını nasıl hesaplayabilirim?


547

İpim var

a.b.c.d

'' Oluşumlarını saymak istiyorum. deyimsel bir şekilde, tercihen bir astar.

(Daha önce herkesin bir döngü kullanmadan neden cevap vermeye çalıştığını merak ediyorsanız, bu kısıtlamayı "döngü olmadan" olarak ifade etmiştim).


1
Ev ödevi? Çünkü aksi takdirde döngüden kaçınma gereksinimini görmüyorum.
PhiLho

22
Deyimsel bir astar bulmak kadar döngüden uzak değil.
Bart

2
Döngüler böyle bir sorun için yapıldı, döngüyü ortak bir Utility sınıfına yazın ve ardından yeni basılmış bir astarınızı çağırın.
che javara

Benzer sorgular için dizeleri: stackoverflow.com/questions/767759/…
koppor

Sadece belirtmek gerekirse - Tek astarları bulmayı takdir ediyorum, eğlenceli ve (gerçek bir avantaj olarak) hatırlanması genellikle kolay, ancak ayrı bir yöntem ve bir döngünün hemen hemen her şekilde daha iyi olduğunu belirtmek istiyorum. - okunabilirlik ve hatta performans. Aşağıdaki "Zarif" çözümlerin çoğu, dizeleri yeniden biçimlendirmeyi / bellek kopyalamayı içerdiğinden çok iyi performans göstermeyecek, ancak dizeyi taranan ve sayılan olayları tekrarlayan bir döngü hızlı ve basit olacaktır. Bu performans genellikle bir faktör olmamalı, ancak bir satıra bir satır üzerinden bakmayın ve daha iyi performans göstereceğini varsayalım.
Bill K

Yanıtlar:


722

Bunun için benim 'deyimsel bir astar':

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

Zaten müşterek dildeyken neden kendiniz yazıyorsunuz ?

Spring Framework'ün bu konudaki öncüsü:

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

44
Guava eşdeğeri: int count = CharMatcher.is('.').countIn("a.b.c.d");... Yinelenen bir soruda dogbane tarafından yanıtlandığı gibi .
Jonik

25
Her ne kadar bu aşağılamak olmaz, (a) 3. parti libs gerektiren ve (b) pahalı.
javadba

Bu sadece bahar çerçeve çalışması ile çalışmak zorunda.
Isuru Madusanka


19
Pahalı olduğum, çalıştığım her şirkette pek çok kötü yazılmış ve bakımlı "* Utils" dersine sahip olmak. İşinizin bir kısmı Apache Commons'ta neler olduğunu bilmek.
AbuNassar

1016

Buna ne dersin. Altında regexp kullanmaz, bu nedenle diğer bazı çözümlerden daha hızlı olmalı ve bir döngü kullanmayacaktır.

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

122
En kolay yol. Zeki olan. Ve StringUtils sınıfının olmadığı Android'de çalışıyor
Jose_GD

43
Bu en iyi cevap. Bunun en iyi nedeni, başka bir kitaplığı içe aktarmanız gerekmemesidir.
Alex Spencer

27
Çok pratik ama cehennem kadar çirkin. Kafa karıştırıcı kod yol açar gibi bunu tavsiye etmiyoruz.
Daniel San

32
Çirkin kod, kendi "StringUtils" sınıfınızda bir yöntem haline getirilerek en aza indirilebilir. Sonra çirkin kod tam olarak bir noktada ve her yerde güzel okunabilir.
RonR

30
Döngü yöntemi bundan çok daha hızlıdır. Özellikle String yerine bir char saymak istediğinizde (String.replace (char, char) yöntemi olmadığından). 15 karakterlik bir dizede, 26499 ns (100run üzerinde ortalama) 6049 ns fark var. Ham sayılar büyük farktır, ama akıllıca algılarlar ... Bellek ayırma kaçının - bir döngü kullanın!
Ben

282

Diğer yanıtı ve tek katmanlı kullanarak bunu yapmanın tüm yollarını bildiklerimi özetleyin:

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

1) Apache Commons Kullanımı

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

2) Spring Framework'leri kullanma

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

3) değiştirin kullanma

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

4) replaceAll kullanma (durum 1)

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

5) replaceAll kullanma (durum 2)

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

6) split kullanma

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

7) Java8 kullanma (durum 1)

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

8) Java8 (durum 2) kullanmak, unicode için durum 1'den daha iyi olabilir

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

9) StringTokenizer Kullanımı

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

Yorumdan : StringTokenizer için dikkatli olun, abcd için işe yarayacak ama ... bc ... d veya ... abcd veya a ... b ...... c ..... d için ... ya da vs. çalışmaz. Sadece sayılır. karakterler arasında sadece bir kez

Daha fazla bilgi github

Performans testi ( JMH kullanarak , mode = AverageTime, 0.010o zaman daha iyi puan 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

Basılı dizeler yukarıdaki dizelerle eşleşmez ve sipariş en hızlısıdır, bu da en azından aramayı zorlaştırır. Güzel cevap başka türlü!
Maarten Bodewes

birden fazla UTF-16 kod birimine ihtiyaç duyan kod noktaları için genelleştirilmiş durum 2:"1🚲2🚲3 has 2".codePoints().filter((c) -> c == "🚲".codePointAt(0)).count()
Tom Blodget

174

Er ya da geç, bir şey ilmek yapmalıdır. splitİhtiyacınız olandan çok daha güçlü bir şey kullanmaktan (çok basit) döngüyü yazmak sizin için çok daha basittir .

Her halükarda, döngüyü ayrı bir yöntemde, örneğin

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

O zaman döngü ana kodunuzda olması gerekmez - ama döngü orada bir yerde olmalı.


5
(int i = 0, l = haystack.length (); i <l; i ++) yığına nazik ol
Chris

12
(Yorumun "yığın" bitinin nereden geldiğinden bile emin değilim. Bu cevabın özyinelemeli olduğu yinelenen bir cevap gibi değil .)
Jon Skeet

2
sadece bu da değil, bu muhtemelen jit'in ne yaptığına bakmadan bir anti optimizasyon. Yukarıdaki işlemi bir dizi döngü için yaptıysanız, işleri daha da kötüleştirebilirsiniz.
ShuggyCoUk

4
@sulai: Chris'in endişesi önemsiz bir JIT optimizasyonu karşısında temelsiz IMO . Üç yıl sonra, yorumun şu anda dikkatinizi çekmesinin bir nedeni var mı? Sadece ilginç.
Jon Skeet

1
Muhtemelen @sulai soruya sadece benim gibi geldi (Java bunun için yerleşik bir yöntem olup olmadığını merak ederken) ve tarihleri ​​fark etmedi. Ancak, @ShuggyCoUk tarafından birkaç yorumda belirtildiği gibi , length()çağrıyı döngü dışına taşımanın performansı nasıl daha da kötüleştirebileceğini merak ediyorum .
JKillian

63

Mladen'e benzer bir fikrim vardı, ama tam tersi ...

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

Doğru. ReplaceAll (".") Yalnızca nokta yerine herhangi bir karakterin yerini alır. ReplaceAll ("\\.") İşe yarardı. Çözümünüz daha basittir.
VonC

jjnguy, "abcd" .split ("\\."). uzunluk-1 çözümümü görünce, öncelikle bir replaceAll ("[^.]") önerdi. Ama 5 kez vurulduktan sonra cevabımı (ve yorumunu) sildim.
VonC

(oblig.) "... şimdi iki sorun var" Neyse, içinde yürütme döngüler onlarca olduğunu bahse girerim replaceAll()ve length().
Görünmüyorsa

2
regex kullanmak ve sayma için yeni bir dize oluşturmak iyi bir fikir olduğunu sanmıyorum. i sadece sayıyı saymak için dizedeki her karakteri döngü statik bir yöntem oluşturmak.
mingfai

1
@mingfai: ama asıl soru bir astar ve hatta bir döngü olmadan yapmakla ilgilidir (bir satırda bir döngü yapabilirsiniz, ancak çirkin olacak!). Soruyu sor, cevabı değil ... :-)
PhiLho

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

ReplaceAll (".") Tüm karakterlerin yerine geçer.

PhiLho'nun çözümü , kaçması gerekmeyen ReplaceAll ("[^.]", "") 'İ kullanır, çünkü [.]' Herhangi bir karakteri 'değil' nokta 'karakterini temsil eder.


Bunu beğendim. Elbette, olması gerektiği gibi, hala bir döngü var.
Arketipik Paul

> 1
rogerdpack

30

'İdiyomatik tek katlı' çözümüm:

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

StringUtils kullanan bir çözümün neden kabul edildiğine dair bir fikriniz yok.


4
Bu yazıda buna benzer daha eski bir çözüm var.
JCalcines

7
Çünkü bu çözüm gerçekten verimsiz
András

Bu sadece bir sayı üretmek için ekstra bir dize oluşturur. Eğer StringUtils bir seçenek ise neden herkes StringUtils tercih edeceğini bilmiyorum. Bu bir seçenek değilse, yalnızca bir yardımcı program sınıfında basit bir döngü oluşturmaları gerekir.
ezmek

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

1
Yerel bir çözüm için + seçeneğine oy verin.
Scadge

24

Daha kısa bir örnek

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

3
Bu nispeten büyük bir ek yüke sahip gibi görünüyor, çok sayıda küçük dize oluşturabileceği konusunda uyarılmalıdır. Normalde bu çok önemli değil, dikkatli kullanın.
Maarten Bodewes

19

İşte döngü olmayan bir çözüm:

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

iyi, bir döngü var, ama görünmez :-)

- Yonatan


2
Dizeniz çok uzun olmadıkça bir OutOfMemoryError alırsınız.
Spencer Kormos

Sorun kulağa ödev olacak kadar çelişkili ve eğer öyleyse, bu özyineleme muhtemelen bulmanız gereken cevaptır.
erickson

Döngüsü olacak indexOf kullanır ... ama güzel bir fikir. Bir dakika içinde gerçekten "sadece özyinelemeli" bir çözüm
yayınlıyoruz

Kullanılabilir yığın yuvalarınızda daha fazla olay varsa, yığın taşması istisnasına sahip olursunuz;)
Luca C.

15

Bu amaç için yeni bir dize tahsis etme fikrini sevmiyorum. Ve dize zaten değerini sakladığı bir char dizisine sahip olduğundan, String.charAt () pratik olarak ücretsizdir.

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

sadece J2SE ile 1 satır veya daha az toplama gerektiren ek tahsisler olmadan hile yapar.


Bunun için biraz sevgi vermek, çünkü ip üzerinden tek bir geçiş yapan tek kişi. Performansı önemsiyorum.
javadba

1
charAtkarakterleri değil 16 bit kod noktaları üzerinden yineler! charJava'daki A bir karakter değildir. Dolayısıyla bu cevap, yüksek bir vekilin kod noktasına eşit olduğu bir Unicode sembolü olmaması gerektiği anlamına gelir delim. Nokta için doğru olup olmadığından emin değilim, ancak genel olarak doğru olmayabilir.
ceving

14

Tamam, Yonatan'ın çözümünden esinlenilen, işte tamamen özyinelemeli - kullanılan tek kütüphane yöntemleri length()ve charAt()ikisi de herhangi bir döngü yapmıyor:

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

Özyinelemenin döngü olarak sayılıp sayılmayacağı, tam olarak hangi tanımı kullandığınıza bağlıdır, ancak muhtemelen alabileceğiniz kadar yakındır.

Çoğu JVM'lerin bugünlerde kuyruk özyineleme yapıp yapmadığını bilmiyorum ... Aksi takdirde, uygun şekilde uzun dizeler için adsız yığın taşması elde edersiniz.


Hayır, kuyruk özyineleme muhtemelen Java 7'de olacak, ancak henüz yaygın değil. Bu basit, doğrudan kuyruk özyineleme derleme zamanında bir döngüye çevrilebilir, ancak Java 7 şeyler aslında farklı yöntemlerle zincirleme işlemek için JVM'de yerleşiktir.
erickson

3
Yönteminiz, bir ekleme yapmanın sonucunu döndürmek yerine, kendisine bir çağrı (çalışan bir toplam parametresi dahil) döndürdüyse, kuyruk özyineleme alma olasılığınız daha yüksektir.
Stephen Denne

12

Yığınınızı uçurmayacak döngü olmayan bir versiyon olan Jon Skeet'ten esinlenmiştir. Çatalla birleştirme çerçevesini kullanmak istiyorsanız da kullanışlı başlangıç ​​noktası.

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

(Feragatname: Test edilmedi, derlenmedi, mantıklı değil.)

Belki de yazmanın en iyi (tek iş parçacıklı, yedek çift desteği yok) yolu:

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

11

Bunun etkinliği hakkında emin değilim, ancak 3. taraf kütüphanelerini getirmeden yazabileceğim en kısa kod:

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

4
Ayrıca dize sonundaki tekrarlarını saymak için böyle bir negatif limiti bağımsız değişkenle bölünmüş aramak zorunda kalacak: return (content.split(target, -1).length - 1);. Varsayılan olarak, dizenin sonundaki oluşumlar split () öğesinden kaynaklanan Dizide yok sayılır. Bkz Doku
vlz

10

İle bunu başarmak için de akışları kullanabilirsiniz. Açıkçası perde arkasında bir yineleme var, ama bunu açıkça yazmak zorunda değilsiniz!

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

.codePoints()Bunun yerine kullanmak .chars()daha sonra herhangi bir Unicode değerini destekleyecektir (vekil çiftler gerektirenler dahil)
Luke Usherwood

10

Ayrıca bu sorunu çözmek için Java 8'de azaltma kullanmak da mümkündür:

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

Çıktı:

3

8

Tam örnek:

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

Aramak:

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

int kodları denediğinizde yanlış kod çalışmıyor = CharacterCounter.countOccurrences ("1", "101"); System.out.println (görünüm); // 1
jayesh

Aynı mantıkla çalışan kod için bir düzeltme taahhüt ediyorum
MaanooAk 27:07

8

Cevabı almanın en basit yolu aşağıdaki gibidir:

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
Bu snippet, belirli bir "abc" girdisi için doğru nokta
sayısını döndürmüyor

@dekaru Bir göz atabilmemiz için lütfen sokağınızı yoruma yapıştırır mısınız?
Amar Magar

5

Spring framework kullanıyorsanız "StringUtils" sınıfını da kullanabilirsiniz. Yöntem "countOccurrencesOf" olacaktır.


5

Sen kullanabilirsiniz split()sadece bir satır kod işlevi

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

Split gerçekten çok zaman harcayan dizeleri oluşturur.
Palec

Haklısın, bu gerçek bir endişe. Başka bir şekilde, projenize üçüncü taraf bir lib getirilmesini önler (henüz yapılmadıysa). Ne yapmak istediğinize ve performans beklentisinin ne olduğuna bağlıdır.
Benj

3
Bu çözüm, arkadaki boş isabetleri İÇERMEZ çünkü argüman limitaşırı yüklenmiş bölünmüş yöntem çağrısında sıfıra ayarlandığından. Örnek: 9 ( ) yerine "1##2#3#####".split("#")yalnızca 4 ( [0:"1";1:""; 2:"2"; 3:"3"]) boyutunda bir dizi verir [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

Yöntemler gizleyebilse de, bir döngü (veya özyineleme) olmadan saymanın bir yolu yoktur. Performans nedenlerinden dolayı bir char [] kullanmak istiyorsunuz.

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

ReplaceAll (yani RE) kullanmak en iyi yol gibi görünmüyor.


Bence bu en zarif çözüm. Neden doğrudan charAt yerine toCharArray kullandınız?
Panayotis

CharAt ile döngü yapmak en azından daha yavaştı. Platforma da bağlı olabilir. Gerçekten öğrenmenin tek yolu farkı ölçmek olacaktır.
tcurdt

3

Oldukça benzer bir görevle bu Konuya rastladım. Hiçbir programlama dili kısıtlaması görmedim ve groovy bir java vm üzerinde çalıştığı için: İşte Groovy kullanarak Sorunumu nasıl çözebildim.

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

yapılır.


3

Çok daha kolay bir çözüm, dizeyi sadece eşleştirdiğiniz karaktere göre bölmek olacaktır.

Örneğin,

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

Aşağıdaki durumlarda 4 geri dönecektir: getOccurences("o", "something about a quick brown fox");


Buradaki sorun, çok yavaş olan bir dizinin ayrılması gerektiğidir.
Palec

2

Kodun bir yerinde, bir şeyin ilmek yapması gerekir. Bunun tek yolu, döngüyü tamamen açmaktır:

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

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


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

... vb, ancak daha sonra, döngüyü çalıştıran bilgisayar yerine, kaynak düzenleyicide elle yapan kişisiniz. Sözde koduna bakın:

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

İşte biraz farklı bir stil özyineleme çözümü:

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

Neden sadece karaktere bölünmez ve sonuçta elde edilen dizinin uzunluğunu elde edemezsiniz. dizi uzunluğu her zaman örnek sayısı + 1 olacaktır. Değil mi?


2

Aşağıdaki kaynak kodu, kullanıcı tarafından girilen bir sözcükte belirli bir dizenin hiçbir örneğini verecektir: -

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();
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.