Come valuteresti le occorrenze di una stringa (in realtà un carattere) all'interno di una stringa?


865

Sto facendo qualcosa in cui mi sono reso conto che volevo contare quanti /s potevo trovare in una stringa, e poi mi ha colpito, che c'erano diversi modi per farlo, ma non sono riuscito a decidere quale fosse il migliore (o il più semplice) .

Al momento vado con qualcosa del tipo:

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

Ma non mi piace affatto, qualche acquirente?

Non voglio davvero scavare RegExper questo, vero?

So che la mia stringa avrà il termine che sto cercando, quindi puoi presumere che ...

Naturalmente per le stringhe in cui lunghezza> 1 ,

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

34
+1: devo dire che è un modo molto diverso di contare. sono sorpreso dei risultati dei test sui
benchmark

4
Non è così diverso ... è il tipico modo per implementare questa funzionalità in SQL: LEN(ColumnToCheck) - LEN(REPLACE(ColumnToCheck,"N","")).
Sheridan,

6
È un dato di fatto che dovresti dividere per "/".Length
Gerard

3
Posso chiederti, quali sarebbero le tue esigenze secondo cui il conteggio dovrebbe essere per il numero di occorrenze di "//" all'interno di "/////"? 2 o 4?
Les

1
usare regex è probabilmente il modo migliore per farlo
Adam Higgins,

Risposte:


1010

Se stai usando .NET 3.5 puoi farlo in una riga con LINQ:

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

Se non desideri utilizzare LINQ, puoi farlo con:

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

Potresti essere sorpreso di apprendere che la tua tecnica originale sembra essere circa il 30% più veloce di una di queste! Ho appena fatto un rapido benchmark con "/ once / upon / a / time /" e i risultati sono i seguenti:

La tua
fonte originale = 12s. Conta = fonte 19s.
Spaccatura = 17s
foreach ( dalla risposta di bobwienholt ) = 10s

(I tempi sono di 50.000.000 di iterazioni, quindi è improbabile che notiate molta differenza nel mondo reale.)


6
Sì, VS nasconde i metodi di estensione LINQ nella classe stringa. Immagino che pensassero che gli sviluppatori non volessero che tutti quei metodi di estensione fossero mostrati nella classe stringa. Probabilmente una decisione saggia.
Judah Gabriel Himango,

11
È possibile che questo comportamento sia dovuto al fatto che VS2010 include automaticamente System.Linq nei nuovi file di classe, VS2008 probabilmente no. Lo spazio dei nomi deve essere attivo affinché l'intellisense funzioni.
Sprague,

30
Nota che le soluzioni Count e Split funzioneranno solo quando stai contando i personaggi. Non funzioneranno con le stringhe, come fa la soluzione dell'OP.
Peter Lillevold,

5
f == '\' riguarda i caratteri in una stringa, non le stringhe in una stringa
Thomas Weller,

9
Questa sembra la risposta a una domanda diversa: "Come valuteresti le occorrenze di un carattere all'interno di una stringa?"
Ben Aaronson,

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

Deve essere più veloce del source.Replace()da solo.


18
Potresti ottenere un miglioramento marginale passando a a anziché a foreach, ma solo una piccola, piccola parte.
Segna il

17
No. La domanda chiede di contare l'occorrenza della stringa, non del carattere.
YukiSakura,

3
Questo sta contando i caratteri in una stringa. Il titolo riguarda il conteggio delle stringhe in una stringa
Thomas Weller,

2
@Mark L'ho appena testato con un ciclo for ed è stato effettivamente più lento dell'uso di foreach. Potrebbe essere a causa del controllo dei limiti? (Il tempo era 1,65 secondi contro 2,05 su iterazioni da 5 mil.)
Misura il

4
Mentre la domanda richiede una stringa all'interno di una stringa, il problema di esempio pubblicato da OP è in realtà solo un carattere, nel qual caso definirei questa risposta ancora una soluzione valida, poiché mostra un modo migliore (ricerca di caratteri anziché ricerca di stringhe) per affrontare il problema in questione.
Chad,

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

8
+1: in alcuni casi potresti voler aggiungere RegexOptions.IgnoreCase.
TrueWill,

