web-dev-qa-db-fra.com

Obtenir l'index de la nième occurrence de char dans une chaîne

J'essaie de créer une fonction qui retourne l'index de la Nième occurrence d'un caractère donné dans une chaîne.

Voici ma tentative:

private int IndexOfNth(string str, char c, int n)
{
    int index = str.IndexOf(c) + 1;
    if (index >= 0)
    {
        string temp = str.Substring(index, str.Length - index);
        for (int j = 1; j < n; j++)
        {
            index = temp.IndexOf(c) + 1;
            if (index < 0)
            {
                return -1;
            }
            temp = temp.Substring(index, temp.Length - index);
        }
        index = index + (str.Length);
    }
    return index;
}

This devrait rechercher la première occurrence, couper la partie avant de la chaîne, rechercher la première occurrence à partir de la nouvelle sous-chaîne, et ainsi de suite jusqu'à l'obtention de l'index de la nième occurrence. Cependant, j'ai omis de considérer comment l'index de la sous-chaîne finale sera décalé par rapport à l'index d'origine d'origine dans la chaîne d'origine. Comment puis-je faire ce travail?

Également en tant que question secondaire, si je veux que le caractère soit le caractère de tabulation, est-ce que je passe cette fonction '\ t' ou quoi?

18
Kyle V.

Utilisation de LINQ pour rechercher l'index de la 5ème a dans la chaîne aababaababa:

var str = "aababaababa";
var ch = 'a';
var n = 5;
var result = str
  .Select((c, i) => new { c, i })
  .Where(x => x.c == ch)
  .Skip(n - 1)
  .FirstOrDefault();
return result != null ? result.i : -1;
14
Martin Liversage

Ne fais pas ça IndexOf prend un deuxième paramètre qui spécifie par où commencer.

private static int IndexOfNth(string str, char c, int n) {
    int s = -1;

    for (int i = 0; i < n; i++) {
        s = str.IndexOf(c, s + 1);

        if (s == -1) break;
    }

    return s;
}
40
Ry-

Prendre toutes ces sous-chaînes me semble plutôt inutile. Pourquoi ne pas simplement vous boucler?

private int IndexOfNth(string str, char c, int n)
{
    int remaining = n;
    for (int i = 0; i < str.Length; i++)
    {
        if (str[i] == c)
        {
            remaining--;
            if (remaining == 0)
            {
                return i;
            }
        }
    }
    return -1;
}

(J'ai envisagé d'utiliser IndexOf dans une boucle comme celle de minitech, mais j'ai décidé que c'était un peu fastidieux. Tout va bien, bien sûr. Les deux font le même travail, en ne vérifiant jamais chaque caractère une fois. Utiliser IndexOf peut être légèrement plus efficace, mais allez pour ce que vous trouvez plus lisible.)

22
Jon Skeet

J'ai tendance à penser d'abord à l'accès à la collection à l'aide de Linq.

  // 0-based n.
char result = str
  .Where(x => x == c)
  .Skip(n)
  .FirstOrDefault();

Ensuite, je vais décompresser le linq et ajouter l'itération indexée.

int foundCount = -1;
for(int position = 0; position < str.Length; position++)
{
  char x = str[position];
  if (x == c)
  {
    foundCount += 1;
    // 0-based n
    if (foundCount == n)
    {
      return position;
    }
  }
}
return -1;

Ensuite, je réfléchis à ceci: et si cette méthode renvoyait tous les index pour pouvoir les interroger:

public IEnumerable<int> IndexesOf(string str, char c)
{
  for(int position = 0; position < str.Length; position++)
  {
    char x = str[position];
    if (x == c)
    {
      yield return position;
    }
  }
}

Appelé par:

int position = IndexesOf(str, c)
 .Skip(n) // 0-based n
 .DefaultIfEmpty(-1)
 .First();
6
Amy B

Au lieu de créer un tas de sous-chaînes, pourquoi ne pas utiliser la surcharge IndexOf qui prend un index de départ ? Cela sera à la fois plus facile (vous n’aurez pas à ajuster l’index final) et plus efficace (vous n’aurez pas à allouer de sous-chaînes).

1
Dan Tao

Non testé mais quelque chose comme ça devrait marcher:

private int IndexOfNth(string str, char c, int n)
{
    int index = -1;
    while (n-- > 0)
    {
        index = str.IndexOf(c, index + 1);
        if (index == -1) break;
    }
    return index;
}
1
Chris Snowden

N'avait encore vu personne utiliser le CharEnumerator ...

    public Int32 getNthIndex(string str, char c, Int32 n)
    {
        Int32 index = 0;
        Int32 count = 0;
        if (str != null && str.Length > 0 && !(n < 1))
        {
            CharEnumerator scanner = str.GetEnumerator();
            while (scanner.MoveNext())
            {
                if (scanner.Current == c) { count++; }
                if (count == n) { break; }
                index++;
            }
            if (count < n) { index = -1; }
        }
        if (count == 0) { return -1; } else { return index; }
    }

Devrait être assez efficace, pas de sous-chaînes ou quoi que ce soit, balayez simplement la chaîne que vous avez donnée et tenez compte.

0
Kevin

Tout d’abord, j’en ferais une méthode d’extension. De cette façon, vous pouvez ignorer la vérification null par ailleurs obligatoire et l'appeler simplement sur une chaîne, comme vous le feriez avec IndexOf, IndexOfAny etc.

Ensuite, je ferais deux méthodes de cela. L'un pour récupérer tous les index (IndexesOf, ce qui peut s'avérer utile parfois) et l'autre (IndexOfNth) utilise la première fonction pour rechercher le nième index:

using System;
using System.Collections.Generic; // # Necessary for IList<int>
using System.Linq; // # Necessary for IList<int>.ToArray()

/// <summary>
/// Returns all indexes of the specified <paramref name="value"/> in the current string.
/// </summary>
/// <param name="@this">The current string this method is operating on.</param>
/// <param name="value">The value to be searched.</param>
/// <returns><c>Null</c>, if <paramref name="value"/> is <c>null</c> or empty.
/// An array holding all indexes of <paramref name="value"/> in this string,
/// else.</returns>
static int[] IndexesOf(this string @this, string value)
{
    // # Can't search for null or string.Empty, you can return what
    //   suits you best
    if (string.IsNullOrEmpty(value))
        return null;

    // # Using a list instead of an array saves us statements to resize the 
    //   array by ourselves
    IList<int> indexes = new List<int>();
    int startIndex = 0;

    while (startIndex < @this.Length)
    {
        startIndex = @this.IndexOf(value, startIndex);
        if (startIndex >= 0)
        {
            // # Add the found index to the result and increment it by length of value
            //   afterwards to keep searching AFTER the current position
            indexes.Add(startIndex);
            startIndex += value.Length;
        }
        else
        {
            // # Exit loop, if value does not occur in the remaining string
            break;
        }
    }

    // # Return an array to conform with other string operations.
    return indexes.ToArray();
}

/// <summary>
/// Returns the indexes of the <paramref name="n"/>th occurrence of the specified
/// <paramref name="value"/> in the current string.
/// </summary>
/// <param name="@this">The current string this method is operating on.</param>
/// <param name="value">The value to be searched.</param>
/// <param name="n">The 1-based nth occurrence.</param>
/// <returns><c>-1</c>, if <paramref name="value"/> is <c>null</c> or empty -or-
/// <paramref name="n"/> is less than 1.</returns>
static int IndexOfNth(this string @this, string value, int n /* n is 1-based */)
{
    // # You could throw an ArgumentException as well, if n is less than 1
    if (string.IsNullOrEmpty(value) || n < 1)
        return -1;

    int[] indexes = @this.IndexesOf(value);

    // # If there are n or more occurrences of 'value' in '@this'
    //   return the nth index.
    if (indexes != null && indexes.Length >= n)
    {
        return indexes[n - 1];
    }

    return -1;
}

Vous pouvez surcharger ceux-ci en utilisant char value au lieu de string value dans la signature et en appelant leurs homologues respectifs en passant value.ToString(). Et voilà!

Certes, ces méthodes peuvent être refactorisées, par ex. en utilisant LINQ, faites IndexesOf récursif etc.

0
Tim

Vous pouvez utiliser la méthode suivante qui renverra la nième occurrence du caractère spécifié dans la chaîne désignée.

public static int IndexOfNthCharacter(string str, int n, char c) {
    int index = -1;
    if (!str.Contains(c.ToString()) || (str.Split(c).Length-1 < n)) {
        return -1;
    }
    else {
        for (int i = 0; i < str.Length; i++) {
            if (n > 0) {            
                index++;
            }
            else {
                return index;
            }
            if (str[i] == c) {
                n--;
            }
        }
        return index;
    }
}

Notez que si le caractère que vous recherchez n'existe pas dans la chaîne recherchée ou si le nombre d'occurrences que vous recherchez est supérieur à ce qui existe dans la chaîne, cette méthode renvoie -1.

0
FrostyOnion