Como você contaria as ocorrências de uma string (na verdade, um caractere) dentro de uma string?


865

Estou fazendo algo em que percebi que queria contar quantos /s eu conseguia encontrar em uma sequência, e então me ocorreu que havia várias maneiras de fazer isso, mas não conseguia decidir qual era o melhor (ou mais fácil) .

No momento eu vou com algo como:

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

Mas eu não gosto de nada, todos os compradores?

Eu realmente não quero cavar RegExpara isso, não é?

Eu sei que minha string terá o termo que estou procurando, então você pode assumir que ...

Claro que para strings em que length> 1 ,

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

34
+1: devo dizer que é uma maneira muito diferente de fazer contagem. Estou surpreendida com os resultados do teste marca de banco :)
Naveen

4
Não é tão diferente ... é a maneira típica para implementar essa funcionalidade no SQL: LEN(ColumnToCheck) - LEN(REPLACE(ColumnToCheck,"N","")).
21413 Sheridan

6
Por uma questão de fato, você deve dividir por "/".Comprimento
Gerard

3
Posso perguntar, quais seriam os seus requisitos para que o número de ocorrências de "//" dentro de "/////" fosse? 2 ou 4?
Les

1
usando regex é provavelmente a melhor maneira de ir sobre ele
Adam Higgins

Respostas:


1010

Se você estiver usando o .NET 3.5, poderá fazer isso em uma linha com o LINQ:

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

Se você não quiser usar o LINQ, poderá fazê-lo com:

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

Você pode se surpreender ao saber que sua técnica original parece ser cerca de 30% mais rápida que qualquer uma delas! Acabei de fazer uma referência rápida com "/ once / upon / a / time /" e os resultados são os seguintes:

Suas = Original 12s
source.Count = 19s
source.Split = 17s
foreach ( de resposta de bobwienholt ) = 10s

(Os horários são de 50.000.000 de iterações, portanto é improvável que você note muita diferença no mundo real.)


6
Sim, o VS oculta os métodos de extensão LINQ na classe string. Eu acho que eles imaginaram que os desenvolvedores não gostariam que todos esses métodos de extensão aparecessem na classe string. Provavelmente uma decisão sábia.
Judah Gabriel Himango 15/02/09

11
É possível que esse comportamento ocorra porque o VS2010 inclui automaticamente System.Linq em novos arquivos de classe, o VS2008 provavelmente não. O espaço para nome precisa estar presente para que o intellisense funcione.
Sprague

30
Observe que as soluções Count e Split funcionarão apenas quando você estiver contando caracteres. Eles não funcionarão com strings, como a solução do OP.
Peter Lillevold

5
f == '\' é de cerca de caracteres em uma string, não strings em uma string
Thomas Weller

9
Isso parece ser a resposta para uma pergunta diferente: "Como você contaria as ocorrências de um caractere dentro de uma string?"
precisa

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

Tem que ser mais rápido que o source.Replace()próprio.


18
Você pode obter uma melhoria marginal mudando para um for em vez de um foreach, mas apenas um pouquinho, um pouquinho.
Mark Mark

17
Não. A pergunta pede para contar a ocorrência de string, não de caractere.
precisa saber é o seguinte

3
Isso está contando caracteres em uma sequência. O título é de cerca de cordas de contagem em uma string
Thomas Weller

2
O @Mark acabou de testá-lo com um loop for e, na verdade, era mais lento do que usar o foreach. Pode ser por causa da verificação de limites? (O tempo foi de 1,65 s vs 2,05 em 5 mil iterações.)
Medindo

4
Enquanto a pergunta está solicitando uma string dentro de uma string, o problema de exemplo que o OP postou é na verdade apenas um caractere. Nesse caso, eu chamaria essa resposta ainda de uma solução válida, pois mostra uma maneira melhor (pesquisa de caracteres em vez de pesquisa de string) para resolver o problema em questão.
Chad

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

