Comment comptez-vous les occurrences d'une chaîne (en fait un caractère) dans une chaîne?

865

Je fais quelque chose où j'ai réalisé que je voulais compter le nombre de /s que je pouvais trouver dans une chaîne, puis cela m'a frappé, qu'il y avait plusieurs façons de le faire, mais je ne pouvais pas décider quel était le meilleur (ou le plus facile) .

En ce moment, je vais avec quelque chose comme:

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

Mais je n'aime pas ça du tout, des preneurs?

Je ne veux pas vraiment creuser RegExpour ça, n'est-ce pas ?

Je sais que ma chaîne aura le terme que je recherche, vous pouvez donc supposer que ...

Bien sûr, pour les chaînes dont la longueur> 1 ,

string haystack = "/once/upon/a/time";
string needle = "/";
int needleCount = ( haystack.Length - haystack.Replace(needle,"").Length ) / needle.Length;
en dépit
la source
34
+1: je dois dire que c'est une façon très différente de faire le décompte. je suis surpris des résultats du test de
référence
4
Ce n'est pas si différent ... c'est la façon typique pour implémenter cette fonctionnalité dans SQL: LEN(ColumnToCheck) - LEN(REPLACE(ColumnToCheck,"N","")).
Sheridan
6
En fait, vous devez diviser par "/".Length
Gerard
3
Puis-je demander, quelles seraient vos exigences selon lesquelles le nombre devrait être pour le nombre d'occurrences de "//" dans "/////"? 2 ou 4?
Les
1
l'utilisation de regex est probablement la meilleure façon de procéder
Adam Higgins

Réponses:

1010

Si vous utilisez .NET 3.5, vous pouvez le faire dans une seule ligne avec LINQ:

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

Si vous ne souhaitez pas utiliser LINQ, vous pouvez le faire avec:

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

Vous pourriez être surpris d'apprendre que votre technique d'origine semble être environ 30% plus rapide que l'une ou l'autre! Je viens de faire un benchmark rapide avec "/ once / upon / a / time /" et les résultats sont les suivants:

Votre
source d' origine = 12 s. Nombre = source de 19
s. Split = 17 s
foreach ( d'après la réponse de bobwienholt ) = 10 s

(Les temps sont pour 50 000 000 itérations, il est donc peu probable que vous remarquiez beaucoup de différence dans le monde réel.)

LukeH
la source
6
Oui, VS masque les méthodes d'extension LINQ sur la classe de chaîne. Je suppose qu'ils ont pensé que les développeurs ne voudraient pas que toutes ces méthodes d'extension apparaissent dans la classe des chaînes. Probablement une sage décision.
Judah Gabriel Himango
11
Il est possible que ce comportement soit dû au fait que VS2010 inclut automatiquement System.Linq dans les nouveaux fichiers de classe, contrairement à VS2008. L'espace de noms doit être accessible pour que l'intellisense fonctionne.
Sprague
30
Notez que les solutions Count et Split ne fonctionnent que lorsque vous comptez des caractères. Ils ne fonctionneront pas avec des chaînes, comme le fait la solution de l'OP.
Peter Lillevold
5
f == '\' concerne les caractères dans une chaîne, pas les chaînes dans une chaîne
Thomas Weller
9
Cela semble être la réponse à une autre question: "Comment compteriez-vous les occurrences d'un caractère dans une chaîne?"
Ben Aaronson
181
string source = "/once/upon/a/time/";
int count = 0;
foreach (char c in source) 
  if (c == '/') count++;

Doit être plus rapide que le source.Replace()par lui-même.

bobwienholt
la source
18
Vous pourriez gagner une amélioration marginale en passant à un pour au lieu d'un foreach, mais seulement un tout petit peu.
Mark
17
Non. La question demande de compter l'occurrence de la chaîne, pas du caractère.
YukiSakura
3
C'est compter les caractères dans une chaîne. Le titre consiste à compter les chaînes dans une chaîne
Thomas Weller
2
@Mark vient de le tester avec une boucle for et il était en fait plus lent que d'utiliser foreach. Cela pourrait être dû à la vérification des limites? (Le temps était de 1,65 s contre 2,05 sur des itérations de 5 mil.)
Mesure
4
Alors que la question demande une chaîne dans une chaîne, l'exemple de problème OP publié n'est en fait qu'un seul caractère, auquel cas j'appellerais cette réponse toujours une solution valide, car elle montre une meilleure façon (recherche de caractères au lieu de recherche de chaînes) pour résoudre le problème actuel.
Chad
136
int count = new Regex(Regex.Escape(needle)).Matches(haystack).Count;
Encore un autre créateur de code
la source
8
+1 - Dans certains cas, vous souhaiterez peut-être ajouter RegexOptions.IgnoreCase.
TrueWill
3
n'est-ce pas incroyablement bas?
Thomas Ayoub
4
Les frais généraux de Regex ne sont pas idéaux, plus "Je ne veux pas vraiment déterrer RegEx pour cela, n'est-ce pas?"
Chad
pourrait ne pas vouloir Regex.Escape(...)ainsinew System.Text.RegularExpressions.Regex(needle).Matches(haystack).Count;
barlop
2
Je suis allé avec celui-ci car il peut rechercher des chaînes, pas seulement des caractères.
James à Indy le
86

Si vous voulez pouvoir rechercher des chaînes entières, et pas seulement des caractères:

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

Lire "pour chaque caractère de la chaîne, prenez le reste de la chaîne à partir de ce caractère comme une sous-chaîne; comptez-le s'il commence par la chaîne cible".

mqp
la source
1
Je ne sais pas comment je peux l'expliquer d'une manière plus claire que la description donnée. Qu'est-ce qui prête à confusion?
mqp
58
SUPER LENT! Je l'ai essayé sur une page html et cela a pris environ 2 minutes par rapport aux autres méthodes de cette page qui ont pris 2 secondes. La réponse était correcte; c'était juste trop lent pour être utilisable.
JohnB
2
d'accord, trop lent. je suis un grand fan des solutions de style linq mais celle-ci n'est tout simplement pas viable.
Sprague
5
Notez que la raison pour laquelle cela est si lent est qu'il crée n chaînes, allouant ainsi environ n ^ 2/2 octets.
Peter Crabtree
6
OutOfMemoryException est levée pour mes 210000 caractères de chaîne.
ender
66

J'ai fait quelques recherches et découvert que la solution de Richard Watson est la plus rapide dans la plupart des cas. C'est le tableau avec les résultats de chaque solution dans le post (sauf ceux qui utilisent Regex car il lève des exceptions lors de l'analyse de la chaîne comme "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|

Vous pouvez voir qu'en cas de recherche du nombre d'occurrences de sous-chaînes courtes (1-5 caractères) dans une chaîne courte (10-50 caractères), l'algorithme original est préféré.

De plus, pour la sous-chaîne multicaractères, vous devez utiliser le code suivant (basé sur la solution de Richard Watson )

int count = 0, n = 0;

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

J'étais sur le point d'ajouter ma propre solution `` bas niveau '' (sans créer de sous-chaînes, en utilisant replace / split, ou tout Regex / Linq), mais la vôtre est peut-être encore meilleure que la mienne (et au moins plus courte). Merci!
Dan W

Pour les solutions Regex, ajoutez unRegex.Escape(needle)
Thymine

2
Juste pour signaler aux autres, la valeur de recherche doit être vérifiée si elle est vide, sinon vous entrerez dans une boucle infinie.
WhoIsRich

2
Peut-être que c'est juste moi, mais car source="aaa" substring="aa"je m'attendais à en récupérer 2, pas 1. Pour "corriger" cela, n += substring.Lengthn++
passez

vous pouvez ajouter le overlappeddrapeau pour répondre à votre cas comme ceci:overlapped=True;.... if(overlapped) {++n;} else {n += substring.Length;}
tsionyx

54

LINQ fonctionne sur toutes les collections, et puisque les chaînes ne sont qu'une collection de caractères, que diriez-vous de ce joli petit one-liner:

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

Assurez-vous que vous avez using System.Linq;en haut de votre fichier de code, tout comme .Countune méthode d'extension de cet espace de noms.


5
Cela vaut-il vraiment la peine d'utiliser var là-bas? Y a-t-il une chance que Count soit remplacé par quelque chose qui ne retourne pas d'int?
Whatsit

70
@Whatsit: vous pouvez taper 'var' avec juste votre main gauche tandis que 'int' requiert les deux mains;)
Sean Bright

7
intles lettres résident toutes dans les clés de la maison, mais varpas. euh .. attendez, j'utilise Dvorak
Michael Buen

2
@BDotA Assurez-vous d'avoir un 'using System.Linq;' en haut de votre dossier. En outre, intellisense peut vous masquer l'appel .Count car il s'agit d'une chaîne. Même ainsi, il se compilera et fonctionnera très bien.
Judah Gabriel Himango

3
@JudahGabrielHimango Je dirais que var devrait être utilisé surtout lorsque le type de variable est évident (et pour plus de concision et de cohérence)
EriF89

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

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

Sur mon ordinateur, c'est environ 2 secondes plus rapide que la solution pour tous les caractères pour 50 millions d'itérations.

Révision 2013:

Remplacez la chaîne par un char [] et parcourez-le. Réduit encore une ou deux secondes le temps total pour les itérations de 50 m!

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

C'est encore plus rapide:

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

Pour faire bonne mesure, l'itération de la fin du tableau à 0 semble être la plus rapide, d'environ 5%.

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

Je me demandais pourquoi cela pouvait être et parcourait Google (je me souviens de quelque chose à propos de l'itération inverse étant plus rapide), et je suis tombé sur cette question SO qui utilise déjà la technique de la chaîne à char [] de manière agaçante. Je pense que l'astuce d'inversion est nouvelle dans ce contexte, cependant.

Quel est le moyen le plus rapide pour parcourir les caractères individuels d'une chaîne en C #?


1
Vous pourriez mettre source.IndexOf('/', n + 1)et perdre le n++et les crochets du moment :) En outre, mettez une variable à la string word = "/"place du caractère.
neeKo

1
Hé Niko, vérifie les nouvelles réponses. Il pourrait cependant être plus difficile de créer une sous-chaîne de longueur variable.
Richard Watson

J'ai utilisé quelque chose de similaire en parcourant la sous-chaîne; c'est jusqu'à ce que je réalise que indexOf a un startIndex. J'aime la première solution, car c'est un bon équilibre entre la vitesse et l'empreinte mémoire.
Samir Banjanovic

1
J'ai lu quelque part qu'il est plus rapide d'itérer en arrière car il est plus rapide de comparer une valeur à 0
reggaeguitar

1
@shitpoet yup. Si vous regardez le code sous-jacent, c'est un appel natif. public char [] toCharArray () {... System.arraycopy (valeur, 0, résultat, 0, valeur.longueur); ...}

la source
46

Ces deux ne fonctionnent que pour les termes de recherche à un seul caractère ...

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

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

peut se révéler meilleur pour les aiguilles plus longues ...

Mais il doit y avoir une manière plus élégante. :)

