Как посчитать количество вхождений символа или подстроки в строке в C#?
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.