Як би ви порахували виникнення рядка (власне знака) в рядку?


864

Я щось роблю, коли зрозумів, що хочу порахувати, скільки /я можу знайти в рядку, і тоді мене вразило, що існує декілька способів зробити це, але я не міг вирішити, що найкраще (або найпростіше) .

На даний момент я збираюся з чимось на кшталт:

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;

34
+1: я мушу сказати, що це зовсім інший спосіб рахування. Я здивований результатам тестової оцінки :)
naveen

4
Це не так вже відрізняється ... це типовий спосіб реалізації цієї функції в SQL: LEN(ColumnToCheck) - LEN(REPLACE(ColumnToCheck,"N","")).
Шерідан

6
Власне кажучи, ви повинні розділити на "/".Length
Джерард

3
Чи можу я запитати, як би сказали ваші вимоги, що підрахунок має бути числом випадків "//" в межах "/////"? 2 чи 4?
Лес

1
використання регексу - це, мабуть, найкращий шлях для цього
Адам Хіггінс

Відповіді:


1009

Якщо ви використовуєте .NET 3.5, ви можете зробити це в одноколірній програмі з LINQ:

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

Якщо ви не хочете використовувати LINQ, ви можете зробити це за допомогою:

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

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

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

(Часи припадають на 50 000 000 ітерацій, тому навряд чи ви помітите велику різницю в реальному світі.)


6
Так, VS приховує методи розширення LINQ у класі рядків. Я здогадуюсь, що вони подумали, що розробники не хочуть, щоб усі ці методи розширення відображалися на строковому класі. Напевно, мудре рішення.
Іуда Габріель Хіманго

11
Можливо, така поведінка полягає в тому, що VS2010 автоматично включає System.Linq у файли нового класу, VS2008, мабуть, ні. Для роботи інтелігенції потрібно створити простір імен.
Sprague

30
Зауважте, що рішення Count and Split будуть працювати лише під час підрахунку символів. Вони не працюватимуть зі струнами, як це робить рішення ОП.
Пітер Ліллевольд

5
f == '\' йдеться про символи в рядку, а не про рядки в рядку
Томас Веллер

9
Це виглядає як відповідь на інше запитання: "Як би ви порахували виникнення знаку в рядку?"
Бен Аронсон

181
string source = "/once/upon/a/time/";
int count = 0;
foreach (char c in source) 
  if (c == '/') count++;

Доводиться швидше, ніж сам source.Replace()по собі.


18
Ви можете досягти незначного покращення, перейшовши на «а», а не на передбачення, але лише крихітний, крихітний шматочок.
Марк

17
Ні. Питання задає підрахунок виникнення рядка, а не символу.
YukiSakura

3
Це підрахунок символів у рядку. Назва про підрахунок рядків у рядку
Томас Веллер

2
@Mark Просто перевірив це на циклі for, і це було насправді повільніше, ніж використання foreach. Можливо, через перевірку меж? (Час становив 1,65 сек проти 2,05 за 5-ти мільйонних ітерацій.)
Вимірювання

4
Хоча питання задає рядок у рядку, приклад проблеми ОП, розміщеної насправді, є лише одним символом, і в такому випадку я б назвав цю відповідь все-таки правильним рішенням, оскільки це показує кращий спосіб (пошук пошуку замість пошуку рядків) для вирішення проблеми.
Чад

136
int count = new Regex(Regex.Escape(needle)).Matches(haystack).Count;

8
+1 - у деяких випадках ви можете додати RegexOptions.IgnoreCase.
TrueWill

3
це не неймовірно низько?
Томас Аюб

4
Накладні витрати Regex не є ідеальним, плюс "я не дуже хочу копати RegEx для цього, чи не так?"
Чад

можливо, не захочеться Regex.Escape(...)такnew System.Text.RegularExpressions.Regex(needle).Matches(haystack).Count;
барлоп

