2008-10-09 27 views
94

Chyba że brakuje mi oczywistej wbudowanej metody, jaki jest najszybszy sposób na uzyskanie wystąpienia ciągu znaków w ciągu znaków?Uzyskaj indeks n-tego wystąpienia ciągu znaków?

Zdaję sobie sprawę, że mogę zapętlić metodę IndexOf aktualizując jej indeks początkowy w każdej iteracji pętli. Ale robienie tego w ten sposób wydaje mi się nierozważne.

+0

Podobne: http://stackoverflow.com/a/9908392/1305911 – JNF

+0

Używałbym wyrażeń regularnych do tego, to musisz optymalnie sposób dopasowywania ciągu znaków w ciągu znaków. To w jednym z pięknych DSL-ów, z których wszyscy powinniśmy korzystać, jeśli to możliwe. [Przykład] (http://www.regular-expressions.info/dotnet.html "Link") w VB.net kod jest prawie taki sam w C#. – bovium

+2

Chciałbym umieścić dobre pieniądze na wersji wyrażeń regularnych, które są znacznie trudniejsze do uzyskania, niż "zachować pętlę i robić proste String.IndexOf". Wyrażenia regularne mają swoje miejsce, ale nie powinny być używane, gdy istnieją prostsze alternatywy. –

Odpowiedz

51

To w zasadzie to, co musisz zrobić - a przynajmniej to najprostsze rozwiązanie. Wszystko, co "marnujesz", to koszt inwokacji metodą n - w rzeczywistości nie będziesz dwa razy sprawdzał przypadku, jeśli się nad tym zastanowisz. (IndexOf powróci, gdy tylko znajdzie dopasowanie, a będziesz kontynuował od miejsca, w którym zostało przerwane).

+2

Przypuszczam, że masz rację, wydaje się, że powinna istnieć metoda wbudowana, jestem pewien, że jest to zdarzenie commmon. – PeteT

+4

Naprawdę? Nie pamiętam, żebym musiał to zrobić od około 13 lat rozwoju języka Java i C#. To nie znaczy, że tak naprawdę nigdy nie musiałem tego robić - ale nie na tyle często, aby pamiętać. –

+0

Mówiąc o Javie, mamy 'StringUtils.ordinalIndexOf()'. C# z wszystkimi Linq i innymi wspaniałymi funkcjami, po prostu nie ma wbudowanej obsługi tego. I tak, to jest bardzo ważne, aby mieć jego wsparcie, jeśli masz do czynienia z parserami i tokenizatorami. – Annie

99

Naprawdę możesz użyć wyrażenia regularnego /((s).*?){n}/, aby wyszukać n-ty występowanie podła s.

W języku C# to może wyglądać następująco:

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

Uwaga: Dodałem Regex.Escape do oryginalnego rozwiązania, które umożliwi wyszukanie znaków, które mają szczególne znaczenie dla regex silnika.

+2

czy powinieneś uciekać z' wartości'? W moim przypadku szukałem kropki http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex.escape.aspx – russau

+1

Ten Regex nie działa, jeśli docelowy ciąg zawiera linebreaks. Czy możesz to naprawić? Dzięki. –

+0

Wygląda na zablokowane, jeśli nie ma N-tego meczu. Musiałem ograniczyć wartość oddzielaną przecinkami do 1000 wartości, a ta zawieszona, gdy csv miał mniej. Więc @ Yogesh - prawdopodobnie nie jest tak dobrze przyjętą odpowiedzią, jaka jest. ;) Korzystanie z wariantu [ta odpowiedź] (http://stackoverflow.com/a/6004505/1028230) (istnieje ciąg do wersji ciągów [tutaj] (http://stackoverflow.com/a/11773674/1028230)) i [zamiast tego zmienili pętlę, aby zatrzymać się po n-tej liczbie] (http://pastebin.com/w6aPDn3x). – ruffin

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

lub w C# z metod rozszerzenie

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

Jeśli się nie mylę, ta metoda nie powiedzie się, jeśli ciąg do dopasowania zaczyna się od pozycji 0, co można poprawić, ustawiając indeks "index" początkowo na -1. –

+1

Możesz również chcieć sprawdzić puste lub puste ciągi s i dopasować lub będzie rzutować, ale to jest decyzja projektowa. –

+0

Dzięki @PeterMajeed - jeśli '" BOB ".IndexOf (" B ")' zwraca 0, więc powinna to być funkcja dla 'IndexOfOccurence (" BOB "," B ", 1)' – PeterX

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

Oto rekurencyjna realizacja (wyzej idei) jako metodę rozszerzenia, dokładnie taki efekt formatu metody ramowej (ów):

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

Także tutaj są niektóre (MBUnit) testy jednostkowe które t pomóc (to udowodnić jest poprawna):

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

Zaktualizowałem formatowanie i przypadki testowe w oparciu o świetne opinie Westona (dzięki Weston). –

-3

Może to zrobić:

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

Nie widzę, jak to by działało. Czy możesz dołączyć krótkie wyjaśnienie tego, co to robi? –

1

Możliwe byłoby również miło pracować z Metodą String.Split() i sprawdzić, czy żądana występowanie jest w tablica, jeśli nie potrzebujesz indeksu, ale wartość na indeksie

Powiązane problemy