ZombieSheep
la source
Pour tenir compte des remplacements multi-caractères. Sans cela, compter "le" dans "le test est la clé" retournerait 6.
ZombieSheep
Benchmarké et comparé à la chaîne.Split-way - fonctionne environ 1,5 fois plus rapidement. Gloire.
Alex
20

Éditer:

source.Split('/').Length-1
Brian Rudolph
la source
2
C'est ce que je fais. Et source.Split(new[]{"//"}, StringSplitOptions.None).Count - 1pour les séparateurs multi-caractères.
bzlm
4
Cela effectuerait au moins n allocations de chaînes sur le tas, plus (éventuellement) quelques redimensionnements de tableau - et tout cela juste pour obtenir le nombre? Extrêmement inefficace, ne s'adapte pas bien et ne doit jamais être utilisé dans un code important.
Zar Shardan
17

En C #, un joli compteur String SubString est ce gars incroyablement délicat:

public static int CCount(String haystack, String needle)
{
    return haystack.Split(new[] { needle }, StringSplitOptions.None).Length - 1;
}
Dave
la source
1
Belle solution - et fonctionne aussi pour la chaîne (pas seulement le caractère)!
ChriPf
Merci, il est trop facile d'oublier certaines des subtilités de la gestion des chaînes lors du changement de langue - comme la plupart d'entre nous le font de nos jours!
Dave
1
-1 car: Connaissez-vous la différence entre Count () et Count ou Length? Si quelqu'un utilise Count () au lieu de Count ou Length, je suis déclenché. Count () crée IEnumerator puis passe par toutes les occurrences de IEnumerable tandis que Count ou Length sont déjà des propriétés définies de l'objet qui contiennent déjà le nombre que vous souhaitez sans avoir à parcourir tous les éléments.
aeroson
Bon endroit, et ce qui est bizarre, c'est que dans ma bibliothèque, d'où j'ai pris la fonction, j'utilise "Longueur". Édité!
Dave
15
Regex.Matches(input,  Regex.Escape("stringToMatch")).Count
cederlof
la source
1
Ce n'est pas correct si les caractères spéciaux de l'expression rationnelle contenue en entrée, c.-à-d. | Il doit y avoir un Regex.Escape (entrée)
Esben Skov Pedersen
1
En fait, ce sont les stringToMatchbesoins qui s'échappent, pas le input.
Theodor Zoulias
Tu as raison. A corrigé.
cederlof
13
private int CountWords(string text, string word) {
    int count = (text.Length - text.Replace(word, "").Length) / word.Length;
    return count;
}

