Bir dizedeki (aslında bir karakter) bir dizenin oluşumlarını nasıl sayacaksınız?


864

/Bir dizede kaç tane s bulabileceğimi saymak istediğimi fark ettiğim bir şey yapıyorum ve sonra bana bunu yapmanın birkaç yolu olduğunu, ancak en iyi (veya en kolay) ne olduğuna karar veremedi .

Şu anda şöyle bir şeyle gidiyorum:

string source = "/once/upon/a/time/";
int count = source.Length - source.Replace("/", "").Length;

Ama hiç hoşlanmadım, herhangi bir kişi?

Bunun için gerçekten kazmak istemiyorum, değil RegExmi?

Dize benim aradığım terimi olacak biliyorum, bu yüzden varsayalım ...

Dizeleri Tabii burada uzunluğu> 1 ,

string haystack = "/once/upon/a/time";
string needle = "/";
int needleCount = ( haystack.Length - haystack.Replace(needle,"").Length ) / needle.Length;

34
+1: saymak için çok farklı bir yol olduğunu söylemeliyim. Ben bench mark test sonuçlarına şaşırdım :)
naveen

4
O SQL bu işlevi uygulamak için tipik yol ... çok farklı değil: LEN(ColumnToCheck) - LEN(REPLACE(ColumnToCheck,"N","")).
Sheridan

6
Nitekim "/".Length
Gerard

3
"/////" içinde "//" tekrar sayısı için sayımın ne olması gerektiğini söyleyebilir miyim? 2 mi 4 mü?
Les

1
regex kullanmak muhtemelen bu konuda en iyi yoldur
Adam Higgins

Yanıtlar:


1009

.NET 3.5 kullanıyorsanız, bunu LINQ ile tek katmanlı olarak yapabilirsiniz:

int count = source.Count(f => f == '/');

LINQ kullanmak istemiyorsanız, aşağıdakileri yapabilirsiniz:

int count = source.Split('/').Length - 1;

Orijinal tekniğinizin her ikisinden de yaklaşık% 30 daha hızlı olduğunu öğrenmek sizi şaşırtabilir! Ben sadece "/ once / upon / a / time /" ile hızlı bir karşılaştırma yaptım ve sonuçlar aşağıdaki gibidir:

