Wie würden Sie das Auftreten einer Zeichenfolge (tatsächlich eines Zeichens) innerhalb einer Zeichenfolge zählen?


864

Ich mache etwas, bei dem mir klar wurde, dass ich zählen wollte, wie viele /s ich in einer Zeichenfolge finden konnte, und dann fiel mir auf, dass es mehrere Möglichkeiten gab, aber ich konnte mich nicht entscheiden, was das Beste (oder Einfachste) war .

Im Moment gehe ich mit so etwas wie:

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

Aber ich mag es überhaupt nicht, irgendwelche Abnehmer?

Ich möchte nicht wirklich danach graben RegEx, oder?

Ich weiß, dass meine Zeichenfolge den Begriff haben wird, nach dem ich suche, also können Sie davon ausgehen, dass ...

Natürlich für Saiten mit einer Länge> 1 ,

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

34
+1: Ich muss sagen, dass es eine ganz andere Art ist, zu zählen. Ich bin überrascht über die Benchmark-Testergebnisse :)
Naveen

4
Es ist nicht so anders ... es ist die typische Art, diese Funktionalität in SQL zu implementieren : LEN(ColumnToCheck) - LEN(REPLACE(ColumnToCheck,"N","")).
Sheridan

6
In der Tat sollten Sie durch "/".Length
Gerard

3
Darf ich fragen, wie hoch sollten Ihre Anforderungen für die Anzahl der Vorkommen von "//" innerhalb von "/////" sein? 2 oder 4?
Les

1
Die Verwendung von Regex ist wahrscheinlich der beste Weg, um dies zu erreichen
Adam Higgins

Antworten:


1009

Wenn Sie .NET 3.5 verwenden, können Sie dies in einem Einzeiler mit LINQ tun:

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

Wenn Sie LINQ nicht verwenden möchten, können Sie dies tun mit:

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

Sie werden überrascht sein zu erfahren, dass Ihre ursprüngliche Technik etwa 30% schneller zu sein scheint als beide! Ich habe gerade einen schnellen Benchmark mit "/ einmal / nach / a / Zeit /" durchgeführt und die Ergebnisse sind wie folgt:

Ihre ursprüngliche = 12s
Quelle.Count = 19s
Quelle.Split = 17s
foreach ( aus Bobwienholts Antwort ) = 10s

(Die Zeiten sind für 50.000.000 Iterationen, sodass Sie in der realen Welt wahrscheinlich keinen großen Unterschied bemerken werden.)


6
Ja, VS versteckt LINQ-Erweiterungsmethoden für die Zeichenfolgenklasse. Ich denke, sie dachten, Entwickler würden nicht wollen, dass all diese Erweiterungsmethoden in der String-Klasse angezeigt werden. Wahrscheinlich eine kluge Entscheidung.
Judah Gabriel Himango

11
Möglicherweise liegt dieses Verhalten daran, dass VS2010 System.Linq automatisch in neue Klassendateien einschließt, VS2008 wahrscheinlich nicht. Der Namespace muss vorhanden sein, damit der Intellisense funktioniert.
Sprague

30
Beachten Sie, dass die Lösungen Count und Split nur funktionieren, wenn Sie Zeichen zählen. Sie funktionieren nicht mit Zeichenfolgen, wie dies bei der OP-Lösung der Fall ist.
Peter Lillevold

5
f == '\' handelt von Zeichen in einer Zeichenfolge, nicht von Zeichenfolgen in einer Zeichenfolge
Thomas Weller

9
Dies scheint die Antwort auf eine andere Frage zu sein: "Wie würden Sie das Auftreten eines Zeichens innerhalb einer Zeichenfolge zählen?"
Ben Aaronson

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

Muss schneller sein als das source.Replace()alleine.


18
Sie könnten eine geringfügige Verbesserung erzielen, indem Sie zu einem for anstelle eines foreach wechseln, aber nur zu einem winzigen, winzigen Teil.
Mark

17
Nein. Bei der Frage wird das Auftreten von Zeichenfolgen und nicht von Zeichen gezählt.
YukiSakura

3
Dies zählt Zeichen in einer Zeichenfolge. Der Titel handelt vom Zählen von Saiten in einer Saite
Thomas Weller

