Finden Sie das N-te Vorkommen eines Zeichens in einer Zeichenfolge

83

Ich benötige Hilfe beim Erstellen einer C # -Methode, die den Index des n-ten Vorkommens eines Zeichens in einer Zeichenfolge zurückgibt.

Zum Beispiel ist das dritte Vorkommen des Zeichens 't'in der Zeichenfolge "dtststxtu"5.
(Beachten Sie, dass die Zeichenfolge 4 ts hat.)

jozi
quelle
Womit müssen Sie bisher arbeiten?
Anthony Forloney
3
Ich habe Ihre Antwort bearbeitet, um klarer zu vermitteln, was Sie wollen. Hoffentlich erhalten Sie einige Antworten, die zur Frage passen. Nicht fließend Englisch zu sprechen ist kein Problem bei Stack Overflow. Sie können jederzeit eine Zeile hinzufügen, in der jemand, der fließender ist, gebeten wird, Ihre Frage zu bearbeiten und zu bereinigen. Sie müssen sich jedoch selbst bemühen, einige Beispiele in der Frage anzugeben, damit die Leute verstehen, was du brauchst.
Lasse V. Karlsen

Antworten:

93
public int GetNthIndex(string s, char t, int n)
{
    int count = 0;
    for (int i = 0; i < s.Length; i++)
    {
        if (s[i] == t)
        {
            count++;
            if (count == n)
            {
                return i;
            }
        }
    }
    return -1;
}

Das könnte viel sauberer gemacht werden, und es gibt keine Überprüfungen der Eingabe.

Mike Zwei
quelle
7
Toller Ansatz. Schön und sauber, leicht zu lesen, leicht zu warten und hervorragende Leistung.
Mike
1
Die Liebe zu solchen Loops bietet nicht nur eine hervorragende Leistung, sondern Sie können auch nichts falsch machen, da alles kristallklar und direkt vor Ihren Augen ist. Sie schreiben eine Linq und einige Entwickler setzen sie in eine Schleife, ohne die Kosten zu verstehen, und alle fragen sich immer wieder, wo der Leistungsengpass liegt.
user734028
20

In der vorherigen Lösung ist ein kleiner Fehler aufgetreten.

Hier ist ein aktualisierter Code:

s.TakeWhile(c => (n -= (c == t ? 1 : 0)) > 0).Count();
Shalin Shah
quelle
1
Was gibt es zurück, wenn der Charakter nicht gefunden wird?
Timuçin
Es gibt die Länge / Anzahl der Zeichenfolge s zurück. Sie müssen nach diesem Wert suchen.
Yoky
10

Hier ist eine weitere LINQ-Lösung:

string input = "dtststx";
char searchChar = 't';
int occurrencePosition = 3; // third occurrence of the char
var result = input.Select((c, i) => new { Char = c, Index = i })
                  .Where(item => item.Char == searchChar)
                  .Skip(occurrencePosition - 1)
                  .FirstOrDefault();

if (result != null)
{
    Console.WriteLine("Position {0} of '{1}' occurs at index: {2}",
                        occurrencePosition, searchChar, result.Index);
}
else
{
    Console.WriteLine("Position {0} of '{1}' not found!",
                        occurrencePosition, searchChar);
}

Nur zum Spaß, hier ist eine Regex-Lösung. Ich habe gesehen, dass einige Leute ursprünglich Regex zum Zählen verwendet haben, aber als sich die Frage änderte, wurden keine Aktualisierungen vorgenommen. So geht das mit Regex - wieder nur zum Spaß. Der traditionelle Ansatz ist der Einfachheit halber am besten.

string input = "dtststx";
char searchChar = 't';
int occurrencePosition = 3; // third occurrence of the char

Match match = Regex.Matches(input, Regex.Escape(searchChar.ToString()))
                   .Cast<Match>()
                   .Skip(occurrencePosition - 1)
                   .FirstOrDefault();

if (match != null)
    Console.WriteLine("Index: " + match.Index);
else
    Console.WriteLine("Match not found!");
Ahmad Mageed
quelle
9

Hier ist eine rekursive Implementierung - als Erweiterungsmethode, die das Format der Framework-Methode (n) nachahmt:

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

    return input.IndexOfNth(value, input.IndexOf(value, startIndex) + 1, --nth);
}

Außerdem sind hier einige (MBUnit) Unit-Tests aufgeführt, die Ihnen helfen könnten (um zu beweisen, dass sie korrekt sind):

[Test]
public void TestIndexOfNthWorksForNth1()
{
    const string input = "foo<br />bar<br />baz<br />";
    Assert.AreEqual(3, input.IndexOfNth("<br />", 0, 1));
}

[Test]
public void TestIndexOfNthWorksForNth2()
{
    const string input = "foo<br />whatthedeuce<br />kthxbai<br />";
    Assert.AreEqual(21, input.IndexOfNth("<br />", 0, 2));
}