Orijinal = 12s
kaynak.Count = 19s kaynak.Split =
17s
foreach ( bobwienholt'un cevabından ) = 10s

(Zamanlar 50.000.000 yineleme içindir, bu nedenle gerçek dünyada çok fazla fark görmeniz olası değildir.)


6
Evet, VS string sınıfında LINQ genişletme yöntemlerini gizler. Sanırım devs, tüm bu uzatma yöntemlerinin dize sınıfında görünmesini istemezlerdi. Muhtemelen akıllıca bir karar.
Judah Gabriel Himango

11
Bu davranış VS2010 otomatik olarak System.Linq yeni sınıf dosyalarına dahil, VS2008 muhtemelen içermiyor olabilir. Zekanın çalışması için isim alanının olması gerekir.
Sprague

30
Say ve Böl çözümlerinin yalnızca karakterleri saydığınızda çalışacağını unutmayın. OP'nin çözümü gibi dizelerle çalışmazlar.
Peter Lillevold

5
f == '\' bir dizede karakterlerle ilgilidir, bir dizede dizelerle değil
Thomas Weller

9
Bu, farklı bir sorunun cevabı gibi görünüyor: "Bir karakter dizisindeki bir karakterin oluşumunu nasıl sayacaksınız?"
Ben Aaronson

181
string source = "/once/upon/a/time/";
int count = 0;
foreach (char c in source) 
  if (c == '/') count++;

source.Replace()Kendisinden daha hızlı olmalı .


18
Bir foreach yerine bir for'a geçerek marjinal bir gelişme elde edebilirsiniz, ancak sadece küçük, küçük bir parça.
Mark

17
Hayır. Soru, karakter değil, dizenin oluşumunu saymayı ister.
YukiSakura

3
Bu, bir dizedeki karakterleri sayıyor. Başlık bir dizede dizeleri saymakla ilgili
Thomas Weller

2
@ Mark Sadece bir for döngüsü ile test etti ve aslında foreach kullanmaktan daha yavaştı. Sınır kontrolü nedeniyle olabilir mi? (Saat, 5 mil yineleme 2.05 vs 1.65 saniye idi.)
Ölçüm

4
Soru bir dize içinde bir dize sorarken, OP yayınlanan örnek aslında sadece bir karakter, bu durumda daha iyi bir yol (dize arama yerine char arama) gösterdiği için bu yanıtı hala geçerli bir çözüm olarak adlandırırım sorunu ele almak için.
Çad

136
int count = new Regex(Regex.Escape(needle)).Matches(haystack).Count;

8
+1 - Bazı durumlarda eklemek isteyebilirsiniz RegexOptions.IgnoreCase.
TrueWill

3
bu inanılmaz derecede düşük değil mi?
Thomas Ayoub

4
Regex yükü ideal değil, artı "Gerçekten bunun için RegEx'i kazmak istemiyorum, değil mi?"
Çad

istemeyebilirsiniz Regex.Escape(...)öylesinenew System.Text.RegularExpressions.Regex(needle).Matches(haystack).Count;
barlop

2
Ben bununla gittim çünkü sadece karakterleri değil dizeleri de arayabiliyor.
James in Indy

86

Yalnızca karakterleri değil, tüm karakter dizilerini arayabilmek istiyorsanız:

src.Select((c, i) => src.Substring(i))
    .Count(sub => sub.StartsWith(target))

"Dizedeki her karakter için, bu karakterden başlayarak dizenin geri kalanını bir alt dize olarak alın; hedef dize ile başlıyorsa sayın."


1
Nasıl verilen açıklamadan daha net bir şekilde açıklayabilirim emin değilim. Kafa karıştırıcı olan nedir?
mqp

58
SÜPER YAVAŞ! Bir html sayfasında denedim ve bu sayfada 2 saniye süren diğer yöntemlere kıyasla yaklaşık 2 dakika sürdü. Cevap doğruydu; kullanılabilir olmak için çok yavaştı.
JohnB

2
kabul etti, çok yavaş. Ben linq tarzı çözümler büyük bir hayranıyım ama bu sadece geçerli değil.
Sprague

5
Bunun bu kadar yavaş olmasının sebebinin n dizeleri oluşturması ve kabaca n ^ 2/2 bayt ayırmasıdır.
Peter Crabtree

6
OutOfMemoryException benim 210000 karakter dizesi için atılır.
bitiş

66

Biraz araştırma yaptım ve Richard Watson'un çözümünün çoğu durumda en hızlı olduğunu buldum . Bu, gönderideki her çözümün sonuçlarını içeren tablodur ( Regex kullananların dışında "test {test" gibi dize ayrıştırılırken istisnalar atar)

    Name      | Short/char |  Long/char | Short/short| Long/short |  Long/long |
    Inspite   |         134|        1853|          95|        1146|         671|
    LukeH_1   |         346|        4490|         N/A|         N/A|         N/A|
    LukeH_2   |         152|        1569|         197|        2425|        2171|
Bobwienholt   |         230|        3269|         N/A|         N/A|         N/A|
Richard Watson|          33|         298|         146|         737|         543|
StefanosKargas|         N/A|         N/A|         681|       11884|       12486|

Kısa dizelerde (1-5 karakter) kısa alt dizelerin (1-5 karakter) oluşum sayısını bulmanız durumunda orijinal algoritmanın tercih edildiğini görebilirsiniz.

Ayrıca, çok karakterli alt dize için aşağıdaki kodu kullanmalısınız ( Richard Watson'un çözümüne dayanarak )

int count = 0, n = 0;

if(substring != "")
{
    while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1)
    {
        n += substring.Length;
        ++count;
    }
}

Kendi 'düşük seviye' çözümümü eklemek üzereydim (alt dizeler oluşturmadan, replace / split veya herhangi bir Regex / Linq kullanarak), ama seninki muhtemelen benimkinden bile daha iyi (ve en azından daha kısa). Teşekkürler!
Dan W

Regex çözümleri için, aRegex.Escape(needle)
Timin

2
Sadece başkaları için işaret etmek gerekirse, boşsa arama değerinin kontrol edilmesi gerekir, aksi takdirde sonsuz bir döngüye girersiniz.
WhoIsRich

2
Belki de sadece ben, ama source="aaa" substring="aa"ben 2 değil, 1 geri almak bekleniyor. Bunu "düzeltmek" n += substring.Lengthiçin,n++
ytoledano

overlappeddavanızı böyle karşılamak için bayrak ekleyebilirsiniz :overlapped=True;.... if(overlapped) {++n;} else {n += substring.Length;}
tsionyx

54

LINQ tüm koleksiyonlarda çalışır ve dizeler sadece bir karakter koleksiyonu olduğu için, bu güzel küçük tek katlıya ne dersiniz:

var count = source.Count(c => c == '/');

Bu ad alanından bir uzantı yöntemi using System.Linq;gibi kod dosyanızın en üstünde bulunduğunuzdan emin olun .Count.


5
Gerçekten var var kullanmaya değer mi? Kont'un bir int döndürmeyen bir şeyle değiştirilmesi ihtimali var mı?
09:00

69
@Whatsit: 'int' iki elinizi gerektirirken sadece sol elinizle 'var' yazabilirsiniz;)
Sean Bright

7
intHarflerin tümü ev anahtarlarında bulunurken var, yok. ahh .. bekle, ben Dvorak kullanıyorum
Michael Buen

2
@BDotA 'System.Linq kullanarak;' kullandığınızdan emin olun. tıklayın. Ayrıca, intellisense, bir dize olduğundan .Count çağrısını sizden gizleyebilir. Yine de, derlenecek ve iyi çalışacaktır.
Judah Gabriel Himango

3
@JudahGabrielHimango Var değişkeninin özellikle değişken tür açık olduğunda (ve kısalık ve tutarlılık için) kullanılması gerektiğini
savunacağım

50
string source = "/once/upon/a/time/";
int count = 0;
int n = 0;

while ((n = source.IndexOf('/', n)) != -1)
{
   n++;
   count++;
}

Bilgisayarımda, 50 milyon yineleme için her karakter için çözümden yaklaşık 2 saniye daha hızlı.

2013 revizyonu:

Dizeyi bir char [] olarak değiştirin ve bunu yineleyin. 50m yineleme için toplam süreyi bir iki saniye daha keser!