8
+1 - Em alguns casos, você pode querer adicionar RegexOptions.IgnoreCase.
TrueWill 23/06

3
isso não é incrivelmente baixo?
21415 Thomas Thomasououb

4
A sobrecarga do Regex não é ideal, mais "Eu realmente não quero descobrir o RegEx para isso, quero?"
Chad

pode não querer Regex.Escape(...)assimnew System.Text.RegularExpressions.Regex(needle).Matches(haystack).Count;
barlop

2
Fui com este porque ele pode procurar por strings, não apenas caracteres.
James em Indy

86

Se você deseja pesquisar cadeias inteiras, e não apenas caracteres:

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

Leia como "para cada caractere da string, pegue o restante da string a partir desse caractere como uma substring; conte-a se ela começar com a string de destino".


1
Não tenho certeza de como posso explicar isso de uma maneira mais clara do que a descrição fornecida. O que é confuso?
Mqp 8/08

58
SUPER LENTO! Tentei em uma página de html e levou cerca de 2 minutos, em comparação com outros métodos nesta página que levaram 2 segundos. A resposta estava correta; era muito lento para ser utilizável.
JohnB

2
concordou, muito lento. Eu sou um grande fã de soluções no estilo linq, mas essa não é viável.
Sprague

5
Observe que a razão disso é tão lento é que ele cria n strings, alocando aproximadamente n ^ 2/2 bytes.
Peter Crabtree

6
OutOfMemoryException é lançado para meus 210000 caracteres de sequência.
ender

66

Fiz algumas pesquisas e descobri que a solução de Richard Watson é mais rápida na maioria dos casos. Essa é a tabela com os resultados de todas as soluções da postagem (exceto aqueles que usam Regex porque lança exceções ao analisar a string como "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|

Você pode ver que, no caso de encontrar o número de ocorrências de substrings curtos (1 a 5 caracteres) na cadeia curta (10 a 50 caracteres), o algoritmo original é o preferido.

Além disso, para substring de vários caracteres, você deve usar o seguinte código (com base na solução de Richard Watson )

int count = 0, n = 0;

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

Eu estava prestes a adicionar minha própria solução de 'baixo nível' (sem criar substrings, usando substituir / dividir ou qualquer Regex / Linq), mas a sua é possivelmente ainda melhor que a minha (e pelo menos mais curta). Obrigado!
Dan W

Para as soluções Regex, adicione umRegex.Escape(needle)
Thymine

2
Apenas para apontar para outras pessoas, o valor da pesquisa precisa ser verificado se estiver vazio, caso contrário, você entrará em um loop infinito.
WhoIsRich

2
Talvez seja só eu, mas para source="aaa" substring="aa"que eu esperava para voltar 2, não 1. Para "corrigir" isto, a mudança n += substring.Lengthparan++
ytoledano

você pode adicionar a overlappedbandeira para atender seu caso da seguinte maneira:overlapped=True;.... if(overlapped) {++n;} else {n += substring.Length;}
tsionyx 02/09

54

O LINQ funciona em todas as coleções e, como as seqüências de caracteres são apenas uma coleção de caracteres, que tal esse pequeno e simples comentário:

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

Verifique se você tem using System.Linq;na parte superior do seu arquivo de código, como .Counté um método de extensão desse espaço para nome.


5
Realmente vale a pena usar var lá? Existe alguma chance de o Count ser substituído por algo que não retorne um int?
Whatsit

70
@Whatsit: você pode digitar 'var' com apenas a mão esquerda enquanto 'int' exige as duas mãos;)
Sean brilhante

7
inttodas as letras residem nas chaves domésticas, enquanto varnão. uh .. espera, eu estou usando Dvorak
Michael Buen

2
@BDotA Verifique se você tem um 'using System.Linq;' na parte superior do seu arquivo. Além disso, o intellisense pode ocultar a chamada .Count de você, pois é uma sequência. Mesmo assim, ele compilará e funcionará perfeitamente.
Judah Gabriel Himango