2
@Mark Habe es gerade mit einer for-Schleife getestet und es war tatsächlich langsamer als mit foreach. Könnte an der Überprüfung der Grenzen liegen? (Zeit war 1,65 Sek. Gegen 2,05 bei 5-Mil-Iterationen.)
Messung

4
Während die Frage nach einer Zeichenfolge innerhalb einer Zeichenfolge fragt, besteht das veröffentlichte Problem OP nur aus einem Zeichen. In diesem Fall würde ich diese Antwort immer noch als gültige Lösung bezeichnen, da sie einen besseren Weg zeigt (Zeichensuche statt Zeichenfolgensuche). um das vorliegende Problem anzugehen.
Tschad

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

8
+1 - In einigen Fällen möchten Sie möglicherweise hinzufügen RegexOptions.IgnoreCase.
TrueWill

3
ist das nicht unglaublich niedrig?
Thomas Ayoub

4
Regex-Overhead ist nicht ideal. "Ich möchte RegEx dafür nicht wirklich ausgraben, oder?"
Tschad

könnte nicht Regex.Escape(...)so wollennew System.Text.RegularExpressions.Regex(needle).Matches(haystack).Count;
Barlop

2
Ich habe mich für dieses entschieden, weil es nach Zeichenfolgen suchen kann, nicht nur nach Zeichen.
James in Indy

86

Wenn Sie nach ganzen Zeichenfolgen und nicht nur nach Zeichen suchen möchten:

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

Lesen Sie als "Nehmen Sie für jedes Zeichen in der Zeichenfolge den Rest der Zeichenfolge, beginnend mit diesem Zeichen, als Teilzeichenfolge; zählen Sie ihn, wenn er mit der Zielzeichenfolge beginnt."


1
Ich bin mir nicht sicher, wie ich es klarer erklären kann als die Beschreibung. Was ist verwirrend?
mqp

58
SUPER LANGSAM! Versuchte es auf einer HTML-Seite und es dauerte ungefähr 2 Minuten im Vergleich zu anderen Methoden auf dieser Seite, die 2 Sekunden dauerten. Die Antwort war richtig; es war einfach zu langsam, um benutzt werden zu können.
JohnB

2
stimmte zu, zu langsam. Ich bin ein großer Fan von Lösungen im Linq-Stil, aber diese ist einfach nicht realisierbar.
Sprague

5
Beachten Sie, dass der Grund dafür, dass dies so langsam ist, darin besteht, dass n Zeichenfolgen erstellt werden, wodurch ungefähr n ^ 2/2 Bytes zugewiesen werden.
Peter Crabtree

6
OutOfMemoryException wird für meine 210000 Zeichenfolgen ausgelöst.
Ender

66

Ich habe einige Nachforschungen angestellt und festgestellt, dass die Lösung von Richard Watson in den meisten Fällen am schnellsten ist. Das ist die Tabelle mit den Ergebnissen aller Lösungen im Beitrag (mit Ausnahme derjenigen, die Regex verwenden, da beim Parsen von Zeichenfolgen wie "test {test" Ausnahmen ausgelöst werden).

    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|

Sie können sehen, dass beim Ermitteln der Anzahl der Vorkommen von kurzen Teilzeichenfolgen (1-5 Zeichen) in kurzen Zeichenfolgen (10-50 Zeichen) der ursprüngliche Algorithmus bevorzugt wird.

Außerdem sollten Sie für Teilzeichenfolgen mit mehreren Zeichen den folgenden Code verwenden (basierend auf der Lösung von Richard Watson ).

int count = 0, n = 0;

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

Ich wollte gerade meine eigene "Low-Level" -Lösung hinzufügen (ohne Teilzeichenfolgen zu erstellen, Ersetzen / Teilen oder Regex / Linq zu verwenden), aber Ihre ist möglicherweise sogar besser als meine (und zumindest kürzer). Vielen Dank!
Dan W

Für die Regex Lösungen, fügen Sie in einemRegex.Escape(needle)
Thymin

2
Um auf andere hinzuweisen, muss der Suchwert überprüft werden, wenn er leer ist. Andernfalls geraten Sie in eine Endlosschleife.
WhoIsRich

