2010-04-03 10 views
62

J'ai besoin d'aide pour créer une méthode C# qui retourne l'index de la Nième occurrence d'un caractère dans une chaîne.Trouver la Nième occurrence d'un caractère dans une chaîne

Par exemple, la 3ème occurrence du caractère 't' dans la chaîne "dtststxtu" est 5.
(Notez que la chaîne a 4 t s.)

+0

Qu'est-ce que vous avez à travailler avec à ce jour? –

+1

J'ai modifié votre réponse pour transmettre plus clairement ce que vous voulez. J'espère que vous obtiendrez des réponses qui correspondent à la question. Ne pas parler couramment l'anglais n'est pas un problème sur Stack Overflow, vous pouvez toujours ajouter une ligne demandant à quelqu'un de plus fluide de modifier votre question et de la nettoyer, mais vous devez vous efforcer de fournir quelques exemples dans la question afin que les gens comprennent vous avez besoin. –

Répondre

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

Cela pourrait être fait un beaucoup plus propre, et il n'y a aucun contrôle sur l'entrée.

+5

Bonne approche. Agréable et propre, facile à lire, facile à entretenir et excellente performance. – Mike

+0

aime les boucles comme celles-ci, non seulement ils donnent d'excellentes performances, mais vous ne pouvez pas vous tromper avec eux puisque tout est limpide et juste devant vos yeux. Vous écrivez un linq et certains développeurs le mettent dans une boucle ne comprenant pas le coût et tout le monde se demande où est le goulot d'étranglement de la performance. – user734028

11

Mise à jour: Index des Nième occurance one-liner:

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

Utilisez ces à vos propres risques. Cela ressemble à des devoirs, je laisse quelques bugs là-bas pour votre trouver:

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

Votre exemple de doublure ne fonctionne pas car la valeur de n n'est jamais modifiée. –

+2

Belle solution, bien que ce ne soit pas un vrai "one-liner" car une variable doit être définie hors de portée du lambda. s.TakeWhile (c => ((n - = (c == 't'))?1: 0)> 0) .Count(); – nullable

+0

-1, "j'ai donc laissé quelques bugs là-dedans pour que vous puissiez trouver" – Zanon

4

La réponse de Joel est bonne (et je l'ai upvoted). Voici une solution basée LINQ:

yourString.Where(c => c == 't').Count(); 
+2

@Andrew - vous pouvez raccourcir cela en sautant le 'Where' et en passant le prédicat à la méthode' Count'. Ce n'est pas qu'il y ait quelque chose qui ne va pas. –

+9

Cela ne va-t-il pas simplement déterminer combien d'occurrences d'un personnage il y a plutôt que l'indice de la nième? – dfoverdx

7

Voici une autre solution LINQ:

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

Juste pour le plaisir, voici une solution Regex. J'ai vu certaines personnes utiliser initialement Regex pour compter, mais quand la question a changé, aucune mise à jour n'a été faite. Voici comment cela peut être fait avec Regex - encore une fois, juste pour le plaisir. L'approche traditionnelle est la meilleure pour la simplicité.

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!"); 
3

Voici une façon amusante de faire

 int i = 0; 
    string s="asdasdasd"; 
    int n = 3; 
    s.Where(b => (b == 'd') && (i++ == n)); 
    return i; 
1

Une autre solution RegEx (non testé):

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

Cela devrait être un peu plus optimale que d'exiger RegEx pour créer un Correspond à la collection, uniquement pour défausser tous les matchs sauf un.

+0

En réponse au commentaire de la collection Matches (puisque c'est ce que j'avais montré dans ma réponse): Je suppose qu'une approche plus efficace serait d'utiliser une boucle while pour vérifier 'match.Success' et obtenir' NextMatch' en incrémentant un contrer et casser tôt quand le 'counter == index'. –

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

Voici une implémentation récursive - comme une méthode d'extension, mimant le format de la méthode du cadre (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); 

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

, voici également quelques tests unitaires (MbUnit) qui pourraient vous aider (pour prouver qu'il est correct):

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

ranomore a commenté à juste titre que uniligne de Joel Coehoorn ne fonctionne pas.

Voici deux-liner que -t travail, une méthode d'extension de chaîne qui retourne l'index de base 0 de la nième occurrence d'un caractère, ou -1 si aucune nième occurrence existe:

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

vous pouvez faire ce travail avec les expressions régulières.

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

meilleure considération.

16

Il existe un bogue mineur dans la solution précédente.

Voici un code mis à jour:

s.TakeWhile(c => (n -= (c == t ? 1 : 0)) > 0).Count(); 
+1

Que retourne-t-il si le caractère n'est pas trouvé? –

+0

Renvoie la longueur/le nombre de la chaîne s. Vous devez vérifier cette valeur. – Yoky

1

Salut à tous j'ai créé deux méthodes de surcharge pour trouver nième occurrence de omble et pour texte avec moins de complexité sans avoir à naviguer à travers la boucle, ce qui augmente les performances de ton application.

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

Depuis le haut-IndexOf fonction est déjà optimisé pour la recherche d'un caractère dans une chaîne, une version encore plus rapide serait (comme méthode d'extension):

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

Ou pour la recherche de la fin de la chaîne en utilisant 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; 
} 

recherche d'une chaîne au lieu d'un caractère est aussi simple que de changer le type de paramètre de char à string et ajoutez éventuellement une surcharge pour spécifier le StringComparison.

2
public int GetNthOccurrenceOfChar(string s, char c, int occ) 
{ 
    return String.Join(c.ToString(), s.Split(new char[] { c }, StringSplitOptions.None).Take(occ)).Length; 
} 
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 
3

ajouter une autre réponse que je qui exécutent assez rapide par rapport aux autres méthodes

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

Marc Cals de LINQ à long terme pour générique.

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

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


} 

}

2

si vous êtes intéressé, vous pouvez également créer des méthodes d'extension de chaîne comme ceci:

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