2008-10-09 3 views
94

Если у меня отсутствует очевидный встроенный метод, какой самый быстрый способ получить n -е вхождение строки в строку?Получить индекс n-го вхождения строки?

Я понимаю, что я мог бы зацикливать метод IndexOf, обновив свой индекс начала на каждой итерации цикла. Но делать это так кажется мне расточительным.

+0

похоже: http://stackoverflow.com/a/9908392/1305911 – JNF 2012-10-24 10:58:55

+0

Я бы использовал регулярные выражения для этого, тогда вам нужно оптимальное способ сопоставления строки внутри строки. Это в одной из прекрасных DSL, которые мы все должны использовать, когда это возможно. [Пример] (http://www.regular-expressions.info/dotnet.html «Ссылка») в VB.net код почти такой же, как на C#. – bovium 2008-10-09 11:09:59

+2

Я бы поместил хорошие деньги на версию регулярных выражений значительно сложнее, чем «продолжить цикл и сделать простой String.IndexOf». Регулярные выражения имеют свое место, но не должны использоваться, когда существуют более простые альтернативы. – 2008-10-09 12:06:10

ответ

51

Это в основном то, что вам нужно сделать - или, по крайней мере, это самое простое решение. Все, что вы «тратите впустую», это стоимость n вызовов метода - вы не будете проверять каждый случай дважды, если вы думаете об этом. (IndexOf вернется, как только он найдет совпадение, и вы продолжите движение от того места, где оно остановилось.)

+2

Я полагаю, ваше право, похоже, что должен быть встроенный метод, но я уверен, что это сообщение. – PeteT 2008-10-09 10:48:55

+4

Действительно? Я не могу вспомнить, когда-либо делал это примерно за 13 лет разработки Java и C#. Это не значит, что мне никогда не приходилось это делать - но этого недостаточно, чтобы помнить. – 2008-10-09 11:01:08

+0

Говоря о Java, мы имеем `StringUtils.ordinalIndexOf()`. C# со всеми Linq и другими замечательными функциями, просто не имеет встроенной поддержки для этого. И да, очень важно иметь поддержку, если вы имеете дело с парсерами и токенизаторами. – Annie 2014-03-21 10:22:06

99

Вы действительно можете использовать регулярное выражение /((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 к исходному раствору, чтобы искать символы, которые имеют особое значение для регулярных выражений.

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

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

Здесь рекурсивная реализация (указанной выше идеи) в качестве метода расширения, имитируя формат метода каркаса (ов):

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

Это может сделать это:

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

Может быть, это будет также хорошо работать с String.Split() метода и проверить, если запрашиваемая появление в массив, если вам не нужен индекс, но значение в индексе

Смежные вопросы