在C#中查找较大字符串中子字符串的所有位置


79

我有一个大字符串需要解析,我需要找到的所有实例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 =大字串

还有更好的主意吗?

Answers:


137

这是一个示例扩展方法:

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/zh-cn/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;
    }
}

7
为什么不使用IEnumerable <int>和yield return索引而不是索引列表?
m0sa

1
@ m0sa:好点。添加了另一个版本,只是为了好玩。
Matti Virkkunen 2010年

2
@ PedroC88:使用yield将使代码“懒惰”。它不会将所有索引收集到该方法的内存列表中。对性能产生何种实际影响取决于许多因素。
Matti Virkkunen

1
@Paul:“不得”,如“不得”。如果您不喜欢这样的措辞,可以随时提出修改建议,但是我认为这并不难理解。
Matti Virkkunen

10
注意!由于添加,value.Length您可能会错过嵌套匹配!示例:“这是NestedNestedNested匹配测试!” 与“ NestedNested”匹配的索引只会找到一个索引,而不是嵌套的索引。要解决此问题,只需添加+=1in循环而不是+=value.Length
ChristophMeißner'17

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。


无法编译
-Anshul

是什么indexes?它没有在任何地方定义。
萨焦2016年

不好,这是残余。删除该行。
csaam '16

2
请注意,此方法与接受的答案具有相同的缺陷。如果您的源字符串为“ ccc”,模式为“ cc”,则它将仅返回一次。
user280498

14

使用LINQ

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

2
但是您忘记了转义subString。
csaam'4

这比公认的解决方案更好,因为它的循环复杂度较低。
Denny Jacob

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

可以使用O(N + M)中的KMP算法以高效的时间复杂度完成此操作,其中N是的长度,textM是的长度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实现相比,此性能如何?在最佳实现中,搜索起始索引设置为每次迭代的上一个匹配项的结尾?
caesay

将IndexOf与AllIndicesOf进行比较是不正确的,因为它们的输出不同。在每次迭代中使用IndexOf方法,将时间复杂度极大地增加到O(N ^ 2 M),而最佳复杂度是O(N + M)。KMP与天真的方法不同,它使用预先计算的数组(LPS)来避免从头开始搜索。建议您阅读KMP算法。Wikipedia中“背景”部分的最后几节说明了它在O(N)中的工作方式。
M.Khooryani

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

如果不使用Regex,则使用字符串比较类型:

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

如果他的代码不正确,您可以编辑他的帖子以进行更正
caesay 2012年

我没有注意到。我不得不承认自己不愿意这样做,以防万一我错了,尽管我不认为自己是错的。
arame3333

对于大字符串使用正则表达式不是一个好主意。该方法占用大量内存。
2013年

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++。我会用 while (true)一个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找到

153786中没有57


1
嗨,马克,我发现您是stackoverflow的新手。这个答案不会给这个老问题增加任何东西,已经有了更好的答案。如果以后再回答这样的问题,请尝试解释为什么您的回答包含某些信息或价值,而这些信息或价值在其他答案中尚不存在。
caesay
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.