Как бы вы посчитали вхождения строки (на самом деле символ) внутри строки?

721

Я делаю что-то, где я понял, что хотел подсчитать, сколько / я мог найти в строке, а затем мне показалось, что есть несколько способов сделать это, но не мог решить, какой лучший (или самый простой),

На данный момент я собираюсь с чем-то вроде:

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

Но мне это совсем не нравится, какие-то участники?

Я действительно не хочу выкапывать RegEx для этого, не так ли?

Я знаю, что моя строка будет иметь термин, который я ищу, поэтому вы можете предположить, что...

Конечно, для строк, где длина> 1,

string haystack = "/once/upon/a/time";
string needle = "/";
int needleCount = ( haystack.Length - haystack.Replace(needle,"").Length ) / needle.Length;
  • 26
    +1: я должен сказать, что это совсем другой способ считать. я удивлен результатами тестов :)
  • 4
    Это не так уж и отличается ... это типичный способ реализации этой функциональности в SQL: LEN(ColumnToCheck) - LEN(REPLACE(ColumnToCheck,"N","")) .
Показать ещё 4 комментария
Теги:
string

29 ответов

875
Лучший ответ

Если вы используете .NET 3.5, вы можете сделать это в одном слое с LINQ:

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

Если вы не хотите использовать LINQ, вы можете сделать это с помощью

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

Вы можете быть удивлены, узнав, что ваша оригинальная техника, кажется, примерно на 30% быстрее, чем любой из этих! Я только что сделал быстрый тест с "/once/on/a/time/", и результаты следующие:

Ваш оригинал = 12s
source.Count = 19s
source.Split = 17s
foreach (из ответа bobwienholt) = 10 с

(Время для 50 000 000 итераций, поэтому вы вряд ли заметите большую разницу в реальном мире.)

  • 1
    Знаете ли вы, что вы можете вызывать Count (предикат) для строки без необходимости преобразовывать ее в массив символов? Смотрите мой ответ выше.
  • 0
    @Judah, вы правы, но странно VS2008 не дает мне intellisense для источника. Счет - он компилируется и работает нормально, так что +1 за ваш ответ.
Показать ещё 11 комментариев
143
string source = "/once/upon/a/time/";
int count = 0;
foreach (char c in source) 
  if (c == '/') count++;

Быть быстрее, чем source.Replace().

  • 13
    Вы могли бы получить незначительное улучшение, переключившись на for вместо foreach, но только чуть-чуть.
  • 14
    Нет. Вопрос состоит в том, чтобы посчитать вхождение строки, а не символа.
Показать ещё 4 комментария
124
int count = new Regex(Regex.Escape(needle)).Matches(haystack).Count;
  • 6
    +1 - В некоторых случаях вы можете добавить RegexOptions.IgnoreCase .
  • 3
    разве это не невероятно низко?
Показать ещё 2 комментария
76

Если вы хотите иметь возможность поиска целых строк, а не только символов:

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

Прочитайте как "для каждого символа в строке, возьмите оставшуюся часть строки, начиная с этого символа в качестве подстроки, посчитайте ее, если она начинается с целевой строки".

  • 1
    Не уверен, как я могу объяснить это более четко, чем приведенное описание. Что смущает?
  • 46
    СУПЕР МЕДЛЕННО! Попробовал его на странице HTML, и это заняло около 2 минут, в отличие от других методов на этой странице, которые заняли 2 секунды. Ответ был правильным; он был слишком медленным, чтобы его можно было использовать.
Показать ещё 7 комментариев
58

Я провел некоторое исследование и обнаружил, что решение Ричарда Уотсона является самым быстрым в большинстве случаев. То, что таблица с результатами каждого решения в сообщении (кроме тех, которые используют Regex, потому что он генерирует исключения при анализе строки типа "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|

Вы можете видеть, что в случае нахождения числа вхождений коротких подстрок (1-5 символов) в короткой строке (10-50 символов) предпочтительным является исходный алгоритм.

Кроме того, для многосимвольной подстроки вы должны использовать следующий код (на основе решения Ричарда Уотсона)

int count = 0, n = 0;

if(substring != "")
{
    while ((n = source.IndexOf(substring, n, StringComparison.InvariantCulture)) != -1)
    {
        n += substring.Length;
        ++count;
    }
}
  • 0
    Я собирался добавить свое собственное «низкоуровневое» решение (без создания подстрок, использования replace / split или какого-либо Regex / Linq), но ваше, возможно, даже лучше, чем мое (и, по крайней мере, короче). Спасибо!
  • 0
    Для решений Regex добавьте Regex.Escape(needle)
Показать ещё 3 комментария
51

LINQ работает со всеми коллекциями, и поскольку строки являются всего лишь набором символов, как насчет этого симпатичного маленького однострочного интерфейса:

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

Убедитесь, что у вас есть using System.Linq; в верхней части файла кода, так как .Count - это метод расширения из этого пространства имен.

  • 5
    Стоит ли там использовать var? Есть ли шанс, что Count будет заменен чем-то, что не возвращает int?
  • 66
    @Whatsit: вы можете печатать «var» только левой рукой, а «int» - обеими руками;)