2
Я пішов з цим, тому що він може шукати рядки, а не лише символи.
Джеймс в Інді

86

Якщо ви хочете мати можливість шукати цілі рядки, а не лише символи:

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

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


1
Не впевнений, як я можу пояснити це більш чітко, ніж наведений опис. Що бентежить?
mqp

58
СУПЕР СЛІД! Спробував це на сторінці html, і це зайняло близько 2 хвилин, порівняно з іншими методами на цій сторінці, що займало 2 секунди. Відповідь була правильною; це було занадто повільно, щоб бути корисним.
ДжонБ

2
погодився, занадто повільно. Я великий прихильник рішень у стилі linq, але це просто не під силу.
Sprague

5
Зауважимо, що причина настільки повільна, що вона створює n рядків, виділяючи таким чином приблизно n ^ 2/2 байти.
Пітер Кребтрі

6
OutOfMemoryException викинуто на мої 210000 символів рядка.
ендер

66

Я провів деякі дослідження і виявив, що рішення Річарда Уотсона в більшості випадків найшвидше. Це таблиця з результатами кожного рішення в публікації (крім тих, хто використовує 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;
    }
}

Я збирався додати власне рішення "низького рівня" (не створюючи підрядки, використовуючи заміну / розділення або будь-який Regex / Linq), але ваш, можливо, навіть кращий, ніж мій (і принаймні коротший). Дякую!
Dan W

Для розчинів Regex додайте вRegex.Escape(needle)
Тимін

2
Просто для того, щоб вказати на інших, значення пошуку потрібно перевірити, якщо воно порожнє, інакше ви потрапите в нескінченний цикл.
WhoIsRich

2
Можливо, це лише я, але оскільки source="aaa" substring="aa"я очікував отримати 2, а не 1. Щоб "виправити" це, перейдіть n += substring.Lengthнаn++
ytoledano

Ви можете додати overlappedпрапор, щоб відповідати вашому випадку так:overlapped=True;.... if(overlapped) {++n;} else {n += substring.Length;}
tsionyx

54

LINQ працює у всіх колекціях, а оскільки рядки - це лише сукупність персонажів, як щодо цього приємного маленького однолітка:

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

Переконайтеся, що у вас є using System.Linq;в коді файл коду, як .Countце метод розширення з цього простору імен.


5
Чи справді варто використовувати var там? Чи є ймовірність, що графа замінять те, що не повертає інт?
Whatsit

69
@Whatsit: ви можете набрати 'var' лише лівою рукою, тоді як 'int' вимагає обох рук;)
Шон Яскравий

7
intвсі букви містяться в домашніх клавішах, а varні. е .. зачекайте, я використовую Дворак
Майкл Буен

2
@BDotA Переконайтеся, що у вас є "за допомогою System.Linq;" вгорі файлу. Також intellisense може приховати від вас дзвінок .Count, оскільки він є рядком. Незважаючи на це, вона складеться і працюватиме чудово.
Іуда Габріель Хіманго

3
@JudahGabrielHimango Я б заперечував, що var слід застосовувати особливо, коли тип змінної очевидний (а також для стислості та послідовності)
EriF89,

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

Мені було цікаво, чому це могло бути і гуглінг навколо (я пригадую щось про те, що зворотна ітерація була швидшою), і я зіткнувся з цим питанням ТАК, який набридливо використовує рядок для техніки char []. Я думаю, що зворотний трюк є новим у цьому контексті.

Який найшвидший спосіб перебрати окремі символи в рядку на C #?


1
Ви можете поставити source.IndexOf('/', n + 1)і втратити n++і дужки в той час :) Крім того, поставте змінну string word = "/"замість символу.
neeKo

1
Привіт Ніко, перевіри нові відповіді. Хоча може бути важче зробити підрядку змінної довжини.
Річард Уотсон

Я використав щось подібне, перейшовши через підряд; це поки я не зрозумів, що indexOf має startIndex. Найбільше мені подобається перше рішення, оскільки це хороший баланс між швидкістю та слідом пам’яті.
Самір Банянович