3
non è incredibilmente basso?
Thomas Ayoub,

4
Il sovraccarico Regex non è l'ideale, in più "Non voglio davvero scavare RegEx per questo, vero?"
Chad,

potrebbe non volere Regex.Escape(...)cosìnew System.Text.RegularExpressions.Regex(needle).Matches(haystack).Count;
barlop

2
Sono andato con questo perché può cercare stringhe, non solo caratteri.
James in Indy,

86

Se vuoi essere in grado di cercare intere stringhe e non solo caratteri:

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

Leggi come "per ogni carattere nella stringa, prendi il resto della stringa a partire da quel carattere come sottostringa; contalo se inizia con la stringa di destinazione".


1
Non sono sicuro di come posso spiegarlo in modo più chiaro della descrizione fornita. Cosa c'è di confuso?
mqp

58
SUPER LENTO! L'ho provato su una pagina di HTML e ci sono voluti circa 2 minuti rispetto ad altri metodi in questa pagina che ha impiegato 2 secondi. La risposta era corretta; era troppo lento per essere utilizzabile.
John B

2
d'accordo, troppo lento. sono un grande fan delle soluzioni in stile linq ma questa non è fattibile.
Sprague,

5
Si noti che il motivo per cui è così lento è che crea n stringhe, allocando quindi all'incirca n ^ 2/2 byte.
Peter Crabtree,

6
OutOfMemoryException viene generata per i miei 210000 caratteri di stringa.
terminato il

66

Ho fatto alcune ricerche e ho scoperto che la soluzione di Richard Watson è la più veloce nella maggior parte dei casi. Questa è la tabella con i risultati di ogni soluzione nel post (tranne quelli che usano Regex perché genera eccezioni mentre analizza una stringa come "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|

Si può vedere che nel caso di trovare il numero di occorrenze di sottostringhe corte (1-5 caratteri) in una stringa corta (10-50 caratteri) è preferito l'algoritmo originale.

Inoltre, per la sottostringa a più caratteri è necessario utilizzare il seguente codice (basato sulla soluzione di Richard Watson )

int count = 0, n = 0;

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

Stavo per aggiungere la mia soluzione di "basso livello" (senza creare sottostringhe, usando sostituisci / dividi o qualsiasi Regex / Linq), ma la tua è forse anche migliore della mia (e almeno più corta). Grazie!
Dan W

Per le soluzioni Regex, aggiungi aRegex.Escape(needle)
Thymine il

2
Solo per sottolineare per gli altri, il valore di ricerca deve essere verificato se vuoto, altrimenti entrerai in un ciclo infinito.
WhoIsRich,

2
Forse sono solo io, ma source="aaa" substring="aa"mi aspettavo di tornare 2, non 1. Per "risolvere" questo, cambia n += substring.Lengthinn++
ytoledano

puoi aggiungere la overlappedbandiera per soddisfare il tuo caso in questo modo:overlapped=True;.... if(overlapped) {++n;} else {n += substring.Length;}
tsionyx

54

LINQ funziona su tutte le raccolte e, dato che le stringhe sono solo una raccolta di caratteri, che ne dici di questa piccola e bella copertina:

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

Assicurati di avere using System.Linq;nella parte superiore del file di codice, in quanto .Countè un metodo di estensione da quello spazio dei nomi.


5
Vale davvero la pena usare var lì? C'è qualche possibilità che Count venga sostituito con qualcosa che non restituisce un int?
Che cosa è il

70
@Whatsit: puoi digitare 'var' con la sola mano sinistra mentre 'int' richiede entrambe le mani;)
Sean Bright,

7
intle lettere risiedono tutte nelle chiavi di casa, mentre varnon lo sono. uh .. aspetta, sto usando Dvorak
Michael Buen,

2
@BDotA Assicurati di avere un 'utilizzo di System.Linq;' nella parte superiore del file. Inoltre, intellisense potrebbe nascondere la chiamata .Count poiché è una stringa. Anche così, verrà compilato ed eseguito correttamente.
Giuda Gabriele Himango,