Показать ещё 5 комментариев
46
string source = "/once/upon/a/time/";
int count = 0;
int n = 0;

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

На моем компьютере это примерно на 2 секунды быстрее, чем решение для каждого персонажа для 50 миллионов итераций.

Версия 2013:

Измените строку на char [] и повторите ее. Сокращает еще одну секунду или два от общего времени для итераций 50 м!

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

Это еще быстрее:

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

Для хорошей меры повторение с конца массива до 0 кажется самым быстрым, примерно на 5%.

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

Мне было интересно, почему это могло быть и было Googling вокруг (я помню что-то об обратном итерации быстрей), и натолкнулся на этот вопрос, который досадно использует строку для char []. Я думаю, что трюк в обратном направлении является новым в этом контексте.

Каков самый быстрый способ перебора отдельных символов в строке на С#?

  • 1
    Вы можете поместить source.IndexOf('/', n + 1) и потерять n++ и скобки while :) Также вместо символа поместите переменную string word = "/" .
  • 1
    Эй, Нико, зацени новые ответы. Хотя сделать подстроку переменной длины будет сложнее.
Показать ещё 4 комментария
44

Они оба работают только для односимвольных поисковых терминов...

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

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

может оказаться лучше для более длинных игл...

Но должен быть более элегантный способ.:)

  • 0
    Для учета многосимвольных замен. Без него подсчет «в» тесте является ключом »вернул бы 6.
  • 0
    Сравнительный анализ и сравнение со строкой. Сплит-способ - работает примерно в 1,5 раза быстрее. Престижность.
19

Edit:

source.Split('/').Length-1
  • 2
    Вот что я делаю. И source.Split(new[]{"//"}, StringSplitOptions.None).Count - 1 для source.Split(new[]{"//"}, StringSplitOptions.None).Count - 1 разделителей.
  • 3
    Это будет выполнять как минимум n выделений строк в куче, плюс (возможно) несколько переопределений массива - и все это только для подсчета? Чрезвычайно неэффективен, плохо масштабируется и никогда не должен использоваться в каком-либо важном коде.
15
Regex.Matches( Regex.Escape(input),  "stringToMatch" ).Count
  • 1
    Это неверно, если входные данные содержат специальные символы regex ie | Там должен быть Regex.Escape (вход)
  • 1
    Ответ обновлен, ура.
13

В С# хороший счетчик String SubString - это неожиданно сложный человек:

public static int CCount(String haystack, String needle)
{
    return haystack.Split(new[] { needle }, StringSplitOptions.None).Length - 1;
}
  • 1
    Хорошее решение - и работает для строки тоже (не только символ)!
  • 0
    Спасибо, слишком легко забыть некоторые тонкости обработки строк при смене языков - как это делают большинство из нас в наши дни!
Показать ещё 2 комментария
11
string s = "65 fght 6565 4665 hjk";
int count = 0;
foreach (Match m in Regex.Matches(s, "65"))
  count++;
  • 20
    или Regex.Matches (s, "65"). Количество ^ _ ^
10
private int CountWords(string text, string word) {
    int count = (text.Length - text.Replace(word, "").Length) / word.Length;
    return count;
}

Поскольку исходное решение было самым быстрым для символов, я полагаю, оно также будет для строк. Итак, вот мой вклад.

В контексте: Я искал такие слова, как "failed" и "successed" в файле журнала.

Gr, Бен

  • 1
    Только не передавайте пустую строку для переменной «слово» (ошибка деления на ноль).
7

Для тех, кто хочет использовать метод расширения String,

вот что я использую, основываясь на лучших опубликованных ответах:

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

Я думаю, что самый простой способ сделать это - использовать регулярные выражения. Таким образом, вы можете получить тот же счетчик расчётов, что и myVar.Split('x'), но с настройкой нескольких символов.

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

Строка в строке:

Найти "и т.д." в ".. JD JD JD JD и т.д. и т.д. JDJDJDJDJDJDJDJD и т.д."

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.

Проверяйте производительность перед тем, как отбросить это как неудобный/неуклюжий...

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

Вариант ответа Ричарда Уотсона, немного более быстрый с повышением эффективности, больше времени char происходит в строке и меньше кода!

Хотя я должен сказать, без тщательного тестирования каждого сценария, я видел очень значительное улучшение скорости, используя:

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

Общая функция для вхождения строк:

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
    Это создает ОГРОМНОЕ количество временных строк и делает сборщик мусора очень тяжелым.
1

В случае строкового разделителя (не для случая char, как говорит предмет):
string source = "@@@once @@@on @@@a @@@time @@@";
int count = source.Split(new [] { "@@@" }, StringSplitOptions.RemoveEmptyEntries).Length - 1;

Исходное исходное значение плаката ( "/once/on/a/time/" ) естественным разделителем является char '/', и ответы действительно объясняют параметр source.Split(char []), хотя...

1

Мой первоначальный взнос дал мне что-то вроде:

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

Игла в подходе сена с использованием замены и деления дает 21 + секунд, тогда как это занимает около 15,2.

