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


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.