Parce que la solution d'origine était la plus rapide pour les caractères, je suppose que ce sera également le cas pour les chaînes. Voici donc ma contribution.

Pour le contexte: je cherchais des mots comme «échoué» et «réussi» dans un fichier journal.

Gr, Ben

Ben
la source
2
Ne passez pas une chaîne vide pour la variable "word" (division par erreur zéro).
Andrew Jens
12
string s = "65 fght 6565 4665 hjk";
int count = 0;
foreach (Match m in Regex.Matches(s, "65"))
  count++;
preetham
la source
20
ou Regex.Matches (s, "65"). Count ^ _ ^
Meta
Ne fonctionne pas pour chaque chaîne. Essayez de rechercher "++" dans "abc ++ def ++ xyz"
marsh-wiggle
7

Pour tous ceux qui souhaitent une méthode d'extension de chaîne prête à l'emploi,

voici ce que j'utilise qui était basé sur la meilleure des réponses postées:

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;
    }
}
WhoIsRich
la source
La deuxième méthode ne va-t-elle pas exploser si la chaîne transmise est nulle ou vide? Purement du point de vue du style, qu'est-ce que vous définissez l'entrée en tant que System.String plutôt que simplement chaîne?
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;
}
user460847
la source
5

Je pense que la façon la plus simple de le faire est d'utiliser les expressions régulières. De cette façon, vous pouvez obtenir le même nombre de divisions que vous pourriez utiliser avec myVar.Split ('x') mais dans un paramètre à plusieurs caractères.

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;
Beroc
la source
3
string search = "/string";
var occurrences = (regex.Match(search, @"\/")).Count;

Cela comptera chaque fois que le programme trouvera "/ s" exactement (sensible à la casse) et le nombre d'occurrences de ceci sera stocké dans la variable "occurrences"