1
Я десь читав, що швидше повторити назад, тому що швидше порівняти значення з 0
reggaeguitar

1
@shitpoet yup. Якщо ви подивитеся на базовий код, це рідний дзвінок. public char [] toCharArray () {... System.arraycopy (значення, 0, результат, 0, value.length); ...}
Річард Уотсон

46

Вони працюють лише для однозначних пошукових термінів ...

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

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

може виявитися кращим для довших голок ...

Але має бути і більш елегантний спосіб. :)


Для обліку багатосимвольних замін. Без нього підрахунок "тест" в "ключ є" повернеться 6.
ZombieSheep

Визначення та порівняння цього з string.Split-way - працює приблизно в 1,5 рази швидше. Кудос.
Алекс

20

Редагувати:

source.Split('/').Length-1

2
Це я і роблю. І source.Split(new[]{"//"}, StringSplitOptions.None).Count - 1для розділових знаків.
bzlm

4
Це дозволило б виконати принаймні n струнових виділень на купі, плюс (можливо) декілька повторних розмірів масиву - і все це лише для отримання рахунку? Надзвичайно неефективний, не масштабує добре і ніколи не повинен використовуватися у будь-якому важливому коді.
Зар Шардан

17

У C # хороший лічильник String SubString - це несподівано хитрий хлопець:

public static int CCount(String haystack, String needle)
{
    return haystack.Split(new[] { needle }, StringSplitOptions.None).Length - 1;
}

1
Приємне рішення - і робота для струни теж (не тільки char)!
ChriPf

Дякую, надто просто забути деякі тонкощі обробці рядків під час обміну мовами - як і більшість із нас до цих пір!
Дейв

1
-1 бо: Чи знаєте ви різницю між Count () та Count чи Long? Якщо хтось використовує Count () замість Count чи Length, я спрацьовує. Count () створює IEnumerator, тоді він проходить через усі виникнення IEnumerable, тоді як Count або Length вже встановлені властивості об'єкта, які вже містять потрібний підрахунок, без необхідності перебирати всі елементи.
аерозон

Добре місце, і що дивно - це те, що в моїй бібліотеці, звідки я взяв функцію, я використовую "Довжина". Відредаговано!
Дейв

15
Regex.Matches(input,  Regex.Escape("stringToMatch")).Count

1
Це невірно, якщо вхід містить спеціальні символи з регулярними виразами, тобто | Там повинен бути Regex.Escape (вхід)
Есбен Сков Педерсен

1
Насправді stringToMatchпотреби втечі, а не їхні input.
Теодор Зуліяс

Право ти є. Виправлено це.
cederlof

13
private int CountWords(string text, string word) {
    int count = (text.Length - text.Replace(word, "").Length) / word.Length;
    return count;
}

Оскільки оригінальне рішення було найшвидшим для символів, я думаю, воно також буде для струн. Тож ось мій внесок.

Для контексту: я шукав слова типу "не вдалося" та "вдалося" у файлі журналу.

Гр, Бен


2
Просто не передайте порожню рядок для змінної "word" (поділ на нульову помилку).
Ендрю Єнс

12
string s = "65 fght 6565 4665 hjk";
int count = 0;
foreach (Match m in Regex.Matches(s, "65"))
  count++;

20
або Regex.Matches (s, "65"). Розрахунок ^ _ ^
Мета

Працює не для кожного рядка. Спробуйте пошукати "++" у "abc ++ def ++ xyz"
-wiggle

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

Чи не буде другим методом бум-буму, якщо передана рядок є нульовою або порожньою? З точки зору стилю, що ви визначаєте як вкладка System.String, а не просто рядок?
Нодоїд

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

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;

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

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


3

Я відчував, що нам бракує певних видів підрахунку підрядків, як-от небезпечні порівняння бай-байта. Я зібрав метод оригінального плаката та будь-які методи, про які я міг придумати.

