Как подсчитать количество вхождений символа или подстроки в строке в C#?
Использование LINQ и встроенных методов для подсчета вхождений
В C# есть несколько элегантных способов подсчета вхождений символов и подстрок. Самый простой и читаемый — использовать LINQ (Language Integrated Query) для символов и метод Split для подстрок. Давайте разберем оба подхода.
1. Подсчет вхождений символа с помощью LINQ
LINQ предоставляет метод Count(), который можно применить к строке как к коллекции символов. Это гораздо короче и понятнее, чем ручной цикл:
string text = "Hello World, Hello C#";
char target = 'l';
int count = text.Count(c => c == target);
Console.WriteLine($"Символ '{target}' встречается {count} раз(а)"); // Вывод: 4
Метод Count() принимает лямбда-выражение, которое проверяет каждый символ. Это работает быстро и чисто.
2. Подсчет вхождений подстроки с помощью Split
Для подсчета вхождений подстроки (например, "Hello") можно использовать комбинацию Split и подсчета получившихся частей. Метод Split разбивает строку по указанному разделителю, а количество элементов в массиве минус 1 даст количество вхождений:
string text = "Hello World, Hello C#";
string substring = "Hello";
int count = text.Split(new string[] { substring }, StringSplitOptions.None).Length - 1;
Console.WriteLine($"Подстрока '{substring}' встречается {count} раз(а)"); // Вывод: 2
Обратите внимание: мы передаем StringSplitOptions.None, чтобы сохранить пустые строки, если подстрока находится в начале или конце. Если вы хотите игнорировать пустые записи, используйте StringSplitOptions.RemoveEmptyEntries, но тогда формула Length - 1 может дать неверный результат.
3. Более точный метод: IndexOf в цикле
Для сложных случаев (например, когда нужно учитывать перекрывающиеся вхождения или регистр) лучше использовать метод IndexOf в цикле. Он последовательно находит каждое вхождение:
string text = "Hello World, Hello C#";
string substring = "Hello";
int count = 0;
int index = 0;
while ((index = text.IndexOf(substring, index, StringComparison.OrdinalIgnoreCase)) != -1)
{
count++;
index += substring.Length; // Сдвигаемся на длину подстроки, чтобы не учитывать перекрытия
}
Console.WriteLine($"Подстрока '{substring}' встречается {count} раз(а)"); // Вывод: 2
Здесь StringComparison.OrdinalIgnoreCase позволяет игнорировать регистр (например, "hello" и "Hello" будут считаться одинаковыми). Если нужен учет регистра, используйте StringComparison.Ordinal.
4. Использование Regex для сложных шаблонов
Если вам нужно подсчитать вхождения по сложному шаблону (например, все слова, начинающиеся с определенной буквы), используйте регулярные выражения:
using System.Text.RegularExpressions;
string text = "Hello World, Hello C#";
string pattern = @"\bHello\b"; // Только целое слово "Hello"
int count = Regex.Matches(text, pattern).Count;
Console.WriteLine($"Целое слово 'Hello' встречается {count} раз(а)"); // Вывод: 2
Сводка рекомендаций
- Для одного символа — используйте
text.Count(c => c == target)(проще всего). - Для простой подстроки —
text.Split(...).Length - 1(быстро, но не подходит для перекрывающихся вхождений). - Для точного подсчета с учетом регистра или перекрытий — цикл с
IndexOf. - Для сложных шаблонов —
Regex.Matches.
Все эти методы встроены в .NET и не требуют дополнительных библиотек.
<<Альтернативный подход: использование Span и ручного перебора
Если вы работаете с очень большими строками (например, логами размером в гигабайты) и хотите максимальной производительности, можно использовать ReadOnlySpan<char> и ручной перебор. Это позволяет избежать выделения памяти, как в случае с Split или Regex.
Пример для подсчета вхождений подстроки без учета регистра:
string text = "Hello World, Hello C#";
ReadOnlySpan<char> span = text.AsSpan();
ReadOnlySpan<char> target = "Hello".AsSpan();
int count = 0;
int index = 0;
while ((index = span.Slice(index).IndexOf(target, StringComparison.OrdinalIgnoreCase)) != -1)
{
count++;
index += target.Length;
}
Console.WriteLine($"Подстрока 'Hello' встречается {count} раз(а)");
Этот метод работает быстрее на больших данных, так как не создает новые строки или массивы. Однако он менее читаем и подходит только для опытных разработчиков, которым важна производительность. Для большинства повседневных задач достаточно LINQ или Split.
Ответов пока нет.
Войдите чтобы оставить ответ