2
Vielleicht source="aaa" substring="aa"bin ich es nur, aber ich habe erwartet, dass ich 2 zurück bekomme, nicht 1. Um dies zu "beheben", wechseln Sie n += substring.Lengthzun++
ytoledano

Sie können die overlappedFlagge hinzufügen , um Ihren Fall wie overlapped=True;.... if(overlapped) {++n;} else {n += substring.Length;}
folgt

54

LINQ funktioniert in allen Sammlungen, und da Zeichenfolgen nur eine Sammlung von Zeichen sind, wie wäre es mit diesem netten kleinen Einzeiler:

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

Stellen Sie sicher, dass Sie using System.Linq;oben in Ihrer Codedatei .Counteine Erweiterungsmethode aus diesem Namespace haben.


5
Lohnt es sich wirklich, dort var zu verwenden? Gibt es eine Chance, dass Count durch etwas ersetzt wird, das kein int zurückgibt?
Whatsit

69
@Whatsit: Sie können 'var' nur mit Ihrer linken Hand eingeben, während 'int' beide Hände benötigt;)
Sean Bright

7
intBuchstaben befinden sich alle in Home-Schlüsseln, varnicht jedoch. Warten Sie, ich benutze Dvorak
Michael Buen

2
@BDotA Stellen Sie sicher, dass Sie ein 'using System.Linq;' oben in Ihrer Datei. Außerdem kann Intellisense den .Count-Aufruf vor Ihnen verbergen, da es sich um eine Zeichenfolge handelt. Trotzdem wird es kompiliert und läuft einwandfrei.
Judah Gabriel Himango

3
@ JudahGabrielHimango Ich würde argumentieren, dass var verwendet werden sollte, insbesondere wenn der Variablentyp offensichtlich ist (und aus
Gründen der

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

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

Auf meinem Computer ist es für 50 Millionen Iterationen etwa 2 Sekunden schneller als die Lösung für jedes Zeichen.

Revision 2013:

Ändern Sie die Zeichenfolge in ein Zeichen [] und durchlaufen Sie diese. Reduziert die Gesamtzeit für 50-Meter-Iterationen um ein oder zwei Sekunden!

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

Das geht noch schneller:

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

Aus gutem Grund scheint die Iteration vom Ende des Arrays bis 0 mit etwa 5% am schnellsten zu sein.

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

Ich habe mich gefragt, warum das so sein könnte und habe gegoogelt (ich erinnere mich an etwas über das schnellere Umkehren von Iterationen) und bin auf diese SO-Frage gestoßen, bei der die Zeichenfolge bereits ärgerlich verwendet wird, um die Technik zu char []. Ich denke jedoch, dass der Umkehrtrick in diesem Zusammenhang neu ist.

Was ist der schnellste Weg, um einzelne Zeichen in einer Zeichenfolge in C # zu durchlaufen?


1
Sie könnten source.IndexOf('/', n + 1)die n++und die Klammern der Zeit setzen und verlieren :) Setzen Sie auch eine Variable string word = "/"anstelle des Zeichens.
neeKo

1
Hey Niko, schau dir neue Antworten an. Es könnte jedoch schwieriger sein, Teilzeichenfolgen mit variabler Länge zu erstellen.
Richard Watson

Ich habe etwas Ähnliches benutzt, indem ich durch den Subtring getreten bin. Bis mir klar wurde, dass indexOf einen startIndex hat. Ich mag die erste Lösung am meisten, da sie ein gutes Gleichgewicht zwischen Geschwindigkeit und Speicherbedarf darstellt.
Samir Banjanovic

1
Ich habe irgendwo gelesen, dass es schneller ist, rückwärts zu iterieren, weil es schneller ist, einen Wert mit 0 zu vergleichen
reggaeguitar

1
@shitpoet yup. Wenn Sie sich den zugrunde liegenden Code ansehen, handelt es sich um einen nativen Aufruf. public char [] toCharArray () {... System.arraycopy (Wert, 0, Ergebnis, 0, Wert.Länge); ...}
Richard Watson

46

Diese beiden funktionieren nur für Suchbegriffe mit einem Zeichen ...

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

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