char[] testchars = source.ToCharArray();
foreach (char c in testchars)
{
     if (c == '/')
         count++;
}

Bu hala daha hızlı:

char[] testchars = source.ToCharArray();
int length = testchars.Length;
for (int n = 0; n < length; n++)
{
    if (testchars[n] == '/')
        count++;
}

İyi bir ölçü için, dizinin sonundan 0'a yineleme, en hızlı, yaklaşık% 5 gibi görünüyor.

int length = testchars.Length;
for (int n = length-1; n >= 0; n--)
{
    if (testchars[n] == '/')
        count++;
}

Bunun neden olabileceğini merak ediyordum ve Googling (daha hızlı olmanın tersine yineleme hakkında bir şey hatırlıyorum) ve rahatsız edici bir şekilde char [] tekniği için dizeyi kullanan bu SO sorusuna geldi. Yine de bu bağlamda geri dönüş hilesi yeni.

C # 'da bir dizede tek tek karakterler arasında yineleme yapmanın en hızlı yolu nedir?


1
Sen source.IndexOf('/', n + 1)ve n++süre parantez koymak ve kaybetmek olabilir :) Ayrıca, string word = "/"karakter yerine bir değişken koymak .
neeKo

1
Hey Niko, yeni cevaplara bak. Yine de, değişken uzunluklu alt dize yapmak daha zor olabilir.
Richard Watson

Subtring'e adım atarak benzer bir şey kullandım; indexOf bir startIndex olduğunu fark edene kadar. İlk çözümü en çok sevdim, çünkü hız ve bellek ayak izi arasında iyi bir denge var.
Samir Banjanovic

1
Bir yerde geriye doğru yineleme
yapmanın

1
@ shitpoet yup. Temeldeki koda bakarsanız, bu yerel bir çağrıdır. public char [] toCharArray () {... System.arraycopy (değer, 0, sonuç, 0, değer.uzunluk); ...}
Richard Watson

46

Her ikisi de sadece tek karakterli arama terimleri için çalışır ...

countOccurences("the", "the answer is the answer");

int countOccurences(string needle, string haystack)
{
    return (haystack.Length - haystack.Replace(needle,"").Length) / needle.Length;
}

daha uzun iğneler için daha iyi olabilir ...

Ama daha zarif bir yol olmalı. :)


Çok karakterli değiştirmeleri hesaba katmak için. Bu olmadan, "test" anahtar sayılır "saymak 6 dönecekti.
ZombieSheep

Kıyaslama ve bunu dize ile karşılaştırdı. Kıvrımlı yol - yaklaşık 1,5 kat daha hızlı çalışır. Kudos.
Alex

20

Düzenle:

source.Split('/').Length-1

2
Bu benim işim. Ve source.Split(new[]{"//"}, StringSplitOptions.None).Count - 1çok karakterli ayırıcılar için.
bzlm

4
Bu yığın üzerinde en az n dize ayırma artı artı (muhtemelen) birkaç dizi yeniden boyutları - ve tüm bunlar sadece saymak için? Son derece verimsizdir, iyi ölçeklenmez ve hiçbir zaman önemli bir kodda kullanılmamalıdır.
Zar Shardan

17

C # 'da, güzel bir String SubString sayacı bu beklenmedik şekilde zor olan arkadaştır:

public static int CCount(String haystack, String needle)
{
    return haystack.Split(new[] { needle }, StringSplitOptions.None).Length - 1;
}

1
Güzel bir çözüm - ve dize için de çalışıyor (sadece char değil)!
ChriPf

Teşekkürler, dil değiştirirken dize işlemenin bazı inceliklerini unutmak çok kolay - çoğumuzun bugünlerde olduğu gibi!
Dave

1
-1 çünkü: Count () ve Count veya Length arasındaki farkı biliyor musunuz? Birisi Count veya Uzunluk yerine Count () kullanıyorsa tetikleniyorum. Count () oluşturur IEnumerator sonra IEnumerable tüm oluşumları gider, ancak Count veya Length zaten tüm öğeleri yinelemeye gerek kalmadan istediğiniz sayıyı zaten tutan nesnenin özellikleri ayarlanmış.
aeroson

İyi nokta, ve garip olan şey, kütüphanemde, işlevi aldığım yerden, "Uzunluk" kullanıyorum. Düzenlenen!
Dave

15
Regex.Matches(input,  Regex.Escape("stringToMatch")).Count

1
Girdi kontink normal regex özel karakterleri ise bu doğru değil yani | Manzara (giriş) olmalı
Esben Skov Pedersen

1
Aslında stringToMatchihtiyaçlar kaçıyor, değil input.
Theodor Zoulias

Aynen öyle. Onu düzeltti.
cederlof

13
private int CountWords(string text, string word) {
    int count = (text.Length - text.Replace(word, "").Length) / word.Length;
    return count;
}

Orijinal çözüm, karakterler için en hızlı olduğu için, sanırım dizeler için de olacak. İşte benim katkım.

Bağlam için: Bir günlük dosyasında 'başarısız' ve 'başarılı' gibi kelimeler arıyordum.

Gr, Ben


2
Sadece "word" değişkeni için boş bir dize (sıfır hata ile bölme) geçmeyin.
Andrew Jens

12
string s = "65 fght 6565 4665 hjk";
int count = 0;
foreach (Match m in Regex.Matches(s, "65"))
  count++;