Це розширення для рядків, які я зробив.

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" шукає рядок для '_', "і" шукає рядок для "і", а "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

Тож мій четвертий метод, безумовно, переможець, але, реально, якщо файл з 3,6 мільйона символів у 100 разів сприймав лише 1586 мс як гірший випадок, то все це зовсім незначно.

До речі, я також просканував графу 'd' у файлі символів 3,6 мільйона методами CountSubstr та CountChar у 100 разів. Результати ...

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

Оригінальний метод плакатів дуже поганий для одиночних голок у великому стогу сіна відповідно до цього.

Примітка: Усі значення були оновлені до виходу версії версії. Я випадково забув побудувати в режимі випуску, коли перший раз опублікував це. Деякі мої заяви були внесені зміни.


Дякую за результативність. Різниця факторів у швидкості 10 може стати причиною того, щоб не розглянути linq чи інше акуратно написане рішення, а йти методом розширення.
Андреас

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
Це створює ВЕЛИЧЕЗНУ кількість тимчасових рядків і змушує роботу сміттєзбірника дуже важко.
EricLaw

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

Різниця у відповіді Річарда Уотсона, трохи швидше з підвищенням ефективності, тим більше разів трапляється знак у рядку та менше коду!

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

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

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

Потрібно зробити щось подібне для тестування умовних висловлювань з рядка.

Замінив те, що я шукав, з одним символом і підрахував екземпляри одного символу.

Очевидно, що один символ, який ви використовуєте, потрібно перевірити, щоб він не існував у рядку, перш ніж це станеться, щоб уникнути неправильних підрахунків.


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

Моя початкова позиція дала мені щось на кшталт:

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 двозначних рядків, ось час, оновлений до одних і тих же зразків текстів:

Голка в копиці сіна (версія ОП): 7,8 секунди

Запропонований механізм: 4,6 секунди.

Редагування 3: Додавши кутовий регістр з одним символом, він пішов на 1,2 секунди.

Правка 4: Для контексту: використано 50 мільйонів ітерацій.


2

Думав, що я кину свій метод розширення на ринг (див. Коментарі для отримання додаткової інформації). Я не робив жодної формальної розмітки на лавці, але думаю, що для більшості сценаріїв це повинно бути дуже швидким.

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

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

    /// <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's 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's 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
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");

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

Якщо ви переглянете цю веб-сторінку , орієнтоване 15 різних способів, включаючи використання паралельних циклів.

Найшвидшим способом, як видається, є або використання однієї різьби для циклу (якщо у вас .Net версія <4.0), або paralel.for циклу (якщо використовується .Net> 4.0 з тисячами перевірок).

Якщо припустимо, що "ss" - це ваша пошукова рядок, "ch" - це ваш масив символів (якщо у вас є кілька знаків, які ви шукаєте), ось основна суть коду, який мав найшвидший час виконання однієї нитки:

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

Базовий вихідний код надається занадто, щоб ви могли запускати власні тести.


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 в цьому"; строкова строкація = "foo"; string [] strarray = str.Split (''); Array.Sort (strarray); str = ""; for (int i = 0; i <strarray.Length - 1; i ++) {if (strarray [i] == stroccurance) {count ++; }} str = "Кількість виникнення для" + строктура + "є" + кількість; Через це ви можете порахувати будь-яку строкову частоту в цьому прикладі, я рахую похибку "foo", і це дасть мені вихід 3.
Narendra Kumar

1

Що стосується роздільника рядків (не для випадку char, як каже тематика):
string source = "@@@ Once @@@ at @@@ a @@@ time @@@";
int count = source.Split (new [] {"@@@"}, StringSplitOptions.RemoveEmptyEntries) .Length - 1;

Первісне значення оригіналу для початкового плану ("/ один раз / після / час /") є символом "/", а відповіді пояснюють джерело. Параметр "char [])", хоча ...


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.