获取字符串的第n次出现的索引?


100

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

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


我将为此使用正则表达式,然后必须以最佳方式在字符串中匹配字符串。这是我们所有人都应尽可能使用的精美DSL中的一种。VB.net中的一个示例代码在C#中几乎相同。
bovium

2
我会在正则表达式版本上花很多钱,比“保持循环并执行简单的String.IndexOf”要难得多。正则表达式有自己的位置,但是当存在更简单的替代方法时,则不应使用正则表达式。
乔恩·斯基特

Answers:


52

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


2
我想您是对的,不过似乎应该有一个内置方法,我敢肯定这是常见的情况。
PeteT

4
真?我不记得曾经在大约13年的Java和C#开发中做到这一点。这并不意味着我真的从来没有做过-只是不够经常记住。
乔恩·斯基特

说到Java,我们有StringUtils.ordinalIndexOf()。具有所有Linq和其他出色功能的C#,只是对此没有内置支持。是的,如果要处理解析器和令牌生成器,则必须提供支持。
安妮

3
@Annie:您说“我们有”-您是在Apache Commons中指的是吗?如果是这样,您可以像使用Java一样轻松地为.NET编写自己的第三方库...因此,这并不是Java标准库中.NET没有的。当然,在C#中,您可以在string:) 上将其添加为扩展方法:
Jon Skeet 2014年

108

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

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

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到原始解决方案中,以允许搜索对正则表达式引擎具有特殊含义的字符。


2
您应该逃脱value吗?在我来说,我一直在寻找一个点msdn.microsoft.com/en-us/library/...
russau

3
如果目标字符串包含换行符,则此Regex不起作用。你能解决吗?谢谢。
伊格纳西奥·索勒·加西亚

如果没有第N个比赛,似乎就锁定了。我需要将逗号分隔的值限制为1000个值,并且在csv较少时将其挂起。所以@Yogesh-可能不是一个很好的公认答案。;)使用此答案的变体(此处为字符串版本的字符串),并将循环更改为在第n个计数处停止
鲁芬2012年

尝试搜索\,传入的值为“ \\”,匹配字符串在regex.match函数之前看起来像这样:(()。*?){2}。我收到此错误:解析“((.. *?){2}”-不够)。正确查找反斜杠的格式是什么?
RichieMN 2014年

3
抱歉,但有一点批评:正则表达式解决方案不是最理想的,因为那样我就必须第n次重新学习正则表达式。使用正则表达式时,代码本质上更难阅读。
马克·罗杰斯

19

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

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

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)单元测试,可能会对您有所帮助(证明它是正确的):

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

我已经根据Weston的出色反馈(感谢Weston)更新了格式和测试用例。
Tod Thomson

14
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#中使用扩展方法

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

5
如果我没记错的话,如果要匹配的字符串从位置0开始,则此方法将失败,可以通过将index初始设置为-1 进行更正。
Peter Majeed 2012年

1
您可能还需要检查null或空字符串s是否匹配,否则将抛出异常,但这就是设计决定。

谢谢@PeterMajeed-如果"BOB".IndexOf("B")返回0,那么此函数应该用于IndexOfOccurence("BOB", "B", 1)
PeterX

2
您的解决方案可能是最终的解决方案,因为它既具有扩展功能,又避免了正则表达式和递归,这两者都使代码的可读性降低。
马克·罗杰斯

@tdyen实际上,如果不检查是否为,则代码分析将发出“ CA1062:验证公共方法的参数”。和String.IndexOf(字符串,Int32)将将抛出,如果是。IndexOfOccurencesnullArgumentNullExceptionmatchnull
DavidRR

1

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


1

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

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

1

System.ValueTuple ftw:

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

从中编写功能就是功课


0

Tod的答案可以稍微简化。

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
3
5
-1

0

或者使用do while循环这样的事情

 private static int OrdinalIndexOf(string str, string substr, int n)
    {
        int pos = -1;
        do
        {
            pos = str.IndexOf(substr, pos + 1);
        } while (n-- > 0 && pos != -1);
        return pos;
    }

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.