Jak policzyłbyś wystąpienia ciągu (właściwie char) w ciągu?


864

Robię coś, w którym zdałem sobie sprawę, że chcę policzyć, ile /s mogę znaleźć w ciągu, a potem uderzyło mnie, że istnieje kilka sposobów, ale nie mogłem zdecydować, co jest najlepsze (lub najłatwiejsze) .

W tej chwili idę z czymś takim jak:

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

Ale w ogóle mi się to nie podoba, jakieś chętne?

Naprawdę nie chcę RegExtego robić, prawda?

Wiem, że mój ciąg będzie zawierał szukany termin, więc możesz założyć, że ...

Oczywiście dla ciągów, których długość> 1 ,

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

34
+1: muszę powiedzieć, że liczy się zupełnie inny sposób. jestem zaskoczony wynikami testu
porównawczego

4
To nie jest tak różne ... to typowy sposób do realizacji tej funkcji w SQL: LEN(ColumnToCheck) - LEN(REPLACE(ColumnToCheck,"N","")).
Sheridan

6
W rzeczywistości powinieneś podzielić przez „/".Length
Gerard

3
Czy mogę zapytać, jakie według waszych wymagań powinna być liczba wystąpień „//” w obrębie „/////”? 2 czy 4?
Les

1
użycie wyrażenia regularnego jest prawdopodobnie najlepszym sposobem, aby się tym
Adam Higgins

Odpowiedzi:


1009

Jeśli korzystasz z .NET 3.5, możesz to zrobić w jednym linku z LINQ:

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

Jeśli nie chcesz korzystać z LINQ, możesz to zrobić za pomocą:

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

Możesz być zaskoczony, gdy dowiesz się, że Twoja oryginalna technika wydaje się być o około 30% szybsza niż którakolwiek z nich! Właśnie wykonałem szybki test porównawczy z „/ once / upon / a / time /”, a wyniki są następujące:

Twój oryginał =
źródło 12s.Count =
źródło 19s.Split = fores 17s
( z odpowiedzi Bobwienholta ) = 10s

(Czasy dotyczą 50 000 000 iteracji, więc nie zauważysz dużej różnicy w prawdziwym świecie).


6
Tak, VS ukrywa metody rozszerzenia LINQ w klasie string. Sądzę, że doszli do wniosku, że deweloperzy nie chcieliby, aby wszystkie te metody rozszerzenia pojawiały się w klasie strun. Prawdopodobnie mądra decyzja.
Judah Gabriel Himango

11
Możliwe, że to zachowanie jest spowodowane tym, że VS2010 automatycznie dołącza System.Linq do plików nowej klasy, VS2008 prawdopodobnie nie. Przestrzeń nazw musi znajdować się, aby inteligencja działała.
Sprague,

30
Pamiętaj, że rozwiązania zliczania i podziału działają tylko podczas liczenia znaków. Nie będą działać z łańcuchami, tak jak rozwiązanie OP.
Peter Lillevold,

5
f == '\' dotyczy znaków w łańcuchu, a nie łańcuchów w łańcuchu
Thomas Weller

9
To wydaje się być odpowiedzią na inne pytanie: „Jak policzyłbyś wystąpienia znaku w ciągu znaków?”
Ben Aaronson

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

Musi być szybszy niż source.Replace()sam.


18
Możesz zyskać marginalną poprawę, przechodząc na for zamiast zamiast foreach, ale tylko mały, mały kawałek.
Mark

17
Nie. Pytanie wymaga policzenia wystąpienia ciągu, a nie znaku.
YukiSakura,

3
Zlicza znaki w ciągu. Tytuł dotyczy zliczania strun w ciągu
Thomas Weller,

2
@Mark Właśnie przetestowałem go za pomocą pętli for i było tak naprawdę wolniejsze niż używanie foreach. Może to być spowodowane sprawdzaniem granic? (Czas wynosił 1,65 sek. W porównaniu do 2,05 przy 5 mil. Iteracjach).
Pomiar

4
Podczas gdy w pytaniu jest o ciąg w ciągu, przykładowy opublikowany problem OP to w rzeczywistości tylko jeden znak, w którym to przypadku nazwałbym tę odpowiedź wciąż poprawnym rozwiązaniem, ponieważ pokazuje lepszy sposób (wyszukiwanie znaków zamiast wyszukiwania ciągu) rozwiązać bieżący problem.
Czad

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

8
+1 - W niektórych przypadkach możesz chcieć dodać RegexOptions.IgnoreCase.
TrueWill

