关于c#:获取字符串第n次出现的索引?

Get the index of the nth occurrence of a string?

除非缺少明显的内置方法,否则在字符串中获取字符串的第n次出现的最快方法是什么?

我意识到我可以通过在每次循环迭代时更新其开始索引来循环IndexOf方法。 但是这样做对我来说似乎是浪费。


您确实可以使用正则表达式/((s).*?){n}/搜索子字符串s的第n次出现。

在C#中,它可能看起来像这样:

1
2
3
4
5
6
7
8
9
10
11
12
public static class StringExtender
{
    public static int NthIndexOf(this string target, string value, int n)
    {
        Match m = Regex.Match(target,"((" + Regex.Escape(value) +").*?){" + n +"}");

        if (m.Success)
            return m.Groups[2].Captures[n - 1].Index;
        else
            return -1;
    }
}

注意:我在原始解决方案中添加了Regex.Escape,以允许搜索对正则表达式引擎具有特殊含义的字符。


这基本上就是您需要做的-至少,这是最简单的解决方案。您要"浪费"的只是n个方法调用的成本-如果您考虑一下,您实际上将不会两次检查任何一种情况。 (IndexOf将在找到匹配项后立即返回,并且您将继续从中断处继续前进。)


That's basically what you need to do - or at least, it's the easiest solution. All you'd be"wasting" is the cost of n method invocations - you won't actually be checking any case twice, if you think about it. (IndexOf will return as soon as it finds the match, and you'll keep going from where it left off.)

这是作为扩展方法的(上述想法的)递归实现,模仿了框架方法的格式:

1
2
3
4
5
6
7
8
9
10
11
12
public static int IndexOfNth(this string input,
                             string value, int startIndex, int nth)
{
    if (nth < 1)
        throw new NotSupportedException("Param 'nth' must be greater than 0!");
    if (nth == 1)
        return input.IndexOf(value, startIndex);
    var idx = input.IndexOf(value, startIndex);
    if (idx == -1)
        return -1;
    return input.IndexOfNth(value, idx + 1, --nth);
}

此外,以下一些(MBUnit)单元测试可能会帮助您(证明它是正确的):

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
using System;
using MbUnit.Framework;

namespace IndexOfNthTest
{
    [TestFixture]
    public class Tests
    {
        //has 4 instances of the
        private const string Input ="TestTest";
        private const string Token ="Test";

        /* Test for 0th index */

        [Test]
        public void TestZero()
        {
            Assert.Throws<NotSupportedException>(
                () => Input.IndexOfNth(Token, 0, 0));
        }

        /* Test the two standard cases (1st and 2nd) */

        [Test]
        public void TestFirst()
        {
            Assert.AreEqual(0, Input.IndexOfNth("Test", 0, 1));
        }

        [Test]
        public void TestSecond()
        {
            Assert.AreEqual(4, Input.IndexOfNth("Test", 0, 2));
        }

        /* Test the 'out of bounds' case */

        [Test]
        public void TestThird()
        {
            Assert.AreEqual(-1, Input.IndexOfNth("Test", 0, 3));
        }

        /* Test the offset case (in and out of bounds) */

        [Test]
        public void TestFirstWithOneOffset()
        {
            Assert.AreEqual(4, Input.IndexOfNth("Test", 4, 1));
        }

        [Test]
        public void TestFirstWithTwoOffsets()
        {
            Assert.AreEqual(-1, Input.IndexOfNth("Test", 8, 1));
        }
    }
}


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
private int IndexOfOccurence(string s, string match, int occurence)
{
    int i = 1;
    int index = 0;

    while (i <= occurence && (index = s.IndexOf(match, index + 1)) != -1)
    {
        if (i == occurence)
            return index;

        i++;
    }

    return -1;
}

或在C#中使用扩展方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static int IndexOfOccurence(this string s, string match, int occurence)
{
    int i = 1;
    int index = 0;

    while (i <= occurence && (index = s.IndexOf(match, index + 1)) != -1)
    {
        if (i == occurence)
            return index;

        i++;
    }

    return -1;
}


System.ValueTuple ftw:

var index = line.Select((x, i) => (x, i)).Where(x => x.Item1 == '"').ElementAt(5).Item2;

从中编写功能就是功课


经过一些基准测试,这似乎是最简单,最有效的解决方案

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public static int IndexOfNthSB(string input,
             char value, int startIndex, int nth)
        {
            if (nth < 1)
                throw new NotSupportedException("Param 'nth' must be greater than 0!");
            var nResult = 0;
            for (int i = startIndex; i < input.Length; i++)
            {
                if (input[i] == value)
                    nResult++;
                if (nResult == nth)
                    return i;
            }
            return -1;
        }


也许使用String.Split()方法并检查所请求的事件是否在数组中(如果您不需要索引,但需要索引处的值)也很好


Tod的答案可以稍微简化。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
using System;

static class MainClass {
    private static int IndexOfNth(this string target, string substring,
                                       int seqNr, int startIdx = 0)
    {
        if (seqNr < 1)
        {
            throw new IndexOutOfRangeException("Parameter 'nth' must be greater than 0.");
        }

        var idx = target.IndexOf(substring, startIdx);

        if (idx < 0 || seqNr == 1) { return idx; }

        return target.IndexOfNth(substring, --seqNr, ++idx); // skip
    }

    static void Main () {
        Console.WriteLine ("abcbcbcd".IndexOfNth("bc", 1));
        Console.WriteLine ("abcbcbcd".IndexOfNth("bc", 2));
        Console.WriteLine ("abcbcbcd".IndexOfNth("bc", 3));
        Console.WriteLine ("abcbcbcd".IndexOfNth("bc", 4));
    }
}

输出量

1
2
3
4
1
3
5
-1

这可以做到:

1
Console.WriteLine(str.IndexOf((@"")+2)+1);