Как бы вы посчитали вхождения строки (на самом деле символ) в строке? - PullRequest
775 голосов
/ 12 февраля 2009

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

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

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;

Ответы [ 30 ]

904 голосов
/ 12 февраля 2009

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

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

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

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

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

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

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

164 голосов
/ 12 февраля 2009
string source = "/once/upon/a/time/";
int count = 0;
foreach (char c in source) 
  if (c == '/') count++;

Должен быть быстрее самого source.Replace().

127 голосов
/ 10 декабря 2010
int count = new Regex(Regex.Escape(needle)).Matches(haystack).Count;
83 голосов
/ 12 февраля 2009

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

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

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

61 голосов
/ 02 августа 2012

Я провел некоторое исследование и обнаружил, что Решение Ричарда Уотсона является самым быстрым в большинстве случаев. Это таблица с результатами каждого решения в посте (кроме тех, которые используют 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;
    }
}
53 голосов
/ 12 февраля 2009

LINQ работает во всех коллекциях, а поскольку строки - это просто коллекция символов, как насчет этой милой строчки:

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

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

47 голосов
/ 15 мая 2011
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++;
}

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

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

45 голосов
/ 12 февраля 2009

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

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

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

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

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

19 голосов
/ 12 февраля 2009

Изменить:

source.Split('/').Length-1
14 голосов
/ 19 июня 2013
Regex.Matches(input,  Regex.Escape("stringToMatch")).Count
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...