3
czy to nie jest niewiarygodnie niskie?
Thomas Ayoub,

4
Koszty ogólne Regex nie są idealne, a ponadto: „Naprawdę nie chcę wykopać RegEx w tym celu, prawda?”
Czad

nie chcieć Regex.Escape(...)taknew System.Text.RegularExpressions.Regex(needle).Matches(haystack).Count;
barlop

2
Poszedłem z tym, ponieważ może wyszukiwać ciągi, a nie tylko znaki.
James w Indy

86

Jeśli chcesz wyszukiwać całe ciągi, a nie tylko znaki:

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

Odczytaj jako „dla każdego znaku w ciągu, weź resztę łańcucha zaczynając od tego znaku jako podłańcuch; policz go, jeśli zaczyna się od ciągu docelowego”.


1
Nie jestem pewien, jak mogę to wyjaśnić w jaśniejszy sposób niż podany opis. Co jest mylące?
mqp,

58
SUPER SPOWOLNIENIE! Wypróbowałem to na stronie HTML i zajęło to około 2 minut w porównaniu z innymi metodami na tej stronie, które zajęły 2 sekundy. Odpowiedź była poprawna; było po prostu zbyt wolne, aby można je było wykorzystać.
JohnB

2
zgodził się, zbyt wolno. Jestem wielkim fanem rozwiązań w stylu linq, ale to po prostu nie jest wykonalne.
Sprague,

5
Zauważ, że powodem tego jest tak powolny, że tworzy on n łańcuchów, przydzielając w ten sposób z grubsza n ^ 2/2 bajtów.
Peter Crabtree

6
OutOfMemoryException jest generowany dla moich 210000 znaków ciągu.
zakończenie

66

Przeprowadziłem badania i odkryłem, że rozwiązanie Richarda Watsona jest najszybsze w większości przypadków. To jest tabela z wynikami każdego rozwiązania w poście (z wyjątkiem tych, które używają Regex, ponieważ zgłasza wyjątki podczas analizowania łańcucha, np. „Test {test”)

    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|

Możesz zauważyć, że w przypadku znalezienia liczby wystąpień krótkich podciągów (1-5 znaków) w krótkim ciągu (10-50 znaków) preferowany jest oryginalny algorytm.

Ponadto w przypadku podciągów wieloznakowych należy użyć następującego kodu (opartego na rozwiązaniu Richarda Watsona )

int count = 0, n = 0;

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

Właśnie miałem dodać własne rozwiązanie „niskiego poziomu” (bez tworzenia podciągów, używając replace / split lub dowolnego Regex / Linq), ale twoje jest prawdopodobnie nawet lepsze niż moje (i przynajmniej krótsze). Dzięki!
Dan W

W przypadku rozwiązań Regex dodajRegex.Escape(needle)
Thymine

2
Aby wskazać innym, wartość wyszukiwania musi być sprawdzona, jeśli jest pusta, w przeciwnym razie przejdziesz do nieskończonej pętli.
WhoIsRich

2
Może to tylko ja, ale source="aaa" substring="aa"spodziewałem się, że odzyskam 2, a nie 1. Aby to naprawić, zmień n += substring.Lengthnan++
ytoledano

możesz dodać overlappedflagę do swojej sprawy w ten sposób:overlapped=True;.... if(overlapped) {++n;} else {n += substring.Length;}
tsionyx

54

LINQ działa na wszystkich kolekcjach, a ponieważ ciągi są tylko zbiorem znaków, co powiesz na ten ładny, mały linijka:

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

Upewnij się, że masz using System.Linq;na górze pliku kodu, ponieważ .Countjest to metoda rozszerzenia z tej przestrzeni nazw.


5
Czy naprawdę warto tam używać var? Czy jest jakaś szansa, że ​​Count zostanie zastąpiony przez coś, co nie zwróci liczby całkowitej?
Whatsit,

69
@Whatsit: możesz wpisać „var” tylko lewą ręką, podczas gdy „int” wymaga obu rąk;)
Sean Bright

7
intwszystkie litery znajdują się w klawiszach domowych, podczas gdy varnie. uh .. czekaj, używam Dvorak
Michael Buen

2
@BDotA Upewnij się, że masz plik „using System.Linq;” u góry pliku. Również intellisense może ukryć przed tobą wywołanie .Count, ponieważ jest to ciąg znaków. Mimo to skompiluje się i będzie działać poprawnie.
Judah Gabriel Himango

