Найти N-е вхождение символа в строке

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

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

Ответ 1

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

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

Ответ 2

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

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

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

Ответ 3

Обновление: Индекс N-го уровня однострочного интерфейса:

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

Ответ 4

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

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

Ответ 5

Здесь другое решение 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!");

Ответ 6

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

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

Ответ 7

ranomore правильно прокомментировал, что однослойный Joel Coehoorn не работает.

Вот двухстрочный файл, который работает, метод расширения строки, который возвращает индекс на основе n для n-го вхождения символа или -1, если не существует 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;
    }
}

Ответ 8

Вот забавный способ сделать это

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

Ответ 9

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

Ответ 10

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

Ответ 11

Я добавляю еще один ответ, который выполняется довольно быстро по сравнению с другими методами.

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

Ответ 12

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

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

Ответ 13

Другое решение на основе 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;
}

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

Ответ 14

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

Ответ 15

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

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

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

Ответ 16

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

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

Ответ 17

Так как встроенная функция 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.

Ответ 18

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


}

}