Encontrar Nth ocurrencia de un personaje en una cadena

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.

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

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

Eso podría hacerse mucho más limpio, y no hay controles en la entrada.

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

Actualización: Índice de Nth occurance one-liner:

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

Úselos bajo su propio riesgo. Esto parece una tarea, así que dejé algunos errores para que 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; } 

Aquí hay una implementación recursiva, como método de extensión, que imita el formato de los métodos del 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); } 

Además, aquí hay algunas pruebas de unidades (MBUnit) que pueden ayudarlo (para demostrar que es correcto):

 [Test] public void TestIndexOfNthWorksForNth1() { const string input = "foo
bar
baz
"; Assert.AreEqual(3, input.IndexOfNth("
", 0, 1)); } [Test] public void TestIndexOfNthWorksForNth2() { const string input = "foo
whatthedeuce
kthxbai
"; Assert.AreEqual(21, input.IndexOfNth("
", 0, 2)); } [Test] public void TestIndexOfNthWorksForNth3() { const string input = "foo
whatthedeuce
kthxbai
"; Assert.AreEqual(34, input.IndexOfNth("
", 0, 3)); }

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

Solo por diversión, aquí hay una solución Regex. 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() .Skip(occurrencePosition - 1) .FirstOrDefault(); if (match != null) Console.WriteLine("Index: " + match.Index); else Console.WriteLine("Match not found!"); 

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

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

Ranomore correctamente comentó que el one-liner de Joel Coehoorn no funciona.

Aquí hay un trazador de líneas dos que funciona, un método de extensión de cadena que devuelve el índice 0 de la enésima aparición de un carácter, o -1 si no hay una aparición enésima:

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

Aquí hay una forma divertida de hacerlo

  int i = 0; string s="asdasdasd"; int n = 3; s.Where(b => (b == 'd') && (i++ == n)); return i; 
 string result = "i am 'bansal.vks@gmail.com'"; // 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 

Agrego otra respuesta que funciona 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; } 

puedes hacer este trabajo con expresiones regulares.

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

cordial saludo.

 public int GetNthOccurrenceOfChar(string s, char c, int occ) { return String.Join(c.ToString(), s.Split(new char[] { c }, StringSplitOptions.None).Take(occ)).Length; } 

si está interesado, también puede crear métodos de extensión de cadena de esta manera:

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

Otra solución basada en RegEx (no probada):

 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 óptimo que requerir RegEx para crear una colección de coincidencias, solo para descartar todas las coincidencias menos una.

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

Hola a todos, he creado dos métodos de sobrecarga para encontrar enésima ocurrencia de char y para texto con menos complejidad sin navegar por el bucle, lo que aumenta 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; } 

Dado que la función integrada IndexOf ya está optimizada para buscar un personaje dentro de una cadena, una versión aún más rápida 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 final de la cadena usando 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; } 

La búsqueda de una cadena en lugar de un carácter es tan simple como cambiar el tipo de parámetro de char a string y opcionalmente agregar una sobrecarga para especificar StringComparison .

LINQ de Marc Cals Extended para generics.

  using System; using System.Collections.Generic; using System.Linq; namespace fNns { public class indexer where T : IEquatable { public T t { get; set; } public int index { get; set; } } public static class fN { public static indexer findNth(IEnumerable tc, T t, int occurrencePosition) where T : IEquatable { var result = tc.Select((ti, i) => new indexer { t = ti, index = i }) .Where(item => item.t.Equals(t)) .Skip(occurrencePosition - 1) .FirstOrDefault(); return result; } public static indexer findNthReverse(IEnumerable tc, T t, int occurrencePosition) where T : IEquatable { var result = tc.Reverse().Select((ti, i) => new indexer {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(string scenario, IEnumerable tc, T t, int occurrencePosition) where T : IEquatable { Console.WriteLine(scenario); return JsonConvert.SerializeObject(fNns.fN.findNth(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(string scenario, IEnumerable tc, T t, int occurrencePosition) where T : IEquatable { Console.WriteLine(scenario); return JsonConvert.SerializeObject(fNns.fN.findNthReverse(tc, t, occurrencePosition)).ToString(); } } 

}

Aquí hay otra implementación, quizás más simple, de la cadena IndexOfNth() con implementación de cadenas.

Aquí está la versión de coincidencia de string :

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

y la versión de coincidencia de caracteres:

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

Creo que para una implementación de bajo nivel, querrá evitar el uso de LINQ, RegEx o recursión para reducir los gastos generales.