Знаходження всіх позицій підрядка у більшому рядку в C #


82

У мене є великий рядок, який мені потрібно проаналізувати, і мені потрібно знайти всі екземпляри extract"(me,i-have lots. of]punctuationта зберегти індекс кожного у списку.

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

Я бавився і string.IndexOfробив майже те, що шукав, і написав певний код - але він не працює, і я не зміг зрозуміти, що саме не так:

List<int> inst = new List<int>();
int index = 0;
while (index < source.LastIndexOf("extract\"(me,i-have lots. of]punctuation", 0) + 39)
{
    int src = source.IndexOf("extract\"(me,i-have lots. of]punctuation", index);
    inst.Add(src);
    index = src + 40;
}
  • inst = Список
  • source = Великий рядок

Будь-які кращі ідеї?

Відповіді:


141

Ось приклад розширення методу для нього:

public static List<int> AllIndexesOf(this string str, string value) {
    if (String.IsNullOrEmpty(value))
        throw new ArgumentException("the string to find may not be empty", "value");
    List<int> indexes = new List<int>();
    for (int index = 0;; index += value.Length) {
        index = str.IndexOf(value, index);
        if (index == -1)
            return indexes;
        indexes.Add(index);
    }
}

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

List<int> indexes = "fooStringfooBar".AllIndexesOf("foo");

Для отримання додаткової інформації про методи розширення, http://msdn.microsoft.com/en-us/library/bb383977.aspx

Те ж саме за допомогою ітератора:

public static IEnumerable<int> AllIndexesOf(this string str, string value) {
    if (String.IsNullOrEmpty(value))
        throw new ArgumentException("the string to find may not be empty", "value");
    for (int index = 0;; index += value.Length) {
        index = str.IndexOf(value, index);
        if (index == -1)
            break;
        yield return index;
    }
}

8
Чому б не використовувати IEnumerable <int> і не повертати індекс віддачі замість списку індексів?
m0sa

2
@ m0sa: Гарна думка. Додано ще одну версію просто для задоволення.
Матті Вірккунен,

2
@ PedroC88: Використання yieldзробить код "лінивим". Він не буде збирати всі індекси в список пам’яті в межах методу. Який практичний ефект впливає на продуктивність, залежить від багатьох факторів.
Матті Вірккунен,

1
@Paul: "Не можна", як у "не повинен". Якщо вам не подобається формулювання, ви завжди можете запропонувати редагування, але я не думаю, що це так важко зрозуміти.
Матті Вірккунен,

10
Увага! Завдяки додаванню value.Lengthви можете пропустити вкладені збіги! Приклад: "Це тест на відповідність NestedNestedNested!" із зіставленням для "Вкладеного" буде знайдено лише один індекс, але не вкладений. Щоб це виправити, просто додайте +=1цикл замість +=value.Length.
Крістоф Мейснер

20

Чому б вам не використовувати вбудований клас RegEx:

public static IEnumerable<int> GetAllIndexes(this string source, string matchString)
{
   matchString = Regex.Escape(matchString);
   foreach (Match match in Regex.Matches(source, matchString))
   {
      yield return match.Index;
   }
}

Якщо вам потрібно повторно використовувати вираз, скомпілюйте його та десь кешуйте. Змініть параметр matchString на Regex matchExpression в іншому перевантаженні для випадку повторного використання.


Це не компілюється
Аншул

що це indexes? Це ніде не визначено.
Саджо

Шкода, це залишок. Видалити цей рядок.
csaam

2
Пам'ятайте, що цей метод має такий самий недолік, як прийнята відповідь. Якщо вихідний рядок "ccc", а шаблон "cc", він поверне лише одне входження.
user280498

15

за допомогою LINQ

public static IEnumerable<int> IndexOfAll(this string sourceString, string subString)
{
    return Regex.Matches(sourceString, subString).Cast<Match>().Select(m => m.Index);
}

2
Ти все-таки забув уникнути підрядка.
csaam

Це переважно прийнятому рішенню через нижчу цикломатичну складність.
Денні Джейкоб,

5

Полірована версія + корпус з ігноруванням підтримки:

public static int[] AllIndexesOf(string str, string substr, bool ignoreCase = false)
{
    if (string.IsNullOrWhiteSpace(str) ||
        string.IsNullOrWhiteSpace(substr))
    {
        throw new ArgumentException("String or substring is not specified.");
    }

    var indexes = new List<int>();
    int index = 0;

    while ((index = str.IndexOf(substr, index, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) != -1)
    {
        indexes.Add(index++);
    }

    return indexes.ToArray();
}

2

Це можна зробити за ефективної часової складності, використовуючи алгоритм KMP в O (N + M), де N - довжина, textа M - довжинаpattern .

Це реалізація та використання:

static class StringExtensions
{
    public static IEnumerable<int> AllIndicesOf(this string text, string pattern)
    {
        if (string.IsNullOrEmpty(pattern))
        {
            throw new ArgumentNullException(nameof(pattern));
        }
        return Kmp(text, pattern);
    }

    private static IEnumerable<int> Kmp(string text, string pattern)
    {
        int M = pattern.Length;
        int N = text.Length;

        int[] lps = LongestPrefixSuffix(pattern);
        int i = 0, j = 0; 

        while (i < N)
        {
            if (pattern[j] == text[i])
            {
                j++;
                i++;
            }
            if (j == M)
            {
                yield return i - j;
                j = lps[j - 1];
            }

            else if (i < N && pattern[j] != text[i])
            {
                if (j != 0)
                {
                    j = lps[j - 1];
                }
                else
                {
                    i++;
                }
            }
        }
    }

    private static int[] LongestPrefixSuffix(string pattern)
    {
        int[] lps = new int[pattern.Length];
        int length = 0;
        int i = 1;

        while (i < pattern.Length)
        {
            if (pattern[i] == pattern[length])
            {
                length++;
                lps[i] = length;
                i++;
            }
            else
            {
                if (length != 0)
                {
                    length = lps[length - 1];
                }
                else
                {
                    lps[i] = length;
                    i++;
                }
            }
        }
        return lps;
    }

і це приклад того, як ним користуватися:

static void Main(string[] args)
    {
        string text = "this is a test";
        string pattern = "is";
        foreach (var index in text.AllIndicesOf(pattern))
        {
            Console.WriteLine(index); // 2 5
        }
    }

Яка ефективність цього в порівнянні з оптимальним втіленням IndexOf, де індекс початку пошуку встановлюється в кінці попереднього збігу на кожній ітерації?
cesay

Порівняння IndexOf з AllIndicesOf є неправильним, оскільки їх вихідний результат відрізняється. Використання методу IndexOf у кожній ітерації значно збільшує складність часу до O (N ^ 2 M), тоді як оптимальна складність - O (N + M). KMP працює не так само, як наївний підхід, він використовує попередньо обчислений масив (LPS), щоб уникнути пошуку з самого початку. Рекомендуємо прочитати алгоритм KMP. Останні абзаци розділу "Довідка" у Вікіпедії пояснюють, як це працює в O (N).
M.Hhooryani

1
public List<int> GetPositions(string source, string searchString)
{
    List<int> ret = new List<int>();
    int len = searchString.Length;
    int start = -len;
    while (true)
    {
        start = source.IndexOf(searchString, start + len);
        if (start == -1)
        {
            break;
        }
        else
        {
            ret.Add(start);
        }
    }
    return ret;
}

Називайте це так:

List<int> list = GetPositions("bob is a chowder head bob bob sldfjl", "bob");
// list will contain 0, 22, 26

1

Привіт, приємна відповідь від @Matti Virkkunen

public static List<int> AllIndexesOf(this string str, string value) {
    if (String.IsNullOrEmpty(value))
        throw new ArgumentException("the string to find may not be empty", "value");
    List<int> indexes = new List<int>();
    for (int index = 0;; index += value.Length) {
        index = str.IndexOf(value, index);
        if (index == -1)
            return indexes;
        indexes.Add(index);
        index--;
    }
}

Але це стосується випадків тестів, таких як AOOAOOA, де підрядок

є AOOA і AOOA

Вихідні дані 0 і 3


1

Без регулярного виразу, використовуючи тип порівняння рядків:

string search = "123aa456AA789bb9991AACAA";
string pattern = "AA";
Enumerable.Range(0, search.Length)
   .Select(index => { return new { Index = index, Length = (index + pattern.Length) > search.Length ? search.Length - index : pattern.Length }; })
   .Where(searchbit => searchbit.Length == pattern.Length && pattern.Equals(search.Substring(searchbit.Index, searchbit.Length),StringComparison.OrdinalIgnoreCase))
   .Select(searchbit => searchbit.Index)

Це повертає {3,8,19,22}. Порожній шаблон відповідав би всім позиціям.

Для кількох шаблонів:

string search = "123aa456AA789bb9991AACAA";
string[] patterns = new string[] { "aa", "99" };
patterns.SelectMany(pattern => Enumerable.Range(0, search.Length)
   .Select(index => { return new { Index = index, Length = (index + pattern.Length) > search.Length ? search.Length - index : pattern.Length }; })
   .Where(searchbit => searchbit.Length == pattern.Length && pattern.Equals(search.Substring(searchbit.Index, searchbit.Length), StringComparison.OrdinalIgnoreCase))
   .Select(searchbit => searchbit.Index))

Це повертає {3, 8, 19, 22, 15, 16}


1

Теоретично @csam правильний, хоча його код не відповідає і може бути перероблений

public static IEnumerable<int> IndexOfAll(this string sourceString, string matchString)
{
    matchString = Regex.Escape(matchString);
    return from Match match in Regex.Matches(sourceString, matchString) select match.Index;
}

якщо його код був неправильним, ви могли б відредагувати його допис, щоб виправити його
cesay

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

це не гарна ідея використовувати регулярний вираз для великих рядків. Цей підхід забирає багато пам’яті.
W92,

1

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

    public static List<int> GetPositions(this string source, string searchString)
    {
        List<int> ret = new List<int>();
        int len = searchString.Length;
        int start = -1;
        while (true)
        {
            start = source.IndexOf(searchString, start +1);
            if (start == -1)
            {
                break;
            }
            else
            {
                ret.Add(start);
            }
        }
        return ret;
    }

0
public static Dictionary<string, IEnumerable<int>> GetWordsPositions(this string input, string[] Susbtrings)
{
    Dictionary<string, IEnumerable<int>> WordsPositions = new Dictionary<string, IEnumerable<int>>();
    IEnumerable<int> IndexOfAll = null;
    foreach (string st in Susbtrings)
    {
        IndexOfAll = Regex.Matches(input, st).Cast<Match>().Select(m => m.Index);
        WordsPositions.Add(st, IndexOfAll);

    }
    return WordsPositions;
}

-1

На основі коду, який я використовував для пошуку декількох екземплярів рядка в більшому рядку, ваш код буде виглядати так:

List<int> inst = new List<int>();
int index = 0;
while (index >=0)
{
    index = source.IndexOf("extract\"(me,i-have lots. of]punctuation", index);
    inst.Add(index);
    index++;
}

Тут є дві проблеми: По-перше, ви завжди додаєте -1 до свого списку результатів, що не є дійсним результатом. По-друге, код не закінчується через indexOfповернення -1 та index++. Я б використовував a while (true)з a, break;якщо результат IndexOfдорівнює -1.
b-pos465

-1

Я знайшов цей приклад і включив його у функцію:

    public static int solution1(int A, int B)
    {
        // Check if A and B are in [0...999,999,999]
        if ( (A >= 0 && A <= 999999999) && (B >= 0 && B <= 999999999))
        {
            if (A == 0 && B == 0)
            {
                return 0;
            }
            // Make sure A < B
            if (A < B)
            {                    
                // Convert A and B to strings
                string a = A.ToString();
                string b = B.ToString();
                int index = 0;

                // See if A is a substring of B
                if (b.Contains(a))
                {
                    // Find index where A is
                    if (b.IndexOf(a) != -1)
                    {                            
                        while ((index = b.IndexOf(a, index)) != -1)
                        {
                            Console.WriteLine(A + " found at position " + index);
                            index++;
                        }
                        Console.ReadLine();
                        return b.IndexOf(a);
                    }
                    else
                        return -1;
                }
                else
                {
                    Console.WriteLine(A + " is not in " + B + ".");
                    Console.ReadLine();

                    return -1;
                }
            }
            else
            {
                Console.WriteLine(A + " must be less than " + B + ".");
               // Console.ReadLine();

                return -1;
            }                
        }
        else
        {
            Console.WriteLine("A or B is out of range.");
            //Console.ReadLine();

            return -1;
        }
    }

    static void Main(string[] args)
    {
        int A = 53, B = 1953786;
        int C = 78, D = 195378678;
        int E = 57, F = 153786;

        solution1(A, B);
        solution1(C, D);
        solution1(E, F);

        Console.WriteLine();
    }

Повернення:

53, знайдене в позиції 2

78 знайдено в позиції 4
78 знайдено в позиції 7

57 - це не в 153786 році


1
Привіт Марк, я бачу, що ти новачок у stackoverflow. Ця відповідь нічого не додає до цього старого питання, вже є набагато кращі відповіді. Якщо ви будете відповідати на подібне запитання надалі, спробуйте пояснити, чому ваша відповідь містить певну інформацію чи значення, яке ще не існує в інших відповідях.
cesay
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.