3
@JudahGabrielHimango Argumentowałbym, że var należy stosować zwłaszcza, gdy typ zmiennej jest oczywisty (i dla zwięzłości i spójności)
EriF89

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

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

Na moim komputerze jest to około 2 sekundy szybsze niż rozwiązanie dla każdej postaci na 50 milionów iteracji.

Wersja 2013:

Zmień ciąg na char [] i iteruj przez to. Skraca o kolejny sekundę lub dwie całkowity czas iteracji 50m!

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

Jest to jeszcze szybsze:

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

Dla pewności, iteracja od końca tablicy do 0 wydaje się najszybsza, o około 5%.

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

Zastanawiałem się, dlaczego to mogło być i było Googling w okolicy (przypominam sobie coś o szybszym powtarzaniu iteracji) i natknąłem się na to pytanie SO, które irytująco używa techniki strun do char [] już. Myślę jednak, że sztuczka polegająca na odwróceniu jest nowa w tym kontekście.

Jaki jest najszybszy sposób na iterację pojedynczych znaków w ciągu w C #?


1
Możesz wstawić source.IndexOf('/', n + 1)i zgubić n++nawiasy klamrowe chwile :) Również string word = "/"zamiast znaku wstaw zmienną .
neeKo

1
Hej, Niko, sprawdź nowe odpowiedzi. Może być jednak trudniej utworzyć podciąg o zmiennej długości.
Richard Watson,

Użyłem czegoś podobnego, przechodząc przez subtring; to dopóki nie zdałem sobie sprawy, że indexOf ma startIndex. Najbardziej podoba mi się pierwsze rozwiązanie, ponieważ zapewnia dobrą równowagę między szybkością i powierzchnią pamięci.
Samir Banjanovic

1
Czytałem gdzieś, że szybciej jest iterować wstecz, ponieważ szybciej jest porównać wartość z 0
reggaeguitar

1
@shitpoet yup. Jeśli spojrzysz na kod źródłowy, jest to połączenie rodzime. public char [] toCharArray () {... System.arraycopy (wartość, 0, wynik, 0, wartość. długość); ...}
Richard Watson,

46

Oba działają tylko w przypadku wyszukiwanych znaków składających się z jednego znaku ...

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

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

może okazać się lepszy w przypadku dłuższych igieł ...

Ale musi być bardziej elegancki sposób. :)


Aby uwzględnić zamienniki wieloznakowe. Bez tego liczenie „w” w teście jest kluczem ”zwraca 6.
ZombieSheep

Benchmarked i porównał to ze sznurkiem. Dzielony - działa około 1,5 razy szybciej. Sława.
Alex

20

Edytować:

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

2
To jest to, co robie. I source.Split(new[]{"//"}, StringSplitOptions.None).Count - 1dla separatorów wieloznakowych.
bzlm,

4
Pozwoliłoby to wykonać co najmniej n przydziałów ciągów na stercie, a także (ewentualnie) kilka zmian rozmiarów tablicy - i to wszystko po to, aby policzyć? Niezwykle nieefektywny, nie skaluje się dobrze i nigdy nie powinien być używany w żadnym ważnym kodzie.
Zar Shardan,

17

W języku C # niezłym licznikiem SubString jest ten niespodziewanie trudny facet:

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

1
Ładne rozwiązanie - i praca na łańcuchach (nie tylko char)!
ChriPf 19.04.16

Dzięki, zbyt łatwo jest zapomnieć o niektórych subtelnościach obsługi ciągów podczas wymiany języków - jak większość z nas ma teraz!
Dave

1
-1 ponieważ: Czy znasz różnicę między Count () a Count lub Length? Jeśli ktoś używa Count () zamiast Count lub Length, zostaniesz wyzwolony. Funkcja Count () tworzy IEnumerator, a następnie przechodzi przez wszystkie wystąpienia IEnumerable, podczas gdy Count lub Length są już ustawionymi właściwościami obiektu, które już zawierają żądaną liczbę bez potrzeby iteracji po wszystkich elementach.
aeroson

Dobre miejsce, a dziwne, że w mojej bibliotece, z której wziąłem funkcję, używam „Length”. Edytowane!
Dave

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

1
Nie jest to poprawne, jeśli dane wejściowe zawierają regex specjalne znaki tj. | Musi istnieć Regex.Escape (wejście)
Esben Skov Pedersen

1
W rzeczywistości stringToMatchpotrzeby uciekają, a nie input.
Theodor Zoulias

