2008-10-09 6 views
94

Wenn ich keine offensichtliche integrierte Methode vermisse, was ist der schnellste Weg, um das Auftreten einer Zeichenkette innerhalb einer Zeichenkette zu erreichen?Erhalten Sie den Index des n. Auftretens eines Strings?

Ich realisiere, dass ich die IndexOf Methode durch Aktualisieren des Startindex bei jeder Iteration der Schleife loopen könnte. Aber so zu handeln, scheint mir verschwenderisch.

+0

Ähnlich: http://StackOverflow.com/A/9908392/1305911 – JNF

+0

Ich würde einen regulären Ausdrücke dafür verwenden, dann müssen Sie optimal Möglichkeit, die Zeichenfolge innerhalb der Zeichenfolge abzugleichen. Dies in einem der schönen DSLs, die wir alle benutzen sollten, wenn es möglich ist. [Ein Beispiel] (http://www.regular-expressions.info/dotnet.html "Link") in VB.net ist der Code in C# fast identisch. – bovium

+2

Ich würde gutes Geld auf die Version mit regulären Ausdrücken setzen, die wesentlich schwieriger ist, richtig zu machen als "keep looping and doing simple String.IndexOf". Reguläre Ausdrücke haben ihren Platz, sollten aber nicht verwendet werden, wenn einfachere Alternativen existieren. –

Antwort

51

Das ist im Grunde, was Sie tun müssen - oder zumindest ist es die einfachste Lösung. Alles, was Sie "verschwenden" würden, sind die Kosten von n Methodenaufrufen - Sie werden tatsächlich keinen Fall zweimal überprüfen, wenn Sie darüber nachdenken. (IndexOf zurückkehren wird, sobald es die Übereinstimmung findet, und Sie halten gehen, wo er unterbrochen wird.)

+2

Ich nehme an, Ihr Recht, es scheint, als ob es eine eingebaute Methode geben sollte, ich bin mir sicher, dass es ein gemeinsames Vorkommen ist. – PeteT

+4

Wirklich? Ich kann mich nicht erinnern, jemals in etwa 13 Jahren Java und C# Entwicklung gemacht zu haben. Das bedeutet nicht, dass ich es wirklich nie tun musste - aber nicht oft genug, um mich daran zu erinnern. –

+0

Apropos Java, wir haben 'StringUtils.ordinalIndexOf()'. C# mit allen Linq und anderen wunderbaren Funktionen, hat einfach keine eingebaute Unterstützung dafür. Und ja, es ist sehr wichtig, Unterstützung zu haben, wenn Sie mit Parsern und Tokenizern zu tun haben. – Annie

99

Sie wirklich den regulären Ausdruck /((s).*?){n}/ verwenden könnten für n-ten Auftreten des Teils suchen s.

In C# es könnte wie folgt aussehen:

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

Hinweis: Ich habe Regex.Escape zur ursprünglichen Lösung gegeben Zeichen zu ermöglichen suchen, die eine besondere Bedeutung zu Regex-Engine haben.

+2

sollten Sie den' Wert' umgehen? In meinem Fall suchte ich nach einem Punkt http://msdn.microsoft.com/en-us/library/system.text.regularexpressions.regex.escape.aspx – russau

+1

Diese Regex funktioniert nicht, wenn die Zielzeichenfolge Zeilenumbrüche enthält. Kannst du es reparieren? Vielen Dank. –

+0

Scheint zu sperren, wenn kein N-tes Match vorhanden ist. Ich musste einen kommagetrennten Wert auf 1000 Werte beschränken, und dieser hing, wenn der CSV weniger hatte. Also @Yogesh - wahrscheinlich keine große akzeptierte Antwort wie es ist. ;) Unter Verwendung einer Variante von [dieser Antwort] (http://stackoverflow.com/a/6004505/1028230) (es gibt eine String-String-Version [hier] (http://stackoverflow.com/a/11773674/1028230)) und [änderte die Schleife, um bei n-ten Zählern zu stoppen] (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; 
} 

oder in C# mit Erweiterungsmethoden

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

Wenn ich mich nicht irre, schlägt diese Methode fehl, wenn die übereinstimmende Zeichenfolge an Position 0 beginnt, was durch anfängliches Setzen von 'index' auf -1 korrigiert werden kann. –

+1

Sie können auch nach leeren oder leeren Zeichenfolgen suchen und sie abgleichen oder es wird geworfen, aber das ist eine Designentscheidung. –

+0

Danke @PeterMajeed - wenn '" BOB ".IndexOf (" B ")' gibt 0 zurück, also sollte diese Funktion für '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.)

Hier ist die rekursive Implementierung (der oben Idee) als Erweiterungsmethode, um das Format des Rahmenverfahren nachahmt (s):

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

Auch hier sind einige (MBUnit) Unit Tests, die migh t Hilfe, die Sie (um es zu beweisen ist richtig):

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

Ich habe meine Formatierungen und Testfälle basierend auf dem großartigen Feedback von Weston aktualisiert (danke Weston). –

-3

Dies könnte es tun:

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

Ich sehe nicht, wie das funktionieren würde. Könnten Sie eine kurze Erklärung dazu geben? –

1

Vielleicht wäre es auch schön, mit der String.Split() Methode zu arbeiten und überprüfen, ob die angeforderte Auftreten in das Array, wenn Sie nicht den Index benötigen, aber der Wert im Index