3
@JudahGabrielHimango defendo que var deve ser utilizada especialmente quando o tipo de variável é óbvio (e por questões de brevidade e consistência)
EriF89

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

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

No meu computador, é cerca de 2 segundos mais rápido que a solução para todos os caracteres, para 50 milhões de iterações.

Revisão de 2013:

Mude a string para um char [] e repita isso. Corta mais um ou dois segundos no tempo total para iterações de 50m!

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

Isso é mais rápido ainda:

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

Para uma boa medida, a iteração do final da matriz para 0 parece ser a mais rápida, em cerca de 5%.

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

Fiquei me perguntando por que isso poderia ser e estava pesquisando no Google (lembro-me de algo sobre a iteração reversa ser mais rápida), e me deparei com essa pergunta do SO, que irritantemente usa a string para char [] já. Eu acho que o truque de reversão é novo neste contexto, no entanto.

Qual é a maneira mais rápida de iterar caracteres individuais em uma string em C #?


1
Você pode colocar source.IndexOf('/', n + 1)e perder os n++colchetes do tempo :) Além disso, coloque uma variável em string word = "/"vez do caractere.
N

1
Ei Niko, confira novas respostas. Pode ser mais difícil criar substring de comprimento variável.
Richard Watson

Eu usei algo semelhante, percorrendo a subtração; isso até eu perceber que indexOf tem um startIndex. Gosto mais da primeira solução, pois é um bom equilíbrio entre velocidade e pegada de memória.
Samir Banjanovic

1
Eu li em algum lugar que é mais rápido para iterar para trás porque é mais rápido para comparar um valor de 0
reggaeguitar

1
@shitpoet yup. Se você olhar para o código subjacente, é uma chamada nativa. public char [] toCharArray () {... System.arraycopy (valor, 0, resultado, 0, valor.length); ...}
Richard Watson

46

Ambos funcionam apenas para termos de pesquisa de um caractere ...

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

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

pode vir a ser melhor para agulhas mais longas ...

Mas tem que haver uma maneira mais elegante. :)


Para explicar substituições de vários caracteres. Sem ele, a contagem de "o" em "o teste é a chave" retornaria 6.
ZombieSheep

Comparação e comparação com a string.Split-way - funciona cerca de 1,5 vezes mais rápido. Parabéns.
9784 Alex

20

Editar:

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

2
Isto é o que eu faço. E source.Split(new[]{"//"}, StringSplitOptions.None).Count - 1para separadores de vários caracteres.
bzlm

4
Isso executaria pelo menos n alocações de strings no heap, além de (possivelmente) alguns redimensionamentos de array - e tudo isso apenas para obter a contagem? Extremamente ineficiente, não dimensiona bem e nunca deve ser usado em nenhum código importante.
Zar Shardan

17

Em C #, um bom contador String SubString é esse sujeito inesperadamente complicado:

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

1
Ótima solução - e trabalhando também com string (não apenas com char)!
ChriPf

Obrigado, é muito fácil esquecer algumas das sutilezas do manuseio de strings ao trocar de idioma - como a maioria de nós tem até hoje!
Dave

1
-1 porque: você sabe a diferença entre Count () e Count ou Length? Se alguém estiver usando Count () em vez de Count ou Length, sou acionado. Count () cria IEnumerator e passa por todas as ocorrências de IEnumerable, enquanto Count ou Length já são propriedades definidas do objeto que já contêm a contagem desejada, sem a necessidade de iterar todos os elementos.
aeroson

Bom ponto, e o que é estranho é que na minha biblioteca, de onde eu peguei a função, estou usando "Length". Editado!
Dave

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

1
Isso não está correto se a entrada contiver caracteres especiais regex, ou seja, | É necessário que haja um Regex.Escape (de entrada)
Esben Skov Pedersen