Masz rację. Naprawione.
cederlof

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

Ponieważ oryginalne rozwiązanie było najszybsze dla znaków, przypuszczam, że będzie również dla ciągów znaków. Oto mój wkład.

W kontekście: szukałem w pliku dziennika takich słów, jak „nie powiodło się” i „udało się”.

Gr, Ben


2
Po prostu nie przekazuj pustego ciągu dla zmiennej „słowo” (błąd dzielenia przez zero).
Andrew Jens,

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

20
lub Regex.Matches (s, „65”). Liczba ^ _ ^
Meta

Działa nie dla każdego ciągu. Spróbuj wyszukać „++” w „abc ++ def ++ xyz”
marsh-wiggle

7

Dla każdego, kto chce mieć gotową do użycia metodę rozszerzenia String,

Oto, czego używam, który został oparty na najlepszych z opublikowanych odpowiedzi:

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

Czy druga metoda nie rozkwitnie, jeśli przekazany ciąg znaków będzie zerowy lub pusty? Z czysto stylowego punktu widzenia, co definiujesz jako System.String, a nie tylko string?
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

Myślę, że najłatwiejszym sposobem na to jest użycie wyrażeń regularnych. W ten sposób możesz uzyskać taką samą liczbę podziałów, jak przy użyciu myVar.Split („x”), ale w ustawieniu wielu znaków.

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;

To będzie się liczyć za każdym razem, gdy program znajdzie „/ s” dokładnie (rozróżnia małe i wielkie litery), a liczba wystąpień tego zdarzenia zostanie zapisana w zmiennej „wystąpienia”


3

Czułem, że brakuje nam pewnego rodzaju liczenia napisów, takich jak niebezpieczne porównania bajt po bajcie. Złożyłem oryginalną metodę plakatu i wszelkie metody, jakie mogłem wymyślić.

To są rozszerzenia, które utworzyłem.

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

Następnie kod testowy ...

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

Wyniki: CSX odpowiada CountSubstrX, a CCX odpowiada CountCharX. „chr” wyszukuje ciąg „_”, „i” wyszukuje ciąg „i”, a „mlw” wyszukuje ciąg „longlongerword”

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

I w końcu miałem plik zawierający 3,6 miliona znaków. Było to „derp adfderdserp dfaerpderp deasderp” powtórzone 100 000 razy. 100 razy te wyniki szukałem „derp” w pliku za pomocą powyższych metod.

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

Tak więc moja czwarta metoda jest zdecydowanie zwycięzcą, ale, realistycznie, jeśli plik 3,6 miliona znaków 100 razy zajęłby 1586 ms jako najgorszy przypadek, to wszystko to jest bardzo znikome.

Nawiasem mówiąc, przeskanowałem również znak „d” w pliku 3,6 miliona znaków przy użyciu 100 razy metod CountSubstr i CountChar. Wyniki ...

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

Zgodnie z tym oryginalna metoda plakatów jest bardzo zła dla igieł jednoznakowych w dużym stogu siana.

Uwaga: Wszystkie wartości zostały zaktualizowane do danych wyjściowych wersji Release. Przypadkowo zapomniałem zbudować tryb Release przy pierwszym opublikowaniu tego. Niektóre z moich oświadczeń zostały zmienione.


Dziękujemy za wyniki wydajności. Różnica współczynników prędkości wynosząca 10 może być powodem, aby nie brać pod uwagę linq lub innego starannie napisanego rozwiązania, ale zastosować metodę rozszerzenia.
Andreas Reiff,

2

Ogólna funkcja dotycząca występowania ciągów:

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
Tworzy to OGROMNĄ liczbę tymczasowych ciągów i sprawia, że ​​moduł śmieciowy pracuje bardzo ciężko.
EricLaw

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

Odmiana odpowiedzi Richarda Watsona, nieco szybsza wraz ze wzrostem wydajności, im więcej razy znak występuje w ciągu, i mniej kodu!

Chociaż muszę powiedzieć, że bez szczegółowego testowania każdego scenariusza, zauważyłem bardzo znaczącą poprawę prędkości dzięki zastosowaniu:

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

Musiałem zrobić coś podobnego do testowania instrukcji warunkowych z łańcucha.

Zamieniłem to, czego szukałem, na jeden znak i policzyłem wystąpienia tego znaku.

Oczywiście pojedynczy znak, którego używasz, musi zostać zaznaczony, aby nie istniał w ciągu, aby uniknąć niepoprawnego liczenia.


2

