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

83

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

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

Джози
источник
С чем вам еще предстоит работать?
Энтони Форлони, 03
3
Я отредактировал ваш ответ, чтобы более четко передать то, что вы хотите. Надеюсь, вы получите ответы на свой вопрос. Отсутствие свободного владения английским не является проблемой для Stack Overflow, вы всегда можете просто добавить строку с просьбой к кому-то более беглому отредактировать ваш вопрос и очистить его, но вы сами должны стремиться предоставить несколько примеров в вопросе, чтобы люди понимали, что тебе нужно.
Лассе В. Карлсен

Ответы:

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

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

Майк два
источник
7
Отличный подход. Красивый и чистый, легко читаемый, простой в обслуживании и отличной производительности.
Майк
1
Любите такие лупы, они не только дают отличную производительность, но и вы не ошибетесь с ними, потому что все кристально чистое и прямо у вас на глазах. Вы пишете linq, и какой-то разработчик зацикливает его, не понимая стоимости, и все задаются вопросом, где же узкое место в производительности.
user734028 08
20

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

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

s.TakeWhile(c => (n -= (c == t ? 1 : 0)) > 0).Count();
Шалин Шах
источник
1
Что возвращает, если персонаж не найден?
Тимучин
Он возвращает длину / количество строки s. Вам нужно проверить это значение.
Yoky
10

Вот еще одно решение 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!");
Ахмад Магид
источник
9

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

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));
}
Тод Томсон
источник
8

Обновление: Индекс 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
Ваш однострочный пример не работает, потому что значение n никогда не изменяется.
Dave Neeley
2
Хорошее решение, хотя это не настоящий «однострочник», поскольку переменная должна быть определена вне области лямбда. s.TakeWhile (c => ((n - = (c == 't'))? 1: 0)> 0) .Count ();
обнуляется
12
−1, "так что я оставил там несколько ошибок, чтобы вы могли их найти"
Занон
6

Раномор правильно заметил, что однострочный вариант Джоэла Кохорна не работает.

Вот двухстрочный, который действительно работает, метод расширения строки, который возвращает отсчитываемый от 0 индекс 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;
    }
}
Уэйн Маурер
источник
4

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

yourString.Where(c => c == 't').Count();
Эндрю Хэйр
источник
2
@Andrew - вы можете сократить это, пропустив Whereи передав предикат Countметоду. Не то, чтобы что-то не так.
Майк Два,
10
Разве это не будет просто определить, сколько вхождений символа существует, а не индекс n-го?
dx_over_dt
4

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

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;
}
Марк Калс
источник
3

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

     int i = 0;
     string s="asdasdasd";
     int n = 3;
     s.Where(b => (b == 'd') && (i++ == n));
     return i;
Dested
источник
3
public int GetNthOccurrenceOfChar(string s, char c, int occ)
{
    return String.Join(c.ToString(), s.Split(new char[] { c }, StringSplitOptions.None).Take(occ)).Length;
}
AGuyCalledGerald
источник
3
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
Викас Бансал
источник
2

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

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

с наилучшими пожеланиями.

EMAI
источник
2

Поскольку встроенная 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 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;
    }
Мэтт Фаргюсон
источник
2

Вот еще одна, может быть, более простая реализация строки IndexOfNth() с реализацией строк.

Вот 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;
}

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

Я думаю, что для такой низкоуровневой реализации вам следует избегать использования LINQ, RegEx или рекурсии для уменьшения накладных расходов.

Рик Страл
источник
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;
}

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

Ричардталлент
источник
В ответ на комментарий коллекции Matches (поскольку это то, что я показал в своем ответе): я полагаю, более эффективным подходом было бы использование проверки цикла while match.Successи получение NextMatchwhile, увеличивая счетчик и преждевременно прерываясь, когда 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 ;
    }
Ислам Яхиатене
источник
1

Привет всем, я создал два метода перегрузки для поиска 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

Расширенный LINQ Марка Калса для универсальных.

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


}

}

user2584621
источник
1
public static int IndexOfAny(this string str, string[] values, int startIndex, out string selectedItem)
    {
        int first = -1;
        selectedItem = null;
        foreach (string item in values)
        {
            int i = str.IndexOf(item, startIndex, StringComparison.OrdinalIgnoreCase);
            if (i >= 0)
            {
                if (first > 0)
                {
                    if (i < first)
                    {
                        first = i;
                        selectedItem = item;
                    }
                }
                else
                {
                    first = i;
                    selectedItem = item;
                }
            }
        }
        return first;
    }
Вахид Боранде
источник
-3
string theString = "The String";
int index = theString.NthIndexOf("THEVALUE", 3, true);
Колин
источник
где определение вашего метода NthIndexOf?
Мухаммад Вакас Азиз