Adam Higgins
la source
3

Je sentais que nous manquions de certains types de comptage de sous-chaînes, comme les comparaisons d'octets à octets dangereuses. J'ai rassemblé la méthode de l'affiche originale et toutes les méthodes auxquelles je pouvais penser.

Ce sont les extensions de chaîne que j'ai faites.

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

Suivi du code de test ...

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

Résultats: CSX correspond à CountSubstrX et CCX correspond à CountCharX. "chr" recherche une chaîne pour '_', "et" recherche une chaîne pour "et", et "mlw" recherche une chaîne pour "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

Et enfin, j'avais un fichier de 3,6 millions de caractères. C'était "derp adfderdserp dfaerpderp deasderp" répété 100 000 fois. J'ai recherché "derp" dans le fichier avec les méthodes ci-dessus 100 fois ces résultats.

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

Donc, ma 4ème méthode est définitivement la gagnante, mais, de façon réaliste, si un fichier de 3,6 millions de caractères 100 fois ne prend que 1586 ms dans le pire des cas, tout cela est assez négligeable.

Soit dit en passant, j'ai également recherché le caractère «d» dans le fichier de 3,6 millions de caractères avec 100 fois les méthodes CountSubstr et CountChar. Résultats...

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

La méthode des affiches originales est très mauvaise pour les aiguilles à caractère unique dans une grande botte de foin selon cela.

Remarque: Toutes les valeurs ont été mises à jour pour publier la sortie de la version. J'ai accidentellement oublié de miser sur le mode Release lors de ma première publication. Certaines de mes déclarations ont été modifiées.

Nicholas R. Grant
la source
Merci pour les résultats de performance. Une différence de facteur dans la vitesse de 10 pourrait être une raison pour ne pas considérer un linq ou une autre solution soigneusement écrite, mais opter pour une méthode d'extension.
Andreas Reiff
2

Une fonction générique pour les occurrences de chaînes:

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;
}
Stefanos Kargas
la source
2
Cela crée un ENORME nombre de chaînes temporaires et rend le garbage collector très dur.
EricLaw
2
string source = "/once/upon/a/time/";
int count = 0, n = 0;
while ((n = source.IndexOf('/', n) + 1) != 0) count++;

Une variation de la réponse de Richard Watson, légèrement plus rapide avec une amélioration de l'efficacité plus le caractère se produit dans la chaîne et moins de code!

Bien que je doive dire, sans tester intensivement chaque scénario, j'ai vu une amélioration de vitesse très significative en utilisant:

int count = 0;
for (int n = 0; n < source.Length; n++) if (source[n] == '/') count++;
user2011559
la source
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;
            }

Nécessaire de faire quelque chose de similaire pour tester les instructions conditionnelles à partir d'une chaîne.

Remplacé ce que je cherchais par un seul caractère et compté les instances du seul caractère.

Évidemment, le caractère unique que vous utilisez devra être vérifié pour ne pas exister dans la chaîne avant que cela ne se produise afin d'éviter des décomptes incorrects.

bizah
la source
2

Chaîne en chaîne:

Trouvez "etc" dans ".. JD JD JD JD etc. et etc. JDJDJDJDJDJDJDJD et etc."

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.

Vérifiez les performances avant de jeter celui-ci comme non sain / maladroit ...

user3090281
la source
2

Ma prise initiale m'a donné quelque chose comme:

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'aiguille dans une approche de meule de foin utilisant le remplacement et la division donne 21+ secondes alors que cela prend environ 15,2.

Modifier après avoir ajouté un peu qui ajouterait substring.Length - 1au charIndex (comme il se doit), c'est à 11,6 secondes.

Edit 2: J'ai utilisé une chaîne qui avait 26 chaînes de deux caractères, voici les temps mis à jour pour les mêmes exemples de textes:

Aiguille dans une botte de foin (version OP): 7,8 secondes

Mécanisme suggéré: 4,6 secondes.