20
veya Regex.Matches (s, "65"). Say ^ _ ^
Meta

Her dize için çalışmaz. "Abc ++ def ++ xyz" içinde "++" aramayı deneyin
marsh-wiggle

7

Kullanıma hazır bir String uzantısı yöntemi isteyen herkes için,

İşte yayınlanan cevapların en iyi dayalı ne kullanıyorum:

public static class StringExtension
{    
    /// <summary> Returns the number of occurences of a string within a string, optional comparison allows case and culture control. </summary>
    public static int Occurrences(this System.String input, string value, StringComparison stringComparisonType = StringComparison.Ordinal)
    {
        if (String.IsNullOrEmpty(value)) return 0;

        int count    = 0;
        int position = 0;

        while ((position = input.IndexOf(value, position, stringComparisonType)) != -1)
        {
            position += value.Length;
            count    += 1;
        }

        return count;
    }

    /// <summary> Returns the number of occurences of a single character within a string. </summary>
    public static int Occurrences(this System.String input, char value)
    {
        int count = 0;
        foreach (char c in input) if (c == value) count += 1;
        return count;
    }
}

Aktarılan dize null veya boşsa ikinci yöntem patlamayacak mı? Tamamen bir stil bakış açısından, girdiyi sadece string yerine System.String olarak tanımlıyorsunuz?
Nodoid

7
public static int GetNumSubstringOccurrences(string text, string search)
{
    int num = 0;
    int pos = 0;

    if (!string.IsNullOrEmpty(text) && !string.IsNullOrEmpty(search))
    {
        while ((pos = text.IndexOf(search, pos)) > -1)
        {
            num ++;
            pos += search.Length;
        }
    }
    return num;
}

5

Bunu yapmanın en kolay yolunun Normal İfadeleri kullanmak olduğunu düşünüyorum. Bu şekilde myVar.Split ('x') kullanarak aynı bölme sayısını elde edebilirsiniz, ancak birden çok karakter ayarında.

string myVar = "do this to count the number of words in my wording so that I can word it up!";
int count = Regex.Split(myVar, "word").Length;

3
string search = "/string";
var occurrences = (regex.Match(search, @"\/")).Count;

Bu, program her "/ s" yi tam olarak (büyük / küçük harfe duyarlı) bulduğunda sayılır ve bunun gerçekleşme sayısı "oluşumlar" değişkeninde saklanır


3

Güvensiz bayt bayt karşılaştırmaları gibi bazı alt dize sayımlarından yoksun olduğumuzu hissettim. Orijinal posterin yöntemini ve aklıma gelen yöntemleri bir araya getirdim.

Bunlar yaptığım dize uzantıları.

namespace Example
{
    using System;
    using System.Text;

    public static class StringExtensions
    {
        public static int CountSubstr(this string str, string substr)
        {
            return (str.Length - str.Replace(substr, "").Length) / substr.Length;
        }

        public static int CountSubstr(this string str, char substr)
        {
            return (str.Length - str.Replace(substr.ToString(), "").Length);
        }

        public static int CountSubstr2(this string str, string substr)
        {
            int substrlen = substr.Length;
            int lastIndex = str.IndexOf(substr, 0, StringComparison.Ordinal);
            int count = 0;
            while (lastIndex != -1)
            {
                ++count;
                lastIndex = str.IndexOf(substr, lastIndex + substrlen, StringComparison.Ordinal);
            }

            return count;
        }

        public static int CountSubstr2(this string str, char substr)
        {
            int lastIndex = str.IndexOf(substr, 0);
            int count = 0;
            while (lastIndex != -1)
            {
                ++count;
                lastIndex = str.IndexOf(substr, lastIndex + 1);
            }

            return count;
        }

        public static int CountChar(this string str, char substr)
        {
            int length = str.Length;
            int count = 0;
            for (int i = 0; i < length; ++i)
                if (str[i] == substr)
                    ++count;

            return count;
        }

        public static int CountChar2(this string str, char substr)
        {
            int count = 0;
            foreach (var c in str)
                if (c == substr)
                    ++count;

            return count;
        }

        public static unsafe int CountChar3(this string str, char substr)
        {
            int length = str.Length;
            int count = 0;
            fixed (char* chars = str)
            {
                for (int i = 0; i < length; ++i)
                    if (*(chars + i) == substr)
                        ++count;
            }

            return count;
        }

        public static unsafe int CountChar4(this string str, char substr)
        {
            int length = str.Length;
            int count = 0;
            fixed (char* chars = str)
            {
                for (int i = length - 1; i >= 0; --i)
                    if (*(chars + i) == substr)
                        ++count;
            }

            return count;
        }

        public static unsafe int CountSubstr3(this string str, string substr)
        {
            int length = str.Length;
            int substrlen = substr.Length;
            int count = 0;
            fixed (char* strc = str)
            {
                fixed (char* substrc = substr)
                {
                    int n = 0;

                    for (int i = 0; i < length; ++i)
                    {
                        if (*(strc + i) == *(substrc + n))
                        {
                            ++n;
                            if (n == substrlen)
                            {
                                ++count;
                                n = 0;
                            }
                        }
                        else
                            n = 0;
                    }
                }
            }

            return count;
        }

        public static int CountSubstr3(this string str, char substr)
        {
            return CountSubstr3(str, substr.ToString());
        }

