2010-04-03 3 views
62

Мне нужна помощь при создании метода C#, который возвращает индекс N-го вхождения символа в строку.Найти N-е вхождение символа в строке

Например, третье вхождение символа 't' в строке "dtststxtu" 5.
(Обратите внимание, что строка имеет 4 t с.)

+0

Что вы должны работать с до сих пор? –

+1

Я отредактировал ваш ответ, чтобы более четко передать то, что вы хотите. Надеюсь, вы получите ответы на вопросы. Если вы не владеете английским языком, это не проблема в переполнении стека, вы всегда можете просто добавить строку, в которой кто-то более свободно говорит на ваш вопрос и очищает его, но вы должны сами попытаться привести некоторые примеры в вопросе, чтобы люди поняли, что тебе нужно. –

ответ

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

Это может быть сделано намного ровнее, и нет никаких проверок на входе.

+5

Отличный подход. Хороший и чистый, легко читаемый, простой в обслуживании и отличная производительность. – Mike

+0

люблю такие петли, не только они дают отличную производительность, но вы не можете ошибиться с ними, потому что все кристально чисто и прямо перед вашими глазами. Вы пишете linq, и какой-то разработчик ставит его в цикл, не понимая стоимости, и все продолжают задаваться вопросом, где это узкое место производительности. – user734028

11

Обновление: индекс Nth вхождение однострочника:

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

Используйте их на свой страх и риск. Это выглядит как домашнее задание, поэтому я оставил несколько ошибок там, где можно найти:

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

Ваш пример с одним слоем не работает, потому что значение n никогда не изменяется. –

+2

Хорошее решение, хотя это не настоящий «однострочный», поскольку переменная должна быть определена из области действия лямбда. s.TakeWhile (c => ((n - = (c == 't'))?1: 0)> 0) .Count(); – nullable

+0

-1, «поэтому я оставил несколько ошибок там, чтобы найти вас» – Zanon

4

Ответ Джоэля является хорошим (и я его поддержал). Вот LINQ-решение:

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

@ Андрей - вы можете сократить это, пропустив 'Where' и передав предикат методу' Count'. Не то, чтобы что-то случилось с тем, как оно есть. –

+9

Не будет ли это просто найти количество вхождений персонажа, а не индекс n-го? – dfoverdx

7

Вот еще одно решение 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); 
} 

Просто для удовольствия, вот решение Regex. Я видел, что некоторые люди первоначально использовали Regex для подсчета, но когда вопрос изменился, никаких обновлений не было сделано. Вот как это можно сделать с помощью Regex - опять же, просто для удовольствия. Традиционный подход лучше всего подходит для простоты.

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

Вот интересный способ сделать это

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

другой RegEx на основе решения (непроверенные):

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

Это должно быть немного более оптимальным, чем требует регулярных выражений, чтобы создать Матчи коллекции, только чтобы отбросить все, кроме одного.

+0

В ответ на комментарий коллекции совпадений (поскольку это то, что я показал в своем ответе): я полагаю, что более эффективным подходом было бы использовать цикл while для проверки соответствия. счетчик и нарушение раннего, когда '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

Вот рекурсивная реализация - как метод расширения, имитируя формат метода рамок (ы):

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

Кроме того, здесь некоторые (MBUnit) юнит-тесты, которые могут помочь вам (чтобы доказать, что это правильно):

[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 правильно заметил, что один вкладыш Joel Coehoorn не работает.

Вот два лайнера, который делает работы, метод расширения строки, которая возвращает 0 на основе индекса п-го появления символа, или -1, если не энное возникновение не существует:

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

вы можете выполнять эту работу с помощью регулярных выражений.

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

наилучшим образом.

16

В предыдущем решении есть незначительная ошибка.

Вот некоторый обновленный код:

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

Что он возвращает, если символ не найден? –

+0

Возвращает длину/счет строки s. Вам нужно проверить это значение. – Yoky

1

Привет всем я создал два метода защиты от перегрузки для нахождения п-й появления в полукокса и текста с меньшей сложностью, не перемещаясь по петле, что повышает производительность ваше приложение.

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

Поскольку встроенная в IndexOf функции уже оптимизированы для поиска символа в строке, еще более быстрая версия будет (как метод расширения):

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

Или искать с конца строки с использованием 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; 
} 

найдите строку вместо символа так просто, как изменить тип параметра из char - string и необязательно добавить перегруз для указания 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

добавить еще один ответ, что бегут довольно быстро по сравнению с другими методами

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 Extended для родовым.

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

Некоторые тесты.

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

если ваш интерес вы можете также создать методы расширения строки, как так:

 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; 
    } 
Смежные вопросы