3
@JudahGabrielHimango Direi che var dovrebbe essere usato soprattutto quando il tipo di variabile è ovvio (e per brevità e coerenza)
EriF89

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

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

Sul mio computer è circa 2 secondi più veloce della soluzione per ogni personaggio per 50 milioni di iterazioni.

Revisione 2013:

Cambia la stringa in un carattere [] e ripetilo. Taglia un secondo o due in più sul tempo totale per iterazioni di 50m!

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

Questo è ancora più veloce:

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

Per una buona misura, iterare dalla fine dell'array a 0 sembra essere il più veloce, di circa il 5%.

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

Mi chiedevo perché questo potesse essere e stavo cercando su Google (ricordo qualcosa sull'iterazione inversa di essere più veloce), e mi sono imbattuto in questa domanda SO che usa già fastidiosamente la stringa per char [] tecnica. Penso che il trucco dell'inversione sia nuovo in questo contesto, però.

Qual è il modo più veloce per scorrere i singoli caratteri in una stringa in C #?


1
Potresti mettere source.IndexOf('/', n + 1)e perdere le n++parentesi e del tempo :) Inoltre, inserisci una variabile string word = "/"anziché il personaggio.
NeeKo,

1
Ehi Niko, controlla nuove risposte. Tuttavia, potrebbe essere più difficile realizzare una sottostringa di lunghezza variabile.
Richard Watson,

Ho usato qualcosa di simile attraversando la sottotitola; questo fino a quando ho realizzato indexOf ha un startIndex. Mi piace di più la prima soluzione in quanto è un buon equilibrio tra velocità e memoria.
Samir Banjanovic,

1
Ho letto da qualche parte che è più veloce iterare all'indietro perché è più veloce confrontare un valore a 0
reggaeguitar

1
@shitpoet yup. Se guardi il codice sottostante, è una chiamata nativa. public char [] toCharArray () {... System.arraycopy (valore, 0, risultato, 0, value.length); ...}
Richard Watson il

46

Entrambi funzionano solo con termini di ricerca a carattere singolo ...

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

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

potrebbe rivelarsi migliore per aghi più lunghi ...

Ma ci deve essere un modo più elegante. :)


Per tenere conto delle sostituzioni multi-carattere. Senza di essa, contando "il" in "il test è la chiave" restituirebbe 6.
ZombieSheep,

Benchmark e confrontato con la stringa.Split-way: funziona circa 1,5 volte più veloce. Complimenti.
Alex

20

Modificare:

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

2
Questo è ciò che faccio. E source.Split(new[]{"//"}, StringSplitOptions.None).Count - 1per separatori multi-carattere.
bzlm,

4
Ciò comporterebbe almeno n allocazioni di stringhe nell'heap, oltre a (eventualmente) alcune ridimensionamenti dell'array - e tutto questo solo per ottenere il conteggio? Estremamente inefficiente, non si adatta bene e non dovrebbe mai essere usato in nessun codice importante.
Zar Shardan,

17

In C #, un bel contatore String SubString è questo compagno inaspettatamente complicato:

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

1
Bella soluzione - e funziona anche per le stringhe (non solo per i caratteri)!
ChriPf

Grazie, è fin troppo facile dimenticare alcune delle sottigliezze della gestione delle stringhe quando si scambiano le lingue - come la maggior parte di noi deve ai giorni nostri!
Dave,

1
-1 perché: conosci la differenza tra Count () e Count o Length? Se qualcuno sta usando Count () invece di Count o Length, mi viene attivato. Count () crea IEnumerator quindi passa attraverso tutte le occorrenze di IEnumerable mentre Count o Length sono già impostate le proprietà dell'oggetto che contengono già il conteggio desiderato senza la necessità di iterare su tutti gli elementi.
aeroson,

Buon posto, e la cosa strana è che nella mia libreria, da dove ho preso la funzione, sto usando "Lunghezza". Modificato!
Dave,

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

1
Questo non è corretto se input containt regex caratteri speciali cioè | Deve esserci un Regex.Escape (input)
Esben Skov Pedersen,

1
In realtà i stringToMatchbisogni sono in fuga, non i input.
Theodor Zoulias,

Hai ragione. Aggiustato.
cederlof,

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