        public static unsafe int CountSubstr4(this string str, string substr)
        {
            int length = str.Length;
            int substrLastIndex = substr.Length - 1;
            int count = 0;
            fixed (char* strc = str)
            {
                fixed (char* substrc = substr)
                {
                    int n = substrLastIndex;

                    for (int i = length - 1; i >= 0; --i)
                    {
                        if (*(strc + i) == *(substrc + n))
                        {
                            if (--n == -1)
                            {
                                ++count;
                                n = substrLastIndex;
                            }
                        }
                        else
                            n = substrLastIndex;
                    }
                }
            }

            return count;
        }

        public static int CountSubstr4(this string str, char substr)
        {
            return CountSubstr4(str, substr.ToString());
        }
    }
}

Ardından test kodu ...

static void Main()
{
    const char matchA = '_';
    const string matchB = "and";
    const string matchC = "muchlongerword";
    const string testStrA = "_and_d_e_banna_i_o___pfasd__and_d_e_banna_i_o___pfasd_";
    const string testStrB = "and sdf and ans andeians andano ip and and sdf and ans andeians andano ip and";
    const string testStrC =
        "muchlongerword amuchlongerworsdfmuchlongerwordsdf jmuchlongerworijv muchlongerword sdmuchlongerword dsmuchlongerword";
    const int testSize = 1000000;
    Console.WriteLine(testStrA.CountSubstr('_'));
    Console.WriteLine(testStrA.CountSubstr2('_'));
    Console.WriteLine(testStrA.CountSubstr3('_'));
    Console.WriteLine(testStrA.CountSubstr4('_'));
    Console.WriteLine(testStrA.CountChar('_'));
    Console.WriteLine(testStrA.CountChar2('_'));
    Console.WriteLine(testStrA.CountChar3('_'));
    Console.WriteLine(testStrA.CountChar4('_'));
    Console.WriteLine(testStrB.CountSubstr("and"));
    Console.WriteLine(testStrB.CountSubstr2("and"));
    Console.WriteLine(testStrB.CountSubstr3("and"));
    Console.WriteLine(testStrB.CountSubstr4("and"));
    Console.WriteLine(testStrC.CountSubstr("muchlongerword"));
    Console.WriteLine(testStrC.CountSubstr2("muchlongerword"));
    Console.WriteLine(testStrC.CountSubstr3("muchlongerword"));
    Console.WriteLine(testStrC.CountSubstr4("muchlongerword"));
    var timer = new Stopwatch();
    timer.Start();
    for (int i = 0; i < testSize; ++i)
        testStrA.CountSubstr(matchA);
    timer.Stop();
    Console.WriteLine("CS1 chr: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrB.CountSubstr(matchB);
    timer.Stop();
    Console.WriteLine("CS1 and: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrC.CountSubstr(matchC);
    timer.Stop();
    Console.WriteLine("CS1 mlw: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrA.CountSubstr2(matchA);
    timer.Stop();
    Console.WriteLine("CS2 chr: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrB.CountSubstr2(matchB);
    timer.Stop();
    Console.WriteLine("CS2 and: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrC.CountSubstr2(matchC);
    timer.Stop();
    Console.WriteLine("CS2 mlw: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrA.CountSubstr3(matchA);
    timer.Stop();
    Console.WriteLine("CS3 chr: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrB.CountSubstr3(matchB);
    timer.Stop();
    Console.WriteLine("CS3 and: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrC.CountSubstr3(matchC);
    timer.Stop();
    Console.WriteLine("CS3 mlw: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrA.CountSubstr4(matchA);
    timer.Stop();
    Console.WriteLine("CS4 chr: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrB.CountSubstr4(matchB);
    timer.Stop();
    Console.WriteLine("CS4 and: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrC.CountSubstr4(matchC);
    timer.Stop();
    Console.WriteLine("CS4 mlw: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrA.CountChar(matchA);
    timer.Stop();
    Console.WriteLine("CC1 chr: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrA.CountChar2(matchA);
    timer.Stop();
    Console.WriteLine("CC2 chr: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrA.CountChar3(matchA);
    timer.Stop();
    Console.WriteLine("CC3 chr: " + timer.Elapsed.TotalMilliseconds + "ms");

    timer.Restart();
    for (int i = 0; i < testSize; ++i)
        testStrA.CountChar4(matchA);
    timer.Stop();
    Console.WriteLine("CC4 chr: " + timer.Elapsed.TotalMilliseconds + "ms");
}

Sonuçlar: CSX, CountSubstrX'e ve CCX, CountCharX'e karşılık gelir. "chr", "_" için bir dizeyi arar ve "ve" için bir dizeyi arar ve "mlw", "çok uzun kelime" için bir dizeyi arar

CS1 chr: 824.123ms
CS1 and: 586.1893ms
CS1 mlw: 486.5414ms
CS2 chr: 127.8941ms
CS2 and: 806.3918ms
CS2 mlw: 497.318ms
CS3 chr: 201.8896ms
CS3 and: 124.0675ms
CS3 mlw: 212.8341ms
CS4 chr: 81.5183ms
CS4 and: 92.0615ms
CS4 mlw: 116.2197ms
CC1 chr: 66.4078ms
CC2 chr: 64.0161ms
CC3 chr: 65.9013ms
CC4 chr: 65.8206ms

Sonunda 3.6 milyon karakterli bir dosyam vardı. 100.000 kez tekrarlanan "derp adfderdserp dfaerpderp deasderp" idi. Ben bu yöntemleri 100 kez yukarıdaki yöntemleri ile dosya içinde "derp" aradı.

CS1Derp: 1501.3444ms
CS2Derp: 1585.797ms
CS3Derp: 376.0937ms
CS4Derp: 271.1663ms

Bu yüzden 4. yöntemim kesinlikle kazanıyor, ancak gerçekçi bir şekilde, 3.6 milyon karakterlik bir dosya 100 kez daha kötü durumda 1586 ms'yi aldıysa, tüm bunlar oldukça önemsizdir.

Bu arada, 3.6 milyon karakter dosyasında 'd' karakterini 100 kez CountSubstr ve CountChar yöntemleriyle taradım. Sonuçlar...

CS1  d : 2606.9513ms
CS2  d : 339.7942ms
CS3  d : 960.281ms
CS4  d : 233.3442ms
CC1  d : 302.4122ms
CC2  d : 280.7719ms
CC3  d : 299.1125ms
CC4  d : 292.9365ms

Orijinal poster yöntemi, buna göre büyük bir samanlıkta tek karakter iğneleri için çok kötü.

Not: Tüm değerler Sürüm sürümü çıktısına güncellendi. Bunu ilk kez yayınladığımda yanlışlıkla Sürüm modunu oluşturmayı unuttum. Bazı ifadelerim değiştirildi.


Performans sonuçları için teşekkürler. 10'luk bir faktör farkı, bir linq veya diğer düzgün yazılmış bir çözümü düşünmemek, ancak bir uzatma yöntemiyle gitmek için bir neden olabilir.
Andreas Reiff

2

Dizelerin oluşumları için genel bir işlev:

public int getNumberOfOccurencies(String inputString, String checkString)
{
    if (checkString.Length > inputString.Length || checkString.Equals("")) { return 0; }
    int lengthDifference = inputString.Length - checkString.Length;
    int occurencies = 0;
    for (int i = 0; i < lengthDifference; i++) {
        if (inputString.Substring(i, checkString.Length).Equals(checkString)) { occurencies++; i += checkString.Length - 1; } }
    return occurencies;
}

2
Bu, çok sayıda geçici dizge oluşturur ve çöp toplayıcının çok çalışmasını sağlar.
EricLaw

2
string source = "/once/upon/a/time/";
int count = 0, n = 0;
while ((n = source.IndexOf('/', n) + 1) != 0) count++;

Richard Watson'un cevabındaki bir varyasyon, verimliliği artırarak biraz daha hızlı, karakter dizede daha fazla ve daha az kod oluşur!

Söylemeliyim ki, her senaryoyu kapsamlı bir şekilde test etmeden, aşağıdakileri kullanarak çok önemli bir hız artışı gördüm:

int count = 0;
for (int n = 0; n < source.Length; n++) if (source[n] == '/') count++;

2
            var conditionalStatement = conditionSetting.Value;

            //order of replace matters, remove == before =, incase of ===
            conditionalStatement = conditionalStatement.Replace("==", "~").Replace("!=", "~").Replace('=', '~').Replace('!', '~').Replace('>', '~').Replace('<', '~').Replace(">=", "~").Replace("<=", "~");

            var listOfValidConditions = new List<string>() { "!=", "==", ">", "<", ">=", "<=" };

            if (conditionalStatement.Count(x => x == '~') != 1)
            {
                result.InvalidFieldList.Add(new KeyFieldData(batch.DECurrentField, "The IsDoubleKeyCondition does not contain a supported conditional statement. Contact System Administrator."));
                result.Status = ValidatorStatus.Fail;
                return result;
            }

Bir dizeden koşullu ifadeleri test etmek için benzer bir şey yapmak gerekiyordu.

Aradığım şeyi tek bir karakterle değiştirdi ve tek karakterin örneklerini saydı.

Açıkçası yanlış sayıları önlemek için kullandığınız tek karakterin dizede bulunup bulunmadığı kontrol edilmelidir.


2

Dize dizesi:

"JD" JD "JD JD vb. Vb. JDJDJDJDJDJDJDJD vb."

var strOrigin = " .. JD JD JD JD etc. and etc. JDJDJDJDJDJDJDJD and etc.";
var searchStr = "etc";
int count = (strOrigin.Length - strOrigin.Replace(searchStr, "").Length)/searchStr.Length.

Bunu sağlam / beceriksiz olarak atmadan önce performansı kontrol edin ...


2

İlk çekimim bana şöyle bir şey verdi:

public static int CountOccurrences(string original, string substring)
{
    if (string.IsNullOrEmpty(substring))
        return 0;
    if (substring.Length == 1)
        return CountOccurrences(original, substring[0]);
    if (string.IsNullOrEmpty(original) ||
        substring.Length > original.Length)
        return 0;
    int substringCount = 0;
    for (int charIndex = 0; charIndex < original.Length; charIndex++)
    {
        for (int subCharIndex = 0, secondaryCharIndex = charIndex; subCharIndex < substring.Length && secondaryCharIndex < original.Length; subCharIndex++, secondaryCharIndex++)
        {
            if (substring[subCharIndex] != original[secondaryCharIndex])
                goto continueOuter;
        }
        if (charIndex + substring.Length > original.Length)
            break;
        charIndex += substring.Length - 1;
        substringCount++;
    continueOuter:
        ;
    }
    return substringCount;
}

public static int CountOccurrences(string original, char @char)
{
    if (string.IsNullOrEmpty(original))
        return 0;
    int substringCount = 0;
    for (int charIndex = 0; charIndex < original.Length; charIndex++)
        if (@char == original[charIndex])
            substringCount++;
    return substringCount;
}

Değiştirme ve bölme kullanan samanlık yaklaşımındaki iğne 21+ saniye verirken, bu yaklaşık 15.2 alır.

Biraz ekledikten sonra düzenle substring.Length - 1CharIndex'e düzenleyin (olması gerektiği gibi), 11.6 saniyede.

Edit 2: 26 iki karakterli dizeleri olan bir dize kullandım, işte aynı örnek metinlere güncellenen saatler:

Samanlıkta iğne (OP'nin versiyonu): 7.8 Saniye

Önerilen mekanizma: 4.6 saniye.

Düzenleme 3: Tek karakterli büyük / küçük harf ekleyerek 1.2 saniyeye çıktı.

Edit 4: Bağlam için: 50 milyon iterasyon kullanıldı.


2

Uzatma yöntemimi ringe atacağımı düşündüm (daha fazla bilgi için yorumlara bakın). Herhangi bir resmi işaretleme yapmadım, ancak çoğu senaryo için çok hızlı olması gerektiğini düşünüyorum.

EDIT: Tamam - bu SO soru beni mevcut uygulama performansının burada sunulan bazı çözümlere karşı nasıl bir yığın merak merak etti. Küçük bir tezgah işareti yapmaya karar verdim ve çözümümüzün Richard Watson tarafından sağlanan çözümün performansına çok uygun olduğunu gördüm büyük dizeler (100 Kb +), büyük alt dizeler (32 Kb +) ile agresif arama yapana kadar ) ve birçok gömülü tekrar (10K +). Bu noktada çözümümüz yaklaşık 2X ila 4X daha yavaştı. Bu ve Richard Watson'ın sunduğu çözümü gerçekten sevdiğimiz göz önüne alındığında, çözümümüzü buna göre yeniden düzenledik. Ben sadece bu yararlanabilir herkes için kullanılabilir yapmak istedim.

Orijinal çözümümüz:

    /// <summary>
    /// Counts the number of occurrences of the specified substring within
    /// the current string.
    /// </summary>
    /// <param name="s">The current string.</param>
    /// <param name="substring">The substring we are searching for.</param>
    /// <param name="aggressiveSearch">Indicates whether or not the algorithm 
    /// should be aggressive in its search behavior (see Remarks). Default 
    /// behavior is non-aggressive.</param>
    /// <remarks>This algorithm has two search modes - aggressive and 
    /// non-aggressive. When in aggressive search mode (aggressiveSearch = 
    /// true), the algorithm will try to match at every possible starting 
    /// character index within the string. When false, all subsequent 
    /// character indexes within a substring match will not be evaluated. 
    /// For example, if the string was 'abbbc' and we were searching for 
    /// the substring 'bb', then aggressive search would find 2 matches 
    /// with starting indexes of 1 and 2. Non aggressive search would find 
    /// just 1 match with starting index at 1. After the match was made, 
    /// the non aggressive search would attempt to make it's next match 
    /// starting at index 3 instead of 2.</remarks>
    /// <returns>The count of occurrences of the substring within the string.</returns>
    public static int CountOccurrences(this string s, string substring, 
        bool aggressiveSearch = false)
    {
        // if s or substring is null or empty, substring cannot be found in s
        if (string.IsNullOrEmpty(s) || string.IsNullOrEmpty(substring))
            return 0;

        // if the length of substring is greater than the length of s,
        // substring cannot be found in s
        if (substring.Length > s.Length)
            return 0;

        var sChars = s.ToCharArray();
        var substringChars = substring.ToCharArray();
        var count = 0;
        var sCharsIndex = 0;

        // substring cannot start in s beyond following index
        var lastStartIndex = sChars.Length - substringChars.Length;

        while (sCharsIndex <= lastStartIndex)
        {
            if (sChars[sCharsIndex] == substringChars[0])
            {
                // potential match checking
                var match = true;
                var offset = 1;
                while (offset < substringChars.Length)
                {
                    if (sChars[sCharsIndex + offset] != substringChars[offset])
                    {
                        match = false;
                        break;
                    }
                    offset++;
                }
                if (match)
                {
                    count++;
                    // if aggressive, just advance to next char in s, otherwise, 
                    // skip past the match just found in s
                    sCharsIndex += aggressiveSearch ? 1 : substringChars.Length;
                }
                else
                {
                    // no match found, just move to next char in s
                    sCharsIndex++;
                }
            }
            else
            {
                // no match at current index, move along
                sCharsIndex++;
            }
        }

        return count;
    }

İşte revize edilmiş çözümümüz:

    /// <summary>
    /// Counts the number of occurrences of the specified substring within
    /// the current string.
    /// </summary>
    /// <param name="s">The current string.</param>
    /// <param name="substring">The substring we are searching for.</param>
    /// <param name="aggressiveSearch">Indicates whether or not the algorithm 
    /// should be aggressive in its search behavior (see Remarks). Default 
    /// behavior is non-aggressive.</param>
    /// <remarks>This algorithm has two search modes - aggressive and 
    /// non-aggressive. When in aggressive search mode (aggressiveSearch = 
    /// true), the algorithm will try to match at every possible starting 
    /// character index within the string. When false, all subsequent 
    /// character indexes within a substring match will not be evaluated. 
    /// For example, if the string was 'abbbc' and we were searching for 
    /// the substring 'bb', then aggressive search would find 2 matches 
    /// with starting indexes of 1 and 2. Non aggressive search would find 
    /// just 1 match with starting index at 1. After the match was made, 
    /// the non aggressive search would attempt to make it's next match 
    /// starting at index 3 instead of 2.</remarks>
    /// <returns>The count of occurrences of the substring within the string.</returns>
    public static int CountOccurrences(this string s, string substring, 
        bool aggressiveSearch = false)
    {
        // if s or substring is null or empty, substring cannot be found in s
        if (string.IsNullOrEmpty(s) || string.IsNullOrEmpty(substring))
            return 0;

        // if the length of substring is greater than the length of s,
        // substring cannot be found in s
        if (substring.Length > s.Length)
            return 0;

        int count = 0, n = 0;
        while ((n = s.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1)
        {
            if (aggressiveSearch)
                n++;
            else
                n += substring.Length;
            count++;
        }

        return count;
    }

1
string Name = "Very good nice one is very good but is very good nice one this is called the term";
bool valid=true;
int count = 0;
int k=0;
int m = 0;
while (valid)
{
    k = Name.Substring(m,Name.Length-m).IndexOf("good");
    if (k != -1)
    {
        count++;
        m = m + k + 4;
    }
    else
        valid = false;
}
Console.WriteLine(count + " Times accures");

1
string s = "HOWLYH THIS ACTUALLY WORKSH WOWH";
int count = 0;
for (int i = 0; i < s.Length; i++)
   if (s[i] == 'H') count++;

Sadece dizedeki her karakteri kontrol eder, eğer karakter aradığınız karakterse, saymak için bir karakter ekleyin.


1

Bu web sayfasına göz atarsanız , paralel döngülerin kullanılması da dahil olmak üzere, bunu yapmanın 15 farklı yolu karşılaştırılır.

En hızlı yol tek bir iş parçacıklı for döngüsü (.Net sürüm <4.0 sürümünüz varsa) veya bir paralel. For döngüsü (.Net> 4.0 kullanıyorsanız binlerce kontrol) kullanıyor gibi görünüyor.

"D" kelimesinin Arama Dizeniz olduğunu varsayarsanız, "ch" karakter dizinizdir (aradığınız birden fazla karaktere sahipseniz), işte en hızlı çalışma süresi tek iş parçacığına sahip olan kodun temel özeti:

for (int x = 0; x < ss.Length; x++)
{
    for (int y = 0; y < ch.Length; y++)
    {
        for (int a = 0; a < ss[x].Length; a++ )
        {
        if (ss[x][a] == ch[y])
            //it's found. DO what you need to here.
        }
    }
}

Karşılaştırma kaynak kodu da verilir, böylece kendi testlerinizi yapabilirsiniz.


1
str="aaabbbbjjja";
int count = 0;
int size = str.Length;

string[] strarray = new string[size];
for (int i = 0; i < str.Length; i++)
{
    strarray[i] = str.Substring(i, 1);
}
Array.Sort(strarray);
str = "";
for (int i = 0; i < strarray.Length - 1; i++)
{

    if (strarray[i] == strarray[i + 1])
    {

        count++;
    }
    else
    {
        count++;
        str = str + strarray[i] + count;
        count = 0;
    }

}
count++;
str = str + strarray[strarray.Length - 1] + count;

Bu, karakter oluşumunu saymak içindir. Bu örnek için çıktı "a4b4j3" olacaktır


2
Tam olarak 'bir dizenin oluşumlarını saymak' daha fazla sayma karakteri değil - Narenda'nın eşleşmesi için dizenin ne olduğunu belirtmenin bir yolu ne olacak?
Paul Sullivan

1
int sayısı = 0; string str = "foo ve foo var, lütfen bu konuda foo sayın"; string stroccurance = "foo"; string [] strarray = str.Split (''); Array.Sort (strArray); str = ""; for (int i = 0; i <strarray.Length - 1; i ++) {if (strarray [i] == stroccurance) {count ++; }} str = "" + stroccurance + "için meydana gelme sayısı" + sayım; Bu sayede bu örnekte herhangi bir dize oluşumunu sayabilirim "foo" nun oluşumunu sayıyorum ve bana çıktı 3'ü verecek.
Narendra Kumar

1

Bir dize sınırlayıcı durumunda (öznenin dediği gibi karakter durumu için değil):
string source = "@@@ bir kez @@@ upon @@@ a @@@ time @@@";
int count = kaynak.Split (yeni [] {"@@@"}, StringSplitOptions.RemoveEmptyEntries). Uzunluk - 1;

Posterin orijinal kaynak değerinin ("/ once / upon / a / time /") doğal sınırlayıcısı bir char '/' ve yanıtlar source.plit (char []) seçeneğini açıklıyor ...


0

System.Linq kullanarak;

int CountOf => "A :: BC :: D" .Split ("::"). Uzunluk - 1;

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.