Edit 3: Ajout de la casse de coin à un seul caractère, il est passé à 1,2 seconde.

Edit 4: Pour le contexte: 50 millions d'itérations ont été utilisées.

Allen Clark Copeland Jr
la source
2

Je pensais que je jetterais ma méthode d'extension dans le ring (voir les commentaires pour plus d'informations). Je n'ai pas fait de benchmarking formel, mais je pense que cela doit être très rapide pour la plupart des scénarios.

EDIT: OK - donc cette question SO m'a amené à me demander comment les performances de notre implémentation actuelle se comparent à certaines des solutions présentées ici. J'ai décidé de faire un petit benchmark et j'ai trouvé que notre solution était très en ligne avec les performances de la solution fournie par Richard Watson jusqu'à ce que vous fassiez une recherche agressive avec de grandes chaînes (100 Kb +), de grandes sous-chaînes (32 Kb + ) et de nombreuses répétitions intégrées (10K +). À ce stade, notre solution était environ 2X à 4X plus lente. Compte tenu de cela et du fait que nous aimons vraiment la solution présentée par Richard Watson, nous avons refactorisé notre solution en conséquence. Je voulais juste mettre cela à la disposition de tous ceux qui pourraient en bénéficier.

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

Et voici notre solution révisée:

    /// <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;
    }
Casey Chester
la source
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");
Prashanth
la source
1
string s = "HOWLYH THIS ACTUALLY WORKSH WOWH";
int count = 0;
for (int i = 0; i < s.Length; i++)
   if (s[i] == 'H') count++;

Il vérifie simplement chaque caractère de la chaîne, si le caractère est le caractère que vous recherchez, ajoutez-en un à compter.

joppiesaus
la source
1

Si vous consultez cette page Web , 15 façons différentes de le faire sont évaluées, y compris en utilisant des boucles parallèles.

Le moyen le plus rapide semble utiliser soit une seule boucle for threadée (si vous avez la version .Net <4.0) ou une boucle parallel.for (si vous utilisez .Net> 4.0 avec des milliers de vérifications).

En supposant que "ss" est votre chaîne de recherche, "ch" est votre tableau de caractères (si vous avez plus d'un caractère que vous recherchez), voici l'essentiel du code qui avait le temps d'exécution le plus rapide en un seul thread:

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

Le code source de référence est également fourni afin que vous puissiez exécuter vos propres tests.


la source
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;

C'est pour compter l'occurrence du personnage. Pour cet exemple, la sortie sera "a4b4j3"

Narendra Kumar
la source
2
Pas tout à fait «compter les occurrences d'une chaîne» plus de caractères de comptage - que diriez-vous d'une façon de spécifier à quoi correspondait la chaîne Narenda?
Paul Sullivan
1
nombre entier = 0; string str = "nous avons foo et foo, veuillez compter foo dans ce cas"; string stroccurance = "foo"; string [] strarray = str.Split (''); Array.Sort (strarray); str = ""; for (int i = 0; i <strarray.Length - 1; i ++) {if (strarray [i] == stroccurance) {count ++; }} str = "Le nombre d'occurrences pour" + stroccurance + "est" + count; Grâce à cela, vous pouvez compter n'importe quelle occurrence de chaîne dans cet exemple, je compte l'occurrence de "foo" et cela me donnera la sortie 3.
Narendra Kumar
1

Pour le cas d'un délimiteur de chaîne (pas pour le cas char, comme le dit le sujet):
string source = "@@@ once @@@ upon @@@ a @@@ time @@@";
int count = source.Split (new [] {"@@@"}, StringSplitOptions.RemoveEmptyEntries) .Length - 1;

Le délimiteur naturel de la valeur d'origine de l'affiche ("/ once / sur / a / heure /") est un caractère '/' et les réponses expliquent l'option source.Split (char []) si ...

Sam Saarian
la source
0

using System.Linq;

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

Solarev Sergey
la source