Poiché la soluzione originale, era la più veloce per i caratteri, suppongo che lo sarà anche per le stringhe. Quindi ecco il mio contributo.

Per il contesto: stavo cercando parole come "fallito" e "riuscito" in un file di registro.

Gr, Ben


2
Basta non passare una stringa vuota per la variabile "word" (divisione per errore zero).
Andrew Jens,

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

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

Funziona non per ogni stringa. Prova a cercare "++" in "abc ++ def ++ xyz"
marsh-wiggle

7

Per chiunque desideri un metodo di estensione String pronto all'uso,

ecco quello che uso che si basava sulla migliore delle risposte postate:

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

Il secondo metodo non aumenterà se la stringa passata è nulla o vuota? Solo dal punto di vista dello stile, cosa stai definendo l'input come System.String piuttosto che come semplice stringa?
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

Penso che il modo più semplice per farlo sia usare le espressioni regolari. In questo modo è possibile ottenere lo stesso conteggio delle divisioni possibile utilizzando myVar.Split ('x') ma con un'impostazione a più caratteri.

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;

Questo conterà ogni volta che il programma trova esattamente "/ s" (case sensitive) e il numero di occorrenze di questo sarà memorizzato nella variabile "occorrenze"


3

Sentivo che mancavano alcuni tipi di conteggio delle stringhe secondarie, come confronti non sicuri di byte per byte. Ho messo insieme il metodo del poster originale e tutti i metodi che mi sono venuti in mente.

Queste sono le estensioni di stringa che ho creato.

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

Seguito dal codice di prova ...

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

Risultati: CSX corrisponde a CountSubstrX e CCX corrisponde a CountCharX. "chr" cerca una stringa per "_", "e" cerca una stringa per "e", e "mlw" cerca una stringa per "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

E infine, ho avuto un file con 3,6 milioni di caratteri. È stato "derp adfderdserp dfaerpderp deasderp" ripetuto 100.000 volte. Ho cercato "derp" all'interno del file con i metodi di cui sopra 100 volte questi risultati.

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

Quindi il mio quarto metodo è sicuramente il vincitore, ma, realisticamente, se un file di 3,6 milioni di caratteri 100 volte impiegava solo 1586ms come caso peggiore, allora tutto questo è abbastanza trascurabile.

A proposito, ho anche cercato il carattere 'd' nel file da 3,6 milioni di caratteri con 100 volte i metodi CountSubstr e CountChar. Risultati ...

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

Il metodo dei poster originali è molto negativo per gli aghi a carattere singolo in un grande pagliaio secondo questo.

Nota: tutti i valori sono stati aggiornati all'output della versione di rilascio. Ho dimenticato per caso di basarmi sulla modalità di rilascio la prima volta che l'ho pubblicato. Alcune delle mie dichiarazioni sono state modificate.


Grazie per i risultati delle prestazioni. Una differenza di fattore nella velocità di 10 potrebbe essere un motivo per non considerare una linq o un'altra soluzione ben scritta ma optare per un metodo di estensione.
Andreas Reiff il

2

Una funzione generica per le occorrenze di stringhe:

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
Questo crea un numero ENORME di stringhe temporanee e rende il Garbage Collector molto duro.
EricLaw,

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

Una variazione sulla risposta di Richard Watson, leggermente più veloce con un miglioramento dell'efficienza più volte si verifica il carattere nella stringa e meno codice!

Anche se devo dire che, senza aver ampiamente testato ogni scenario, ho visto un miglioramento della velocità molto significativo usando:

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

Necessario fare qualcosa di simile per testare le istruzioni condizionali da una stringa.

Sostituito ciò che stavo cercando con un singolo personaggio e contò le istanze del singolo personaggio.

Ovviamente il singolo carattere che stai usando dovrà essere verificato per non esistere nella stringa prima che ciò accada per evitare conteggi errati.


2

Stringa in stringa:

Trova "etc" in ".. JD JD JD JD ecc. Ecc. JDJDJDJDJDJDJDJD e così via"

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.

Controlla le prestazioni prima di scartare questa come non corretta / goffa ...


2

