2010-04-03 18 views
62

Necesito ayuda para crear un método C# que devuelva el índice de la enésima aparición de un carácter en una cadena.Buscar Nth ocurrencia de un carácter en una cadena

Por ejemplo, la tercera aparición del carácter en la cadena 't'"dtststxtu" es 5.
(Tenga en cuenta que la cadena tiene 4 t s.)

+0

¿Qué tiene que trabajar con hasta ahora? –

+1

He editado su respuesta para transmitir más claramente lo que desea. Con suerte, obtendrá algunas respuestas que se ajustan a la pregunta. No dominar el inglés no es un problema en Stack Overflow, siempre puedes simplemente agregar una línea para pedirle a alguien más fluido que edite tu pregunta y limpiarla, pero debes esforzarte por dar algunos ejemplos en la pregunta para que la gente entienda qué necesitas. –

Respuesta

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

que se podría hacer mucho más limpio, y no hay control de las entradas.

+5

Gran enfoque. Agradable y limpio, fácil de leer, fácil de mantener y excelente rendimiento. – Mike

+0

Me encantan los bucles como estos, no solo dan un excelente rendimiento, pero no puedes equivocarte con ellos, ya que todo es claro como el cristal y justo en frente de tus ojos. Escribes un linq y un desarrollador lo pone en un bucle sin entender el costo y todos siguen preguntándose dónde está el cuello de botella de rendimiento. – user734028

11

Actualización: Índice de enésima ocurrencia de una sola línea:

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

utilizar estos a su propio riesgo. Esto parece una tarea, así que dejé algunos errores para que los encuentres:

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

Su ejemplo de una sola línea no funciona porque el valor de n nunca cambia. –

+2

Buena solución, aunque este no es un verdadero "unidireccional" ya que una variable debe definirse fuera del alcance de la lambda. s.TakeWhile (c => ((n - = (c == 't'))?1: 0)> 0) .Count(); – nullable

+0

-1, "así que dejé algunos errores ahí para que los encuentres" – Zanon

4

La respuesta de Joel es buena (y la he votado positivamente). Aquí es una solución basada en LINQ:

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

@Andrew - puedes acortar esto omitiendo el 'Donde' y pasando el predicado al método' Count'. No es que haya nada de malo en la forma en que está. –

+9

¿No solo encontrará cuántas apariciones de un personaje hay en lugar del índice de la enésima? – dfoverdx

7

Aquí hay otra solución 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); 
} 

Sólo por diversión, he aquí una solución expresión regular. Vi a algunas personas usar inicialmente Regex para contar, pero cuando la pregunta cambió no se realizaron actualizaciones. Así es como se puede hacer con Regex, de nuevo, solo por diversión. El enfoque tradicional es mejor para la simplicidad.

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

Aquí es una manera divertida de hacerlo

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

Otra solución basado en expresiones regulares (no probado):

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

Esto debería ser un poco más óptima de requerir expresiones regulares para crear una Coincide con la colección, solo para descartar todas las coincidencias menos una.

+0

En respuesta al comentario de la colección Matches (ya que eso es lo que había mostrado en mi respuesta): supongo que un enfoque más eficiente sería usar un ciclo while para verificar 'match.Success' y obtener' NextMatch' mientras se incrementa un contrarrestar y romper temprano cuando el 'contador == índice'. –

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

Aquí es una implementación recursiva - como un método de extensión, imitando el formato del método (s) Marco:

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

nuevo, aquí hay algunas (MbUnit) pruebas unitarias que pueden ayudarle (para demostrar que es correcta):

[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 comentó correctamente que una sola línea de Joel Coehoorn no funciona.

Aquí es un dos-liner que hace trabajo, un método de extensión de cadena que devuelve el índice 0 a base de de la enésima aparición de un carácter, o -1 si no existe ninguna ocurrencia de orden n:

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

puede hacer este trabajo con expresiones regulares.

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

mejor relación.

16

Hay un error menor en la solución anterior.

Aquí hay un código actualizado:

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

¿Qué devuelve si no se encuentra el personaje? –

+0

Devuelve la longitud/conteo de la cadena s. Necesitas verificar ese valor. – Yoky

1

Hola a todos, he creado dos métodos de sobrecarga para encontrar enésima aparición de carbón y para texto con menos complejidad sin tener que navegar a través del bucle, que aumentan el rendimiento de su aplicación.

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

Desde la incorporada en IndexOf función ya está optimizada para la búsqueda de un carácter dentro de una cadena, una versión aún más rápido sería (como método de extensión):

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

O para buscar desde el extremo de la cadena mediante 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; 
} 

búsqueda de una cadena en lugar de un personaje es tan simple como cambiar el tipo de parámetro a partir de char a string y opcionalmente agregue una sobrecarga para especificar 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

que añadir otra respuesta que funcionan bastante rápido en comparación con otros métodos

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' LINQ extendido para genérico.

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

Algunas pruebas.

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 su interesado también puede crear métodos de extensión de cadena, así:

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