kann sich für längere Nadeln als besser herausstellen ...

Aber es muss einen eleganteren Weg geben. :) :)


Berücksichtigung von Ersetzungen mit mehreren Zeichen. Ohne sie würde das Zählen von "das" in "der Test ist der Schlüssel" 6 zurückgeben.
ZombieSheep

Benchmarked & verglichen dies mit dem String. Split-Way - funktioniert etwa 1,5-mal schneller. Ein großes Lob.
Alex

20

Bearbeiten:

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

2
Das ist was ich mache. Und source.Split(new[]{"//"}, StringSplitOptions.None).Count - 1für Trennzeichen mit mehreren Zeichen.
bzlm

4
Dies würde mindestens n Zeichenfolgenzuweisungen auf dem Heap sowie (möglicherweise) einige Array-Größenänderungen durchführen - und das alles nur, um die Anzahl zu ermitteln? Extrem ineffizient, nicht gut skalierbar und sollte niemals in wichtigen Codes verwendet werden.
Zar Shardan

17

In C # ist ein netter String SubString-Zähler dieser unerwartet knifflige Kerl:

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

1
Gute Lösung - und auch für String (nicht nur für Char)!
ChriPf

Vielen Dank, es ist allzu leicht, einige der Feinheiten des String-Handlings beim Sprachenwechsel zu vergessen - wie es die meisten von uns heutzutage tun!
Dave

1
-1 weil: Kennen Sie den Unterschied zwischen Count () und Count oder Length? Wenn jemand Count () anstelle von Count oder Length verwendet, werde ich ausgelöst. Count () erstellt den IEnumerator und durchläuft dann alle Vorkommen des IEnumerable, während Count oder Length bereits Eigenschaften des Objekts sind, die bereits die gewünschte Anzahl enthalten, ohne dass alle Elemente durchlaufen werden müssen.
Aeroson

Guter Ort, und was seltsam ist, ist, dass ich in meiner Bibliothek, von wo aus ich die Funktion übernommen habe, "Länge" verwende. Bearbeitet!
Dave

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

1
Dies ist nicht korrekt, wenn die Eingabe Regex-Sonderzeichen enthält, z. B. | Es muss eine Regex.Escape (Eingabe) geben
Esben Skov Pedersen

1
Eigentlich stringToMatchentgehen die Bedürfnisse, nicht die input.
Theodor Zoulias

Du hast Recht. Repariert.
Cederlof

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

Da die ursprüngliche Lösung für Zeichen die schnellste war, wird sie vermutlich auch für Zeichenfolgen gelten. Also hier ist mein Beitrag.

Für den Kontext: Ich habe in einer Protokolldatei nach Wörtern wie "fehlgeschlagen" und "erfolgreich" gesucht.

Gr, Ben


2
Übergeben Sie einfach keine leere Zeichenfolge für die Variable "Wort" (Fehler durch Division durch Null).
Andrew Jens

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

20
oder Regex.Matches (s, "65"). Count ^ _ ^
Meta

Funktioniert nicht für jede Zeichenfolge. Versuchen Sie, "++" in "abc ++ def ++ xyz" zu suchen
Marsh-Wiggle

7

Für alle, die eine gebrauchsfertige String-Erweiterungsmethode wünschen,

Folgendes verwende ich, das auf den besten Antworten basiert:

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

Wird die zweite Methode nicht boomen, wenn die übergebene Zeichenfolge null oder leer ist? Was definieren Sie die Eingabe rein aus Stilsicht als System.String und nicht nur als 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

Ich denke, der einfachste Weg, dies zu tun, ist die Verwendung der regulären Ausdrücke. Auf diese Weise können Sie dieselbe Aufteilungsanzahl erhalten wie mit myVar.Split ('x'), jedoch in einer Einstellung mit mehreren Zeichen.

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;

Dies zählt jedes Mal, wenn das Programm "/ s" genau findet (Groß- und Kleinschreibung beachten), und die Anzahl der Vorkommen davon wird in der Variablen "Vorkommen" gespeichert.


3

Ich hatte das Gefühl, dass uns bestimmte Arten der Teilzeichenfolgenzählung fehlten, wie unsichere Byte-für-Byte-Vergleiche. Ich habe die Methode des Originalplakats und alle Methoden zusammengestellt, die mir einfallen.

