c# - veces - visual basic contar caracteres repetidos




¿Cómo contarías las ocurrencias de una cadena(en realidad, un carácter) dentro de una cadena? (19)

Estoy haciendo algo en el que me di cuenta de que quería contar cuántos / s podía encontrar en una cuerda, y luego me pareció que había varias formas de hacerlo, pero no podía decidir cuál era la mejor (o la más fácil). estaba.

Por el momento me voy con algo como:

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

Pero a mi no me gusta nada, ¿algún tomador?

Realmente no quiero desenterrar RegEx para esto, ¿verdad?

Sé que mi cadena tendrá el término que estoy buscando, así que puedes asumir que ...

Por supuesto para cuerdas donde longitud> 1 ,

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

Cadena en cadena:

Encuentre "etc" en ".. JD JD JD JD etc. y etc. JDJDJDJDJDJDJDJD y 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.

Compruebe el rendimiento antes de descartar este como inadecuado / torpe ...


Creo que la forma más fácil de hacer esto es usar las expresiones regulares. De esta manera, puede obtener el mismo conteo dividido que podría usar myVar.Split ('x') pero en una configuración de múltiples 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;

Estos dos solo funcionan para términos de búsqueda de un solo carácter ...

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

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

Puede resultar mejor para agujas más largas ...

Pero tiene que haber una forma más elegante. :)


He investigado un poco y descubrí que la solución de Richard Watson es la más rápida en la mayoría de los casos. Esa es la tabla con los resultados de cada solución en la publicación (excepto los que usan Regex porque arroja excepciones al analizar cadenas 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|

Puede ver que en el caso de encontrar un número de apariciones de subcadenas cortas (1-5 caracteres) en una cadena corta (10-50 caracteres), se prefiere el algoritmo original.

Además, para la subcadena de caracteres múltiples debe usar el siguiente código (basado en la solución de Richard Watson )

int count = 0, n = 0;

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

Mi toma inicial me dio 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;
}

La aguja en un enfoque de pajar que usa reemplazar y división produce más de 21 segundos, mientras que esto toma aproximadamente 15.2.

Edite después de agregar un bit que agregaría substring.Length - 1 Longitud substring.Length - 1 al índice de caracteres (como debería), es de 11.6 segundos.

Edición 2: usé una cadena que tenía 26 cadenas de dos caracteres, aquí están las horas actualizadas para los mismos textos de muestra:

Aguja en un pajar (versión OP): 7.8 Segundos

Mecanismo sugerido: 4,6 segundos.

Edición 3: añadiendo el caso de la esquina de un solo carácter, fue de 1,2 segundos.

Edición 4: Por contexto: se usaron 50 millones de iteraciones.


Para cualquier persona que desee un método de extensión String listo para usar,

Esto es lo que uso, que se basó en la mejor de las respuestas publicadas:

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

Pensé que lanzaría mi método de extensión al ring (ver comentarios para más información). No he realizado ninguna evaluación formal de banco, pero creo que tiene que ser muy rápido para la mayoría de los escenarios.

EDICIÓN: OK, entonces esta pregunta de SO me hizo preguntarme cómo el rendimiento de nuestra implementación actual se compararía con algunas de las soluciones presentadas aquí. Decidí hacer una pequeña marca de banco y descubrí que nuestra solución estaba muy en línea con el rendimiento de la solución proporcionada por Richard Watson hasta que usted está haciendo una búsqueda agresiva con cadenas grandes (100 Kb +), cadenas grandes (32 Kb + ) y muchas repeticiones incrustadas (10K +). En ese punto, nuestra solución fue de 2X a 4X más lenta. Teniendo en cuenta esto y el hecho de que realmente nos gusta la solución presentada por Richard Watson, hemos reformulado nuestra solución en consecuencia. Solo quería poner esto a disposición de cualquier persona que pudiera beneficiarse de él.

Nuestra solución 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;
    }

Y aquí está nuestra solución 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;
    }

Si visita esta página web , se evalúan 15 formas diferentes de hacerlo, incluido el uso de bucles paralelos.

La forma más rápida parece ser usar un solo bucle for-loop (si tiene la versión .Net <4.0) o un bucle paralelo.for (si usa .Net> 4.0 con miles de cheques).

Asumiendo que "ss" es su Cadena de búsqueda, "ch" es su matriz de caracteres (si tiene más de una char que está buscando), aquí está la esencia básica del código que tuvo el hilo de ejecución más rápido:

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

El código fuente de referencia también se proporciona para que pueda ejecutar sus propias pruebas.


Si está utilizando .NET 3.5, puede hacerlo en una sola línea con LINQ:

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

Si no quieres usar LINQ puedes hacerlo con:

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

¡Puede que te sorprenda saber que tu técnica original parece ser un 30% más rápida que cualquiera de estas! Acabo de hacer una prueba rápida con "/ once / upon / a / time /" y los resultados son los siguientes:

Tu original = 12s
source.Count = 19s
source.Split = 17s
foreach ( de la respuesta de bobwienholt ) = 10s

(Los tiempos son de 50,000,000 iteraciones, por lo que es poco probable que note mucha diferencia en el mundo real).


Una función genérica para ocurrencias de cadenas:

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

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

Necesitaba hacer algo similar para probar sentencias condicionales de una cadena.

Reemplazé lo que estaba buscando con un solo carácter y conté las instancias del único carácter.

Obviamente, debe comprobarse que el único carácter que está utilizando no existe en la cadena antes de que esto suceda para evitar recuentos incorrectos.


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

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

Debido a que la solución original fue la más rápida para los caracteres, supongo que también lo será para las cuerdas. Así que aquí está mi contribución.

Para el contexto: estaba buscando palabras como "fallado" y "exitoso" en un archivo de registro.

Gr, ben


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

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

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

string search = "/string";
var occurrences = (regex.Match(search, @"\/")).Count;

Esto contará cada vez que el programa encuentre "/ s" exactamente (distingue entre mayúsculas y minúsculas) y el número de apariciones de este se almacenará en la variable "apariciones"


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

Una variación de la respuesta de Richard Watson, un poco más rápida con la mejora de la eficiencia, cuantas más veces se produzca el carácter en la cadena y menos código.

Aunque debo decir que, sin probar exhaustivamente todos los escenarios, vi una mejora de velocidad muy significativa al usar:

int count = 0;
for (int n = 0; n < source.Length; n++) if (source[n] == '/') count++;

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

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

En mi computadora es aproximadamente 2 segundos más rápido que la solución para cada carácter para 50 millones de iteraciones.

Revisión 2013:

Cambie la cadena a un char [] y repítalo. ¡Corta uno o dos segundos más del tiempo total para las iteraciones de 50 m!

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

Esto es aún más rápido:

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

En buena medida, la iteración desde el final de la matriz a 0 parece ser la más rápida, en aproximadamente un 5%.

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

Me preguntaba por qué podría ser esto y estaba buscando en Google (recuerdo algo sobre la iteración inversa siendo más rápido), y me encontré con esta pregunta de SO que ya usa molesta la técnica de la cadena para char []. Sin embargo, creo que el truco de la inversión es nuevo en este contexto.

¿Cuál es la forma más rápida de iterar a través de caracteres individuales en una cadena en C #?







string