[Test]
public void TestIndexOfNthWorksForNth3()
{
    const string input = "foo<br />whatthedeuce<br />kthxbai<br />";
    Assert.AreEqual(34, input.IndexOfNth("<br />", 0, 3));
}
Tod Thomson
quelle
8

Update: Index des N-ten Vorkommens Einzeiler:

int NthOccurence(string s, char t, int n)
{
    s.TakeWhile(c => n - (c == t)?1:0 > 0).Count();
}

Verwenden Sie diese auf eigenes Risiko. Das sieht nach Hausaufgaben aus, also habe ich ein paar Fehler hinterlassen, die Sie finden können:

int CountChars(string s, char t)
{
   int count = 0;
   foreach (char c in s)
      if (s.Equals(t)) count ++;
   return count;
}

.

int CountChars(string s, char t)
{
     return s.Length - s.Replace(t.ToString(), "").Length;
}

.

int CountChars(string s, char t)
{
    Regex r = new Regex("[\\" + t + "]");
    return r.Match(s).Count;
}
Joel Coehoorn
quelle
4
Ihr Einzeiler-Beispiel funktioniert nicht, da der Wert von n nie geändert wird.
Dave Neeley
2
Gute Lösung, obwohl dies kein echter "Einzeiler" ist, da eine Variable außerhalb des Geltungsbereichs des Lambda definiert werden muss. s.TakeWhile (c => ((n - = (c == 't'))? 1: 0)> 0) .Count ();
nullable
12
−1, "also habe ich ein paar Fehler hinterlassen, die du finden kannst"
Zanon
6

ranomore hat richtig kommentiert, dass Joel Coehoorns Einzeiler nicht funktioniert.

Hier ist ein zweizeiliger, funktionierender String, eine String-Erweiterungsmethode, die den 0-basierten Index des n-ten Vorkommens eines Zeichens zurückgibt, oder -1, wenn kein n-tes Vorkommen vorhanden ist:

public static class StringExtensions
{
    public static int NthIndexOf(this string s, char c, int n)
    {
        var takeCount = s.TakeWhile(x => (n -= (x == c ? 1 : 0)) > 0).Count();
        return takeCount == s.Length ? -1 : takeCount;
    }
}
Wayne Maurer
quelle
4

Joels Antwort ist gut (und ich habe sie positiv bewertet). Hier ist eine LINQ-basierte Lösung:

yourString.Where(c => c == 't').Count();
Andrew Hare
quelle
2
@ Andrew - Sie können dies verkürzen, Whereindem Sie das Prädikat überspringen und an die CountMethode übergeben. Nicht, dass irgendetwas falsch daran ist, wie es ist.
Mike Two
10
Wird dies nicht nur herausfinden, wie viele Vorkommen eines Zeichens es gibt, anstatt den Index des n-ten?
dx_over_dt
4

Ich füge eine weitere Antwort hinzu, die im Vergleich zu anderen Methoden ziemlich schnell abläuft

private static int IndexOfNth(string str, char c, int nth, int startPosition = 0)
{
    int index = str.IndexOf(c, startPosition);
    if (index >= 0 && nth > 1)
    {
        return  IndexOfNth(str, c, nth - 1, index + 1);
    }

    return index;
}
Marc Cals
quelle
3

Hier ist eine unterhaltsame Möglichkeit, dies zu tun

     int i = 0;
     string s="asdasdasd";
     int n = 3;
     s.Where(b => (b == 'd') && (i++ == n));
     return i;
Geschachtelt
quelle
3
public int GetNthOccurrenceOfChar(string s, char c, int occ)
{
    return String.Join(c.ToString(), s.Split(new char[] { c }, StringSplitOptions.None).Take(occ)).Length;
}
AGuyCalledGerald
quelle
3
string result = "i am '[email protected]'"; // string

int in1 = result.IndexOf('\''); // get the index of first quote

int in2 = result.IndexOf('\'', in1 + 1); // get the index of second

string quoted_text = result.Substring(in1 + 1, in2 - in1); // get the string between quotes
Vikas Bansal
quelle
2

Sie können diese Arbeit mit regulären Ausdrücken ausführen.

        string input = "dtststx";
        char searching_char = 't';
        int output = Regex.Matches(input, "["+ searching_char +"]")[2].Index;

mit bestem Gruß.

EMAI
quelle
2

Da die integrierte IndexOfFunktion bereits für die Suche nach einem Zeichen innerhalb einer Zeichenfolge optimiert ist, wäre eine noch schnellere Version (als Erweiterungsmethode):

