Avatar ·

Как посчитать количество вхождений символа или подстроки в строке в C#?

Привет всем! Я начинающий C# разработчик и столкнулся с задачей, которая кажется простой, но я никак не могу найти элегантное решение. Мне нужно посчитать, сколько раз определенный символ (например, 'a') встречается в строке. Я пробовал писать цикл вручную, но это выглядит громоздко. Вот мой текущий код:
string text = "Hello, world! Hello, C#!";
char target = 'l';
int count = 0;
for (int i = 0; i < text.Length; i++)
{
    if (text[i] == target)
        count++;
}
Console.WriteLine(count); // Выводит 4
Это работает, но мне кажется, что в C# должно быть встроенное решение. Кроме того, мне нужно считать не только символы, но и целые подстроки. Например, сколько раз слово "Hello" встречается в строке "HelloHelloWorld". Если я использую тот же подход с циклом, то для подстрок это становится очень запутанным — нужно проверять совпадение по нескольким индексам. Подскажите, есть ли в .NET готовые методы для подсчета вхождений? Может быть, что-то типа LINQ или Regex? Или нужно писать свой велосипед? Буду благодарен за примеры с кодом и объяснениями. Заранее спасибо! P.S. Версия .NET — .NET 6 или выше, если это важно. <<>>

Лучший способ: использование LINQ и Regex для подсчета вхождений

В C# есть несколько элегантных способов решить вашу задачу без написания громоздких циклов. Выбор метода зависит от того, что вы считаете: одиночные символы или подстроки, а также от требований к производительности.

1. Подсчет одиночных символов через LINQ

Самый простой и читаемый способ — использовать метод Count() из LINQ. Он работает с любым IEnumerable, а строка в C# реализует этот интерфейс как последовательность символов:

using System;
using System.Linq;

string text = "Hello, world! Hello, C#!";
char target = 'l';
int count = text.Count(c => c == target);
Console.WriteLine(count); // Выводит 4

Это однострочное решение. Выражение c => c == target — это лямбда-функция, которая проверяет каждый символ. Метод Count() автоматически перебирает все символы и возвращает количество совпадений. Код компактный и легко читается.

2. Подсчет подстрок с помощью Regex

Для подсчета вхождений целой подстроки (например, "Hello") лучше всего подходит класс Regex из пространства имен System.Text.RegularExpressions. Регулярные выражения специально разработаны для поиска шаблонов в тексте:

using System;
using System.Text.RegularExpressions;

string text = "HelloHelloWorld";
string pattern = "Hello";
int count = Regex.Matches(text, pattern).Count;
Console.WriteLine(count); // Выводит 2

Метод Regex.Matches() находит все непересекающиеся вхождения шаблона в строке и возвращает коллекцию совпадений. Свойство Count этой коллекции дает нужное число. Важно: по умолчанию поиск регистрозависимый. Если нужно игнорировать регистр, используйте флаг RegexOptions.IgnoreCase:

int count = Regex.Matches(text, pattern, RegexOptions.IgnoreCase).Count;

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

Если вы не хотите использовать Regex (например, из-за производительности или простоты), можно применить метод IndexOf в цикле:

string text = "HelloHelloWorld";
string pattern = "Hello";
int count = 0;
int index = 0;
while ((index = text.IndexOf(pattern, index, StringComparison.Ordinal)) != -1)
{
    count++;
    index += pattern.Length; // Сдвигаемся на длину подстроки, чтобы избежать перекрытий
}
Console.WriteLine(count); // Выводит 2

Этот подход быстрее, чем Regex, для простых строк без специальных символов, но требует больше кода. Параметр StringComparison.Ordinal обеспечивает самый быстрый поиск без учета культуры.

4. Подсчет символов с учетом производительности

Если вам нужно считать только символы и производительность критична (например, обрабатываются миллионы строк), LINQ может быть медленнее, чем ручной цикл или специализированный метод. В .NET Core / .NET 5+ есть метод Span.Count() для подсчета символов:

ReadOnlySpan<char> span = text.AsSpan();
int count = 0;
foreach (char c in span)
{
    if (c == target) count++;
}
// Или используйте встроенный метод (только .NET 6+):
// int count = span.Count(target); // Недоступен напрямую, но есть в MemoryExtensions

Рекомендации

  • Для символов: используйте text.Count(c => c == target) — просто и читаемо.
  • Для подстрок: используйте Regex.Matches() или цикл с IndexOf().
  • Для высокой производительности: пишите ручной цикл со Span или используйте IndexOf().

Выберите подход, который лучше всего соответствует вашим требованиям к читаемости и скорости. Для большинства приложений LINQ и Regex — идеальный выбор.

<<>>

Альтернативный подход: использование Split и Replace

Если вы предпочитаете избегать LINQ и Regex, можно использовать трюки со стандартными строковыми методами. Вот два нестандартных, но рабочих способа:

1. Подсчет через Split

Метод Split() разделяет строку по указанному разделителю. Количество вхождений подстроки будет равно количеству частей минус один:

string text = "HelloHelloWorld";
string pattern = "Hello";
int count = text.Split(new[] { pattern }, StringSplitOptions.None).Length - 1;
Console.WriteLine(count); // Выводит 2

Этот метод прост, но имеет недостаток: он создает массив строк в памяти, что может быть неэффективно для больших строк. Кроме того, если подстрока находится в начале или конце, это все равно работает корректно.

2. Подсчет через Replace и разницу длин

Другой хитрый способ — заменить все вхождения подстроки на пустую строку и вычислить разницу в длине:

string text = "HelloHelloWorld";
string pattern = "Hello";
int count = (text.Length - text.Replace(pattern, "").Length) / pattern.Length;
Console.WriteLine(count); // Выводит 2

Этот метод не создает массив, но все равно создает новую строку при вызове Replace. Он работает только для непересекающихся вхождений (как и все стандартные методы). Для подсчета символов можно упростить:

char target = 'l';
int count = text.Length - text.Replace(target.ToString(), "").Length;
Console.WriteLine(count); // Выводит 4

Когда использовать эти методы?

Эти подходы полезны, когда вы не хотите подключать LINQ (например, в очень старых версиях .NET Framework) или когда нужно быстро написать код без дополнительных зависимостей. Однако помните о создании временных строк — для больших объемов данных это может быть медленнее, чем цикл или Regex.