[c#] Finding all positions of substring in a larger string in C#

I have a large string I need to parse, and I need to find all the instances of extract"(me,i-have lots. of]punctuation, and store the index of each to a list.

So say this piece of string was in the beginning and middle of the larger string, both of them would be found, and their indexes would be added to the List. and the List would contain 0 and the other index whatever it would be.

I've been playing around, and the string.IndexOf does almost what I'm looking for, and I've written some code - but it's not working and I've been unable to figure out exactly what is wrong:

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 = The list
  • source = The large string

Any better ideas?

This question is related to c# .net asp.net string

The answer is


@csam is correct in theory, although his code will not complie and can be refractored to

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

Without Regex, using string comparison type:

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)

This returns {3,8,19,22}. Empty pattern would match all positions.

For multiple patterns:

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))

This returns {3, 8, 19, 22, 15, 16}


I found this example and incorporated it into a function:

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

Returns:

53 found at position 2

78 found at position 4
78 found at position 7

57 is not in 153786


Based on the code I've used for finding multiple instances of a string within a larger string, your code would look like:

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

using LINQ

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

I noticed that at least two proposed solutions don't handle overlapping search hits. I didn't check the one marked with the green checkmark. Here is one that handles overlapping search hits:

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

Why don't you use the built in RegEx class:

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

If you do need to reuse the expression then compile it and cache it somewhere. Change the matchString param to a Regex matchExpression in another overload for the reuse case.


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

It could be done in efficient time complexity using KMP algorithm in O(N + M) where N is the length of text and M is the length of the pattern.

This is the implementation and usage:

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

and this is an example of how to use it:

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

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

Call it like this:

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

Hi nice answer by @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--;
    }
}

But this covers tests cases like AOOAOOA where substring

are AOOA and AOOA

Output 0 and 3


Polished version + case ignoring support:

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

Examples related to c#

How can I convert this one line of ActionScript to C#? Microsoft Advertising SDK doesn't deliverer ads How to use a global array in C#? How to correctly write async method? C# - insert values from file into two arrays Uploading into folder in FTP? Are these methods thread safe? dotnet ef not found in .NET Core 3 HTTP Error 500.30 - ANCM In-Process Start Failure Best way to "push" into C# array

Examples related to .net

You must add a reference to assembly 'netstandard, Version=2.0.0.0 How to use Bootstrap 4 in ASP.NET Core No authenticationScheme was specified, and there was no DefaultChallengeScheme found with default authentification and custom authorization .net Core 2.0 - Package was restored using .NetFramework 4.6.1 instead of target framework .netCore 2.0. The package may not be fully compatible Update .NET web service to use TLS 1.2 EF Core add-migration Build Failed What is the difference between .NET Core and .NET Standard Class Library project types? Visual Studio 2017 - Could not load file or assembly 'System.Runtime, Version=4.1.0.0' or one of its dependencies Nuget connection attempt failed "Unable to load the service index for source" Token based authentication in Web API without any user interface

Examples related to asp.net

RegisterStartupScript from code behind not working when Update Panel is used You must add a reference to assembly 'netstandard, Version=2.0.0.0 No authenticationScheme was specified, and there was no DefaultChallengeScheme found with default authentification and custom authorization How to use log4net in Asp.net core 2.0 Visual Studio 2017 error: Unable to start program, An operation is not legal in the current state How to create roles in ASP.NET Core and assign them to users? How to handle Uncaught (in promise) DOMException: The play() request was interrupted by a call to pause() ASP.NET Core Web API Authentication Could not load file or assembly 'CrystalDecisions.ReportAppServer.CommLayer, Version=13.0.2000.0 WebForms UnobtrusiveValidationMode requires a ScriptResourceMapping for jquery

Examples related to string

How to split a string in two and store it in a field String method cannot be found in a main class method Kotlin - How to correctly concatenate a String Replacing a character from a certain index Remove quotes from String in Python Detect whether a Python string is a number or a letter How does String substring work in Swift How does String.Index work in Swift swift 3.0 Data to String? How to parse JSON string in Typescript