Localizar a enésima ocorrência de um caractere em uma string

Preciso de ajuda para criar um método C # que retorne o índice da enésima ocorrência de um caractere em uma string.

Por exemplo, a terceira ocorrência do caractere 't' na string "dtststxtu" é 5.
(Note que a string tem 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; } 

Isso poderia ser muito mais limpo, e não há verificações na input.

Há um pequeno erro na solução anterior.

Aqui está algum código atualizado:

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

Atualização: Índice de enésima ocorrência de uma linha:

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

Use isso por sua conta e risco. Isso parece lição de casa, então eu deixei alguns bugs lá para você encontrar:

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

Aqui está uma implementação recursiva – como um método de extensão, imitando o formato do (s) método (s) da estrutura:

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

Além disso, aqui estão alguns testes de unidade (MBUnit) que podem ajudá-lo (para provar que está correto):

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

Aqui está outra solução 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); } 

Apenas por diversão, aqui está uma solução Regex. Eu vi algumas pessoas inicialmente usarem o Regex para contar, mas quando a pergunta mudou, nenhuma atualização foi feita. Aqui está como isso pode ser feito com o Regex – novamente, apenas por diversão. A abordagem tradicional é melhor pela simplicidade.

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

A resposta de Joel é boa (e eu inventei isso). Aqui está uma solução baseada em LINQ:

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

Ranomore corretamente comentou que o one-liner de Joel Coehoorn não funciona.

Aqui está um two-liner que funciona, um método de extensão de string que retorna o índice baseado em 0 da enésima ocorrência de um caractere, ou -1 se não existir a n-ésima ocorrência:

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

Aqui está uma maneira divertida de fazer isso

  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 

Eu adiciono outra resposta que corre muito rápido em comparação com outros methods

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

você pode fazer este trabalho com expressões regulares.

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

os meus cumprimentos.

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

Se você está interessado, você também pode criar methods de extensão de string como:

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

Outra solução baseada em RegEx (não testada):

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

Isso deve ser um pouco mais otimizado do que exigir que o RegEx crie uma coleção de correspondências, apenas para descartar todas as correspondências, exceto uma.

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

Oi tudo eu criei dois methods de sobrecarga para encontrar nth ocorrência de char e para texto com menos complexidade sem navegar através de loop, o que aumenta o desempenho de sua aplicação.

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

Como a function IndexOf interna já está otimizada para pesquisar um caractere em uma string, uma versão ainda mais rápida seria (como método de extensão):

 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 para pesquisar a partir do final da string 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; } 

Procurar por uma string em vez de um caractere é tão simples quanto mudar o tipo de parâmetro de char para string e, opcionalmente, adicionar uma sobrecarga para especificar StringComparison .

Marc Cals ‘LINQ Extended para genérico.

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

Alguns testes.

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

}

Aqui está outra implementação, talvez mais simples, da cadeia IndexOfNth() com a implementação de strings.

Aqui está a versão de correspondência 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; } 

e a versão do jogo char :

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

Eu acho que para uma implementação de baixo nível você gostaria de evitar o uso de LINQ, RegEx ou recursion para reduzir a sobrecarga.