public static int NthIndexOf(this string input, char value, int n)
{
    if (n <= 0) throw new ArgumentOutOfRangeException("n", n, "n is less than zero.");

    int i = -1;
    do
    {
        i = input.IndexOf(value, i + 1);
        n--;
    }
    while (i != -1 && n > 0);

    return i;
}

Oder um vom Ende der Zeichenfolge aus zu suchen LastIndexOf:

public static int NthLastIndexOf(this string input, char value, int n)
{
    if (n <= 0) throw new ArgumentOutOfRangeException("n", n, "n is less than zero.");

    int i = input.Length;
    do
    {
        i = input.LastIndexOf(value, i - 1);
        n--;
    }
    while (i != -1 && n > 0);

    return i;
}

Das Suchen nach einer Zeichenfolge anstelle eines Zeichens ist so einfach wie das Ändern des Parametertyps von charin stringund optional das Hinzufügen einer Überladung, um die anzugeben StringComparison.

Ronald
quelle
2

Wenn Sie interessiert sind, können Sie auch String-Erweiterungsmethoden wie die folgenden erstellen:

     public static int Search(this string yourString, string yourMarker, int yourInst = 1, bool caseSensitive = true)
    {
        //returns the placement of a string in another string
        int num = 0;
        int currentInst = 0;
        //if optional argument, case sensitive is false convert string and marker to lowercase
        if (!caseSensitive) { yourString = yourString.ToLower(); yourMarker = yourMarker.ToLower(); }
        int myReturnValue = -1; //if nothing is found the returned integer is negative 1
        while ((num + yourMarker.Length) <= yourString.Length)
        {
            string testString = yourString.Substring(num, yourMarker.Length);

            if (testString == yourMarker)
            {
                currentInst++;
                if (currentInst == yourInst)
                {
                    myReturnValue = num;
                    break;
                }
            }
            num++;
        }           
       return myReturnValue;
    }

   public static int Search(this string yourString, char yourMarker, int yourInst = 1, bool caseSensitive = true)
    {
        //returns the placement of a string in another string
        int num = 0;
        int currentInst = 0;
        var charArray = yourString.ToArray<char>();
        int myReturnValue = -1;
        if (!caseSensitive)
        {
            yourString = yourString.ToLower();
            yourMarker = Char.ToLower(yourMarker);
        }
        while (num <= charArray.Length)
        {                
            if (charArray[num] == yourMarker)
            {
                currentInst++;
                if (currentInst == yourInst)
                {
                    myReturnValue = num;
                    break;
                }
            }
            num++;
        }
        return myReturnValue;
    }
Matt Farguson
quelle
2

Hier ist eine andere, vielleicht einfachere Implementierung von String IndexOfNth()mit String-Implementierung.

Hier ist die stringMatch-Version:

public static int IndexOfNth(this string source, string matchString, 
                             int charInstance, 
                             StringComparison stringComparison = StringComparison.CurrentCulture)
{
    if (string.IsNullOrEmpty(source))
        return -1;

    int lastPos = 0;
    int count = 0;

    while (count < charInstance )
    {
        var len = source.Length - lastPos;
        lastPos = source.IndexOf(matchString, lastPos,len,stringComparison);
        if (lastPos == -1)
            break;

        count++;
        if (count == charInstance)
            return lastPos;

        lastPos += matchString.Length;
    }
    return -1;
}

und die charMatch-Version:

public static int IndexOfNth(string source, char matchChar, int charInstance)        
{
    if (string.IsNullOrEmpty(source))
        return -1;

    if (charInstance < 1)
        return -1;

    int count = 0;
    for (int i = 0; i < source.Length; i++)
    {
        if (source[i] == matchChar)
        {
            count++;
            if (count == charInstance)                 
                return i;                 
        }
    }
    return -1;
}

Ich denke, für eine so einfache Implementierung möchten Sie sich von der Verwendung von LINQ, RegEx oder Rekursion fernhalten, um den Overhead zu reduzieren.

Rick Strahl
quelle
1

Eine weitere RegEx-basierte Lösung (ungetestet):

int NthIndexOf(string s, char t, int n) {
   if(n < 0) { throw new ArgumentException(); }
   if(n==1) { return s.IndexOf(t); }
   if(t=="") { return 0; }
   string et = RegEx.Escape(t);
   string pat = "(?<="
      + Microsoft.VisualBasic.StrDup(n-1, et + @"[.\n]*") + ")"
      + et;
   Match m = RegEx.Match(s, pat);
   return m.Success ? m.Index : -1;
}

Dies sollte etwas optimaler sein, als wenn RegEx eine Matches-Auflistung erstellen muss, um nur alle Übereinstimmungen bis auf eine zu verwerfen.