Ciąg w ciągu:

Znajdź „etc” w „.. JD JD JD JD itp. I itp. JDJDJDJDJDJDJDJD i itp.”

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.

Sprawdź wydajność, zanim odrzucisz tę jako nieudolną / niezdarną ...


2

Moje pierwsze ujęcie dało mi coś takiego:

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

Igła w podejściu do stogu siana przy użyciu zamiany i podziału daje ponad 21 sekund, podczas gdy zajmuje to około 15,2.

Edytuj po dodaniu bitu, który by dodał substring.Length - 1 dodałby do charIndex (tak jak powinien), ma 11,6 sekundy.

Edycja 2: Użyłem ciągu, który miał 26 ciągów dwóch znaków, oto czasy zaktualizowane do tych samych przykładowych tekstów:

Igła w stogu siana (wersja OP): 7,8 sekundy

Sugerowany mechanizm: 4,6 sekundy.

Edycja 3: Dodanie pojedynczego znaku narożnego przypadku zajęło 1,2 sekundy.

Edycja 4: Dla kontekstu: użyto 50 milionów iteracji.


2

Pomyślałem, że wrzucę moją metodę rozszerzenia do ringu (zobacz komentarze, aby uzyskać więcej informacji). Nie przeprowadziłem formalnego testu porównawczego, ale myślę, że musi być bardzo szybki w większości scenariuszy.

EDYCJA: OK - więc to pytanie SO skłoniło mnie do zastanowienia się, jak wypadałoby nasze obecne wdrożenie w porównaniu z niektórymi rozwiązaniami przedstawionymi tutaj. Postanowiłem zrobić małe wyciskanie na ławce i stwierdziłem, że nasze rozwiązanie było bardzo zgodne z wydajnością rozwiązania dostarczonego przez Richarda Watsona aż do agresywnego wyszukiwania z dużymi ciągami (100 Kb +), dużymi podciągami (32 Kb + ) i wiele osadzonych powtórzeń (10 KB +). W tym momencie nasze rozwiązanie było około 2 do 4 razy wolniejsze. Biorąc pod uwagę to i fakt, że naprawdę podoba nam się rozwiązanie przedstawione przez Richarda Watsona, odpowiednio zmodyfikowaliśmy nasze rozwiązanie. Chciałem tylko udostępnić to każdemu, kto mógłby z tego skorzystać.

Nasze oryginalne rozwiązanie:

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

Oto nasze zmienione rozwiązanie:

    /// <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++;

Sprawdza tylko każdy znak w ciągu, jeśli znak jest poszukiwanym znakiem, dodaj jeden, aby policzyć.


1

Jeśli przejrzysz tę stronę , zostanie przetestowanych 15 różnych sposobów, w tym przy użyciu równoległych pętli.

Najszybszym sposobem wydaje się być użycie jednowątkowej pętli for (jeśli masz .Net w wersji <4.0) lub równoległej pętli for (jeśli używasz .Net> 4.0 z tysiącami kontroli).

Zakładając, że „ss” jest ciągiem wyszukiwania, „ch” to tablica znaków (jeśli masz więcej niż jeden znak, którego szukasz), oto podstawowa treść kodu, który miał najszybszy jednowątkowy czas działania:

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

Dostarczono również kod źródłowy testu porównawczego, abyś mógł uruchomić własne testy.


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;

Służy do liczenia występowania postaci. W tym przykładzie wynikiem będzie „a4b4j3”


2
Niezupełnie „liczenie wystąpień ciągu” więcej liczących znaków - co powiesz na sposób określania, który ciąg pasować to Narenda?
Paul Sullivan,

1
liczba int = 0; string str = "mamy foo i foo proszę policzyć foo w tym"; 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 = „Liczba wystąpień dla„ + stroccurance + ”to„ + count; Dzięki temu można policzyć każde wystąpienie łańcucha w tym przykładzie. Liczę wystąpienie „foo” i da mi to wynik 3.
Narendra Kumar

1

W przypadku ogranicznika łańcucha (nie dla znaku, jak mówi podmiot):
łańcuch źródła = "@@@ raz @@@ na @@@ @ @@ czas @@@";
int count = source.Split (new [] {"@@@"}, StringSplitOptions.RemoveEmptyEntries) .Length - 1;

Naturalnym ogranicznikiem oryginalnej wartości źródłowej plakatu („/ once / upon / a / time /”) jest char '/ ”, a odpowiedzi wyjaśniają opcję source.Split (char []) ...


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.