La mia interpretazione iniziale mi ha dato qualcosa del tipo:

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

L'ago in un approccio al pagliaio con sostituzione e divisione produce 21+ secondi mentre ciò richiede circa 15,2.

Modifica dopo aver aggiunto un po 'che aggiungerebbe substring.Length - 1 a charIndex (come dovrebbe), è a 11,6 secondi.

Modifica 2: ho usato una stringa con 26 stringhe di due caratteri, ecco i tempi aggiornati agli stessi testi di esempio:

Needle in a haystack (versione di OP): 7,8 secondi

Meccanismo suggerito: 4,6 secondi.

Modifica 3: aggiungendo il caso angolare a carattere singolo, è andato a 1,2 secondi.

Modifica 4: Per il contesto: sono stati utilizzati 50 milioni di iterazioni.


2

Ho pensato di buttare il mio metodo di estensione sul ring (vedi commenti per maggiori informazioni). Non ho fatto alcuna marcatura formale al banco, ma penso che debba essere molto veloce per la maggior parte degli scenari.

EDIT: OK - quindi questa domanda SO mi ha portato a chiedermi come le prestazioni della nostra attuale implementazione si sarebbero confrontate con alcune delle soluzioni presentate qui. Ho deciso di fare una piccola marcatura da banco e ho scoperto che la nostra soluzione era molto in linea con le prestazioni della soluzione fornita da Richard Watson fino a quando non si esegue una ricerca aggressiva con stringhe di grandi dimensioni (100 Kb +), sottostringhe di grandi dimensioni (32 Kb + ) e molte ripetizioni integrate (10K +). A quel punto la nostra soluzione era più lenta da 2 a 4 volte. Dato questo e il fatto che ci piace molto la soluzione presentata da Richard Watson, abbiamo riformulato di conseguenza la nostra soluzione. Volevo solo renderlo disponibile per chiunque potesse trarne beneficio.

La nostra soluzione originale:

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

Ed ecco la nostra soluzione rivista:

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

Controlla solo ogni carattere nella stringa, se il personaggio è il personaggio che stai cercando, aggiungine uno per contare.


1

Se dai un'occhiata a questa pagina web , vengono confrontati 15 modi diversi per farlo, incluso l'uso di circuiti paralleli.

Il modo più veloce sembra utilizzare un singolo thread-for-loop (se si dispone della versione .Net <4.0) o un parallel.for loop (se si utilizza .Net> 4.0 con migliaia di controlli).

Supponendo che "ss" sia la tua stringa di ricerca, "ch" è la tua matrice di caratteri (se hai più di un carattere che stai cercando), ecco l'essenza di base del codice che ha il tempo di esecuzione più veloce a thread singolo:

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

Viene fornito anche il codice sorgente del benchmark in modo da poter eseguire i propri test.


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;

Questo è per contare l'occorrenza del personaggio. Per questo esempio l'output sarà "a4b4j3"


2
Non del tutto "contando le occorrenze di una stringa" più contando i caratteri - che ne dite di un modo per specificare quale fosse la stringa da abbinare a Narenda?
Paul Sullivan,

1
int count = 0; string str = "abbiamo foo e foo, per favore contate foo in questo"; string stroccurance = "pippo"; string [] strarray = str.Split (''); Array.Sort (strArray); str = ""; for (int i = 0; i <strarray.Length - 1; i ++) {if (strarray [i] == stroccurance) {count ++; }} str = "Il numero di occorrenze per" + stroccurance + "è" + count; Attraverso questo puoi contare qualsiasi occorrenza di stringa in questo esempio, sto contando la presenza di "pippo" e mi darà l'output 3.
Narendra Kumar,

1

Nel caso di un delimitatore di stringa (non per il caso char, come dice il soggetto):
string source = "@@@ once @@@ upon @@@ a @@@ time @@@";
int count = source.Split (new [] {"@@@"}, StringSplitOptions.RemoveEmptyEntries) .Length - 1;

Il delimitatore naturale del valore sorgente originale del poster ("/ once / upon / a / time /") è un carattere '/' e le risposte spiegano l'opzione source.Split (char []) sebbene ...


0

utilizzando System.Linq;

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

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.