Dies sind die String-Erweiterungen, die ich gemacht habe.

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

Gefolgt vom Testcode ...

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

Ergebnisse: CSX entspricht CountSubstrX und CCX entspricht CountCharX. "chr" durchsucht eine Zeichenfolge nach "_" und "durchsucht eine Zeichenfolge nach" und ", und" mlw "durchsucht eine Zeichenfolge nach" muchlongerword ".

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

Und schließlich hatte ich eine Datei mit 3,6 Millionen Zeichen. Es wurde "derp adfderdserp dfaerpderp deasderp" 100.000 Mal wiederholt. Ich habe mit den oben genannten Methoden 100 Mal nach "derp" in der Datei gesucht.

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

Meine 4. Methode ist definitiv der Gewinner, aber realistisch gesehen ist all dies vernachlässigbar, wenn eine Datei mit 3,6 Millionen Zeichen 100 Mal nur 1586 ms als der schlimmste Fall benötigte.

Übrigens habe ich auch in der 3,6-Millionen-Zeichen-Datei mit den 100-fachen CountSubstr- und CountChar-Methoden nach dem Zeichen 'd' gesucht. Ergebnisse...

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

Die ursprüngliche Postermethode ist dementsprechend für Nadeln mit einem einzelnen Charakter in einem großen Heuhaufen sehr schlecht.

Hinweis: Alle Werte wurden auf die Ausgabe der Release-Version aktualisiert. Ich habe versehentlich vergessen, auf dem Release-Modus aufzubauen, als ich dies zum ersten Mal gepostet habe. Einige meiner Aussagen wurden geändert.


Vielen Dank für die Leistungsergebnisse. Ein Faktor-Geschwindigkeitsunterschied von 10 könnte ein Grund sein, keine Linq oder eine andere ordentlich geschriebene Lösung in Betracht zu ziehen, sondern eine Erweiterungsmethode zu wählen.
Andreas Reiff

2

Eine generische Funktion für das Auftreten von Zeichenfolgen:

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
Dies erzeugt eine RIESIGE Anzahl temporärer Zeichenfolgen und lässt den Garbage Collector sehr hart arbeiten.
EricLaw

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

Eine Variation von Richard Watsons Antwort, die mit zunehmender Effizienz etwas schneller ist, je öfter das Zeichen in der Zeichenfolge vorkommt, und weniger Code!

Obwohl ich sagen muss, ohne jedes Szenario ausführlich zu testen, habe ich eine sehr signifikante Geschwindigkeitsverbesserung festgestellt, indem ich Folgendes verwendet habe:

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

Musste etwas Ähnliches tun, um bedingte Anweisungen aus einer Zeichenfolge zu testen.

Ersetzte das, wonach ich suchte, durch ein einzelnes Zeichen und zählte die Instanzen des einzelnen Zeichens.

Offensichtlich muss das einzelne Zeichen, das Sie verwenden, überprüft werden, damit es nicht in der Zeichenfolge vorhanden ist, bevor dies geschieht, um falsche Zählungen zu vermeiden.


2

String in String:

Finden Sie "etc" in ".. JD JD JD JD usw. und usw. JDJDJDJDJDJDJDJD und usw."

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.

Überprüfen Sie die Leistung, bevor Sie diese als unsolide / ungeschickt verwerfen ...


2

Meine erste Einstellung gab mir so etwas wie:

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

Die Nadel im Heuhaufen mit Ersetzen und Teilen ergibt mehr als 21 Sekunden, während dies etwa 15,2 Sekunden dauert.

Bearbeiten Sie nach dem Hinzufügen eines Bits, das substring.Length - 1dem charIndex hinzugefügt werden würde (wie es sollte), bei 11,6 Sekunden.

Bearbeiten 2: Ich habe eine Zeichenfolge mit 26 zweistelligen Zeichenfolgen verwendet. Hier sind die Zeiten, die auf dieselben Beispieltexte aktualisiert wurden:

Nadel im Heuhaufen (OP-Version): 7,8 Sekunden

Vorgeschlagener Mechanismus: 4,6 Sekunden.