Отредактируйте после добавления бит, который добавит substring.Length - 1 в charIndex (как и должно быть), через 11.6 секунд.

Изменить 2: я использовал строку, в которой было 26 двухсимвольных строк, здесь приведены времена, обновленные до тех же самых текстов:

Игла в стоге сена (версия OP): 7.8 секунды

Предлагаемый механизм: 4.6 секунды.

Редактирование 3: добавление одиночного символьного углового фрейма, оно продолжалось до 1,2 секунды.

Изменить 4: Для контекста: использовалось 50 миллионов итераций.

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

Это будет отсчитываться каждый раз, когда программа находит "/s" точно (с учетом регистра) и число вхождений этого будет сохранено в переменной "вхождения"

1

Думал, что я брошу свой метод расширения на ринг (см. комментарии для получения дополнительной информации). Я не делал никаких официальных проверок, но я думаю, что для большинства сценариев это должно быть очень быстро.

EDIT: ОК - поэтому этот вопрос помог мне понять, как производительность нашей текущей реализации будет складываться против некоторых из представленных здесь решений. Я решил сделать небольшую заметку и нашел, что наше решение в значительной степени соответствует производительности решения, предоставленного Ричардом Уотсоном, до тех пор, пока вы не начнете агрессивный поиск с большими строками (100 Kb +), большими подстроками (32 Kb + ) и много встроенных повторений (10K +). В этот момент наше решение было примерно в 2X-4 раза медленнее. Учитывая это и тот факт, что нам действительно нравится решение, представленное Ричардом Уотсоном, мы соответствующим образом реорганизовали наше решение. Я просто хотел сделать это доступным для всех, кто мог бы извлечь из этого выгоду.

Наше оригинальное решение:

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

И вот наше исправленное решение:

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

Если вы проверьте эту веб-страницу, 15 различных способов сделать это сравниваются, включая использование параллельных циклов.

Самый быстрый способ, по-видимому, заключается в использовании одного потока для цикла (если у вас есть .Net версия < 4.0) или цикл parallel.for(при использовании .Net > 4.0 с тысячами проверок).

Предполагая, что "ss" - это ваша строка поиска, "ch" - это ваш массив символов (если у вас есть более одного char, который вы ищете), здесь основной смысл кода, который имел самое быстрое время выполнения single резьбовое:

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 found. DO what you need to here.
        }
    }
}

Исходный код теста также предоставляется, чтобы вы могли запускать собственные тесты.

1
string s = "HOWLYH THIS ACTUALLY WORKSH WOWH";
int count = 0;
for (int i = 0; i < s.Length; i++)
   if (s[i] == 'H') count++;

Он просто проверяет каждый символ в строке, если символ является символом, который вы ищете, добавьте его для подсчета.

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

Нужно сделать что-то подобное тестовым условным операторам из строки.

Заменили то, что я искал с помощью одного символа, и подсчитал экземпляры одного символа.

Очевидно, что один символ, который вы используете, должен быть проверен, чтобы не существовать в строке, прежде чем это произойдет, чтобы избежать неправильного подсчета.

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");
  • 0
    Пожалуйста, прокомментируйте свой код.
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;

Это для подсчета появления символа. Для этого примера вывод будет "a4b4j3"

  • 2
    Не совсем «подсчет вхождений строки», более подсчет символов - как насчет способа указать, какой строке соответствовать Narenda?
  • 1
    int count = 0; string str = "у нас есть foo и foo, пожалуйста, подсчитайте foo в этом"; 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 = "Количество случаев для" + stroccurance + "is" + count; Посредством этого вы можете сосчитать любое вхождение строки в этом примере. Я подсчитываю вхождение "foo", и оно даст мне вывод 3.
0

Я чувствовал, что нам не хватало определенных видов подсчета подстрок, таких как небезопасные побайтовые сравнения. Я собрал оригинальный метод постера и любые методы, которые могли придумать.

Это расширения строк, которые я сделал.

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

Вслед за тестовым кодом...

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

Результаты: CSX соответствует CountSubstrX, а CCX соответствует CountCharX. "chr" ищет строку для "_", "и" ищет строку для "and", а "mlw" ищет строку для "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

И, наконец, у меня был файл с 3,6 миллионами символов. Это был "derp adfderdserp dfaerpderp deasderp", повторенный 100 000 раз. Я искал "derp" внутри файла с помощью описанных выше методов 100 раз эти результаты.

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

Так что мой 4-й метод определенно является победителем, но на самом деле, если файл с 3,6 миллионами символов 100 раз потребовал всего 1586мс как худший случай, то все это довольно незначительно.

Кстати, я также отсканировал символ 'd' в 3,6-миллионном символьном файле со 100-кратными методами CountSubstr и CountChar. Результаты...

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

Исходя из этого, оригинальный метод постеров очень плох для односимвольных игл в большом стоге сена.

Примечание. Все значения были обновлены до версии выпуска. Я случайно забыл использовать режим Release при первой публикации этого сообщения. Некоторые из моих заявлений были изменены.

Ещё вопросы

Сообщество Overcoder
Наверх
Меню