richardtallent
quelle
Als Antwort auf den Matches-Auflistungskommentar (da ich dies in meiner Antwort gezeigt hatte): Ich nehme an, ein effizienterer Ansatz wäre die Verwendung einer while-Schleife match.Success, die nach einem NextMatchwhile-Inkrement sucht und diesen erhält, während ein Zähler inkrementiert und vorzeitig unterbrochen wird counter == index.
Ahmad Mageed
1
    public static int FindOccuranceOf(this string str,char @char, int occurance)
    {
       var result = str.Select((x, y) => new { Letter = x, Index = y })
            .Where(letter => letter.Letter == @char).ToList();
       if (occurence > result.Count || occurance <= 0)
       {
           throw new IndexOutOfRangeException("occurance");
       }
       return result[occurance-1].Index ;
    }
Islam Yahiatene
quelle
1

Hallo zusammen, ich habe zwei Überladungsmethoden erstellt, um das n-te Vorkommen von char und Text mit geringerer Komplexität zu finden, ohne durch die Schleife zu navigieren, was die Leistung Ihrer Anwendung erhöht.

public static int NthIndexOf(string text, char searchChar, int nthindex)
{
   int index = -1;
   try
   {
      var takeCount = text.TakeWhile(x => (nthindex -= (x == searchChar ? 1 : 0)) > 0).Count();
      if (takeCount < text.Length) index = takeCount;
   }
   catch { }
   return index;
}
public static int NthIndexOf(string text, string searchText, int nthindex)
{
     int index = -1;
     try
     {
        Match m = Regex.Match(text, "((" + searchText + ").*?){" + nthindex + "}");
        if (m.Success) index = m.Groups[2].Captures[nthindex - 1].Index;
     }
     catch { }
     return index;
}
Suneel Gupta
quelle
1

Marc Cals 'LINQ Extended für Generika.

   using System;
   using System.Collections.Generic;
   using System.Linq;

   namespace fNns
   {
       public class indexer<T> where T : IEquatable<T>
       {
           public T t { get; set; }
           public int index { get; set; }
       }
       public static class fN
       {
           public static indexer<T> findNth<T>(IEnumerable<T> tc, T t,
               int occurrencePosition) where T : IEquatable<T>
           {
               var result = tc.Select((ti, i) => new indexer<T> { t = ti, index = i })
                      .Where(item => item.t.Equals(t))
                      .Skip(occurrencePosition - 1)
                      .FirstOrDefault();
               return result;
           }
           public static indexer<T> findNthReverse<T>(IEnumerable<T> tc, T t,
       int occurrencePosition) where T : IEquatable<T>
           {
               var result = tc.Reverse<T>().Select((ti, i) => new indexer<T> {t = ti, index = i })
                      .Where(item => item.t.Equals(t))
                      .Skip(occurrencePosition - 1)
                      .FirstOrDefault();
               return result;
           }
       }
   }

Einige Tests.

   using System;
   using System.Collections.Generic;
   using NUnit.Framework;
   using Newtonsoft.Json;
   namespace FindNthNamespace.Tests
   {

       public class fNTests
       {
           [TestCase("pass", "dtststx", 't', 3, Result = "{\"t\":\"t\",\"index\":5}")]
           [TestCase("pass", new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
        0, 2, Result="{\"t\":0,\"index\":10}")]
           public string fNMethodTest<T>(string scenario, IEnumerable<T> tc, T t, int occurrencePosition) where T : IEquatable<T>
           {
               Console.WriteLine(scenario);
               return JsonConvert.SerializeObject(fNns.fN.findNth<T>(tc, t, occurrencePosition)).ToString();
           }

           [TestCase("pass", "dtststxx", 't', 3, Result = "{\"t\":\"t\",\"index\":6}")]
           [TestCase("pass", new int[] { 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9 },
        0, 2, Result = "{\"t\":0,\"index\":19}")]
           public string fNMethodTestReverse<T>(string scenario, IEnumerable<T> tc, T t, int occurrencePosition) where T : IEquatable<T>
           {
               Console.WriteLine(scenario);
               return JsonConvert.SerializeObject(fNns.fN.findNthReverse<T>(tc, t, occurrencePosition)).ToString();
           }


}

}}

user2584621
quelle
1
public static int IndexOfAny(this string str, string[] values, int startIndex, out string selectedItem)
    {
        int first = -1;
        selectedItem = null;
        foreach (string item in values)
        {
            int i = str.IndexOf(item, startIndex, StringComparison.OrdinalIgnoreCase);
            if (i >= 0)
            {
                if (first > 0)
                {
                    if (i < first)
                    {
                        first = i;
                        selectedItem = item;
                    }
                }
                else
                {
                    first = i;
                    selectedItem = item;
                }
            }
        }
        return first;
    }
Vahid Borandeh
quelle
-3
string theString = "The String";
int index = theString.NthIndexOf("THEVALUE", 3, true);
Colin
quelle
Wo ist die Definition Ihrer NthIndexOf-Methode?
Muhammad Waqas Aziz