Bearbeiten 3: Beim Hinzufügen des Einzelzeicheneckens wurden 1,2 Sekunden benötigt.

Edit 4: Für den Kontext: 50 Millionen Iterationen wurden verwendet.


2

Ich dachte, ich würde meine Erweiterungsmethode in den Ring werfen (siehe Kommentare für weitere Informationen). Ich habe kein formelles Benchmarking durchgeführt, aber ich denke, dass es für die meisten Szenarien sehr schnell sein muss.

EDIT: OK - diese SO-Frage hat mich dazu gebracht, mich zu fragen, wie sich die Leistung unserer aktuellen Implementierung gegenüber einigen der hier vorgestellten Lösungen verhalten würde. Ich entschied mich für ein kleines Benchmarking und stellte fest, dass unsere Lösung sehr gut mit der Leistung der von Richard Watson bereitgestellten Lösung übereinstimmt , bis Sie eine aggressive Suche mit großen Zeichenfolgen (100 Kb +) und großen Teilzeichenfolgen (32 Kb +) durchführen ) und viele eingebettete Wiederholungen (10K +). Zu diesem Zeitpunkt war unsere Lösung etwa 2X bis 4X langsamer. Angesichts dessen und der Tatsache, dass uns die von Richard Watson vorgestellte Lösung wirklich gefällt, haben wir unsere Lösung entsprechend überarbeitet. Ich wollte dies nur für alle verfügbar machen, die davon profitieren könnten.

Unsere ursprüngliche Lösung:

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

Und hier ist unsere überarbeitete Lösung:

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

Es wird nur jedes Zeichen in der Zeichenfolge überprüft. Wenn das Zeichen das gesuchte Zeichen ist, fügen Sie eines zum Zählen hinzu.


1

Wenn Sie diese Webseite besuchen , werden 15 verschiedene Methoden verglichen, einschließlich der Verwendung paralleler Schleifen.

Der schnellste Weg scheint darin zu bestehen, entweder eine einzelne for-Schleife mit Thread (wenn Sie die .Net-Version <4.0 haben) oder eine parallele for-Schleife (wenn Sie .Net> 4.0 mit Tausenden von Überprüfungen verwenden) zu verwenden.

Angenommen, "ss" ist Ihre Suchzeichenfolge, "ch" ist Ihr Zeichenarray (wenn Sie mehr als ein Zeichen haben, das Sie suchen), dann ist hier der grundlegende Kern des Codes, der die schnellste Laufzeit mit einem Thread hatte:

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

Der Benchmark-Quellcode wird ebenfalls bereitgestellt, damit Sie Ihre eigenen Tests durchführen können.


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;

Dies dient zum Zählen der Zeichenvorkommen. In diesem Beispiel lautet die Ausgabe "a4b4j3".


2
Nicht ganz "Vorkommen einer Zeichenfolge zählen" mehr Zeichen zählen - wie wäre es mit einer Möglichkeit, anzugeben, mit welcher Zeichenfolge Narenda übereinstimmen soll?
Paul Sullivan

1
int count = 0; string str = "wir haben foo und foo, bitte zähle foo darin"; string stroccurance = "foo"; string [] strarray = str.Split (''); Array.Sort (strarray); str = ""; für (int i = 0; i <strarray.Length - 1; i ++) {if (strarray [i] == stroccurance) {count ++; }} str = "Anzahl der Vorkommen für" + stroccurance + "ist" + count; Dadurch können Sie jedes String-Vorkommen in diesem Beispiel zählen. Ich zähle das Vorkommen von "foo" und es gibt mir die Ausgabe 3.
Narendra Kumar

1

Für den Fall eines Zeichenfolgenbegrenzers (nicht für den Zeichenfall, wie im Betreff angegeben):
string source = "@@@ einmal @@@ nach @@@ a @@@ Zeit @@@";
int count = source.Split (new [] {"@@@"}, StringSplitOptions.RemoveEmptyEntries) .Length - 1;

Der natürliche Begrenzer des ursprünglichen Quellwerts des Posters ("/ einmal / nach / a / Zeit /") ist ein Zeichen '/', und die Antworten erklären die Option source.Split (char []).


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.