【问题标题】:Get the index of the nth occurrence of a string?获取字符串第 n 次出现的索引?
【发布时间】:2010-09-16 05:52:18
【问题描述】:

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

我意识到我可以循环IndexOf 方法,方法是在循环的每次迭代中更新它的开始索引。但是这样做对我来说似乎很浪费。

【问题讨论】:

  • 我会为此使用正则表达式,然后您必须以最佳方式匹配字符串中的字符串。这是我们都应该尽可能使用的漂亮 DSL 之一。 An example 在 VB.net 中的代码与 C# 中的代码几乎相同。
  • 我会花很多钱在正则表达式版本上,它比“保持循环并执行简单的 String.IndexOf”要难得多。正则表达式有它们的位置,但不应该在存在更简单的替代方案时使用。

标签: c# .net string indexing indexof


【解决方案1】:

或者类似这样的 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;
    }

【讨论】:

    【解决方案2】:

    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
    

    【讨论】:

      【解决方案3】:

      System.ValueTuple ftw:

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

      写一个函数是功课

      【讨论】:

        【解决方案4】:

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

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

        【讨论】:

          【解决方案5】:

          您确实可以使用正则表达式/((s).*?){n}/ 来搜索第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 添加到原始解决方案中,以允许搜索对正则表达式引擎具有特殊意义的字符。

          【讨论】:

          • 你应该转义value吗?就我而言,我正在寻找一个点msdn.microsoft.com/en-us/library/…
          • 如果目标字符串包含换行符,则此正则表达式不起作用。你能修好吗?谢谢。
          • 如果没有第 N 个匹配项似乎会锁定。我需要将逗号分隔的值限制为 1000 个值,当 csv 的值较少时,这会挂起。所以@Yogesh - 可能不是一个很好的接受答案。 ;) 使用this answer 的变体(有一个字符串到字符串版本here)和changed the loop to stop at nth count
          • 尝试在\上搜索,传入的值为“\\”,匹配字符串在regex.match函数之前如下所示:(().*?){2}。我收到此错误:解析“(()。*?){2}” - 不够)。什么是正确查找反斜杠且不出错的格式?
          • 对不起,但有一个小小的批评:正则表达式解决方案不是最理想的,因为那时我必须第 n 次重新学习正则表达式。使用正则表达式时,代码本质上更难阅读。
          【解决方案6】:

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

          【讨论】:

            【解决方案7】:

            这基本上就是您需要做的 - 或者至少,这是最简单的解决方案。你“浪费”的只是 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)。
            【解决方案8】:

            这可能会做到:

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

            【讨论】:

            • 我不明白这将如何工作。您能否简要解释一下这是做什么的?
            【解决方案9】:
            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;
            }
            

            【讨论】:

            • 如果我没记错的话,如果要匹配的字符串从位置 0 开始,则此方法会失败,可以通过将 index 初始设置为 -1 来纠正此问题。
            • 您可能还想检查 null 或空字符串并匹配,否则它会抛出但这是一个设计决定。
            • 感谢@PeterMajeed - 如果"BOB".IndexOf("B") 返回0,那么IndexOfOccurence("BOB", "B", 1) 的这个函数也应该如此
            • 你的可能是最终的解决方案,因为它既有扩展功能,又避免了正则表达式和递归,这两者都会降低代码的可读性。
            • @tdyen 实际上,如果IndexOfOccurence 不检查s 是否为null,代码分析将发出"CA1062: Validate arguments of public methods"。如果matchnullString.IndexOf (String, Int32) 将抛出ArgumentNullException
            【解决方案10】:

            这基本上就是您需要做的——或者至少,这是最简单的解决方案。你“浪费”的只是 n 次方法调用的成本——如果你仔细想想,你实际上不会检查任何情况两次。 (IndexOf 将在找到匹配项后立即返回,并且您将从中断处继续。)

            【讨论】:

            • 我想你是对的,它似乎应该有一个内置的方法,但我敢肯定这是一个常见的情况。
            • 真的吗?我不记得在大约 13 年的 Java 和 C# 开发中曾经做过。这并不意味着我真的从来没有这样做过——只是不够经常记住。
            • 说到Java,我们有StringUtils.ordinalIndexOf()。具有所有 Linq 和其他出色功能的 C# 只是没有对此的内置支持。是的,如果您正在处理解析器和标记器,那么获得它的支持是非常必要的。
            • @Annie:你说“我们有”——你的意思是在 Apache Commons 中吗?如果是这样,您可以像为 Java 一样轻松地为 .NET 编写自己的第三方库……所以这并不是 Java 标准库所具有的 .NET 所没有的东西。当然,在 C# 中,您可以将其作为扩展方法添加到 string :)
            猜你喜欢
            • 1970-01-01
            • 1970-01-01
            • 2016-11-21
            • 2013-01-06
            • 1970-01-01
            • 2014-08-08
            • 1970-01-01
            • 1970-01-01
            相关资源
            最近更新 更多