1
Na verdade, as stringToMatchnecessidades escapam, não as input.
Theodor Zoulias

Você está certo. Corrigido.
Cederlof 29/05/19

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

Como a solução original foi a mais rápida para caracteres, acho que também será para seqüências de caracteres. Então aqui está minha contribuição.

Para o contexto: eu estava procurando palavras como 'falhou' e 'conseguiu' em um arquivo de log.

Gr, Ben


2
Apenas não passe uma string vazia para a variável "word" (divisão por erro zero).
Andrew Jens

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

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

Funciona não para todas as seqüências. Tentar procurar "++" em "abc ++ ++ def xyz"
pântano-de manobra

7

Para quem deseja um método de extensão String pronto para usar,

Aqui está o que eu uso, baseado nas melhores respostas postadas:

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

O segundo método não será acionado se a sequência passada for nula ou vazia? Puramente do ponto de vista do estilo, o que você está definindo como entrada System.String em vez de apenas string?
Nodeid 29/04/19

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

Eu acho que a maneira mais fácil de fazer isso é usar as expressões regulares. Dessa forma, você pode obter a mesma contagem de divisão que poderia usando myVar.Split ('x'), mas em uma configuração de vários caracteres.

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;

Isso será contado sempre que o programa encontrar "/ s" exatamente (diferencia maiúsculas de minúsculas) e o número de ocorrências disso será armazenado na variável "ocorrências"


3

Senti que estavam faltando certos tipos de contagem de sub-strings, como comparações inseguras de byte a byte. Eu montei o método do pôster original e todos os métodos que consegui pensar.

Estas são as extensões de string que eu fiz.

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

Seguido pelo código de teste ...

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

Resultados: o CSX corresponde ao CountSubstrX e o CCX corresponde ao CountCharX. "chr" pesquisa uma string por '_', "e" pesquisa uma string por "e", e "mlw" pesquisa uma string por "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, finalmente, eu tinha um arquivo com 3,6 milhões de caracteres. Foi "derp adfderdserp dfaerpderp deasderp" repetido 100.000 vezes. Eu procurei por "derp" dentro do arquivo com os métodos acima 100 vezes esses resultados.

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

Portanto, meu quarto método é definitivamente o vencedor, mas, realisticamente, se um arquivo de 3,6 milhões de caracteres 100 vezes levar apenas 1586ms como o pior caso, tudo isso será desprezível.

A propósito, também procurei o 'd' char no arquivo de 3,6 milhões de caracteres com 100 vezes os métodos CountSubstr e CountChar. Resultados...

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

O método original de pôsteres é muito ruim para agulhas de caractere único em um palheiro grande, de acordo com isso.

Nota: Todos os valores foram atualizados para a saída da versão Release. Esqueci-me acidentalmente de usar o modo Release na primeira vez que publiquei isso. Algumas das minhas declarações foram alteradas.


Obrigado pelos resultados de desempenho. Uma diferença de fator na velocidade de 10 pode ser um motivo para não considerar um linq ou outra solução bem escrita, mas seguir um método de extensão.
Andreas Reiff

2

Uma função genérica para ocorrências de strings:

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
Isso cria um número enorme de cadeias temporárias e faz com que o coletor de lixo trabalhe muito.
EricLaw

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

Uma variação na resposta de Richard Watson, um pouco mais rápida com a melhoria da eficiência quanto mais vezes o caractere ocorre na string e menos código!

Embora eu deva dizer, sem testar extensivamente todos os cenários, vi uma melhoria de velocidade muito significativa 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;
            }

Precisava fazer algo semelhante para testar instruções condicionais de uma string.

Substituiu o que eu estava procurando por um único caractere e contou as instâncias do único caractere.

Obviamente, o único caractere que você está usando precisará ser verificado para não existir na string antes que isso aconteça para evitar contagens incorretas.


2

String em string:

Encontre "etc" em ".. JD JD JD JD etc. e etc. JDJDJDJDJDJDJDJD e 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.

Verifique o desempenho antes de descartar este como doentio / desajeitado ...


2

Minha visão inicial me deu algo como:

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

A agulha em uma abordagem de palheiro usando substituição e divisão produz 21+ segundos, enquanto isso leva cerca de 15,2.

Edite depois de adicionar um pouco que adicionaria substring.Length - 1ao charIndex (como deveria), em 11,6 segundos.

Edit 2: usei uma string que tinha 26 strings de dois caracteres, aqui estão os horários atualizados para os mesmos textos de exemplo:

Agulha no palheiro (versão do OP): 7.8 Segundos

Mecanismo sugerido: 4,6 segundos.

Edit 3: Adicionando a caixa de canto de um caractere, foi para 1,2 segundos.

Editar 4: Para o contexto: 50 milhões de iterações foram usadas.


2

Pensei em jogar meu método de extensão no ringue (veja os comentários para mais informações). Não fiz nenhuma marcação formal de banco, mas acho que deve ser muito rápido para a maioria dos cenários.

EDIT: OK - então essa pergunta SO me levou a pensar em como o desempenho da nossa implementação atual se compara a algumas das soluções apresentadas aqui. Decidi fazer uma pequena marcação de banco e descobri que nossa solução estava muito alinhada com o desempenho da solução fornecida por Richard Watson até você fazer uma pesquisa agressiva com grandes seqüências de caracteres (100 Kb +), substrings grandes (32 Kb + ) e muitas repetições incorporadas (10K +). Nesse ponto, nossa solução era 2X a 4X mais lenta. Dado isso e o fato de realmente gostarmos da solução apresentada por Richard Watson, refatoramos nossa solução de acordo. Eu só queria disponibilizar isso para qualquer um que pudesse se beneficiar.

Nossa solução original:

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

E aqui está a nossa solução revisada:

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

Ele apenas verifica todos os caracteres da string. Se o caractere é o que você está procurando, adicione um para contar.


1

Se você verificar esta página da Web , serão comparadas 15 maneiras diferentes de fazer isso, incluindo o uso de loops paralelos.

A maneira mais rápida parece estar usando um loop for de thread único (se você tiver .Net versão <4.0) ou um loop parallel.for (se estiver usando .Net> 4.0 com milhares de verificações).

Supondo que "ss" seja sua String de Pesquisa, "ch" seja sua matriz de caracteres (se você tiver mais de um caractere que está procurando), aqui está a essência básica do código que teve o tempo de execução mais rápido único:

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

O código-fonte de referência também é fornecido para que você possa executar seus próprios testes.


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;

Isto é para contar a ocorrência do personagem. Neste exemplo, a saída será "a4b4j3"


2
Não é mais 'contar ocorrências de uma sequência' mais caracteres contadores - que tal uma maneira de especificar qual a sequência a ser correspondida era Narenda?
Paul Sullivan

1
int count = 0; string str = "temos foo e foo, conte foo nisto"; stroccurance de string = "foo"; string [] strarray = str.Split (''); Array.Sort (strarray); str = ""; for (int i = 0; i <strarray.Length - 1; i ++) {if (strarray [i] == stroccurance) {count ++; }} str = "Número de ocorrências para" + stroccurance + "é" + count; Com isso, você pode contar qualquer ocorrência de string neste exemplo. Estou contando a ocorrência de "foo" e isso me dará a saída 3. #
Narendra Kumar

1

Para o caso de um delimitador de string (não para o caso char, como o sujeito diz):
string source = "@@@ uma vez @@@ em @@@ a @@@ time @@@";
int count = source.Split (novo [] {"@@@"}, StringSplitOptions.RemoveEmptyEntries) .Length - 1;

O delimitador natural do valor-fonte original do pôster ("/ once / upon / a / time /") é um caractere '/' e as respostas explicam a opção source.Split (char []) ...


Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.