web-dev-qa-db-fra.com

Obtenir l'index de la nième occurrence d'une chaîne?

À moins que je manque une méthode intégrée évidente, quel est le moyen le plus rapide d’obtenir la n ème occurrence d’une chaîne dans une chaîne?

Je me rends compte que je pourrais boucler la méthode IndexOf en mettant à jour son index de départ à chaque itération de la boucle. Mais le faire de cette façon me semble inutile.

97
PeteT

C'est essentiellement ce que vous devez faire - ou du moins, c'est la solution la plus simple. Tout ce que vous «gaspillez», c’est le coût de n invocations de méthode - vous ne contrôlerez en fait aucun cas deux fois, si vous y réfléchissez. (IndexOf reviendra dès qu'il trouvera la correspondance et vous continuerez d'avancer là où il s'était arrêté.)

51
Jon Skeet

Vous pouvez vraiment utiliser l'expression régulière /((s).*?){n}/ pour rechercher la n-ième occurrence de la sous-chaîne s

En C # cela pourrait ressembler à ceci:

public static class StringExtender
{
    public static int NthIndexOf(this string target, string value, int n)
    {
        Match m = Regex.Match(target, "((" + Regex.Escape(value) + ").*?){" + n + "}");

        if (m.Success)
            return m.Groups[2].Captures[n - 1].Index;
        else
            return -1;
    }
}

Remarque: / J'ai ajouté Regex.Escape à la solution d'origine pour permettre la recherche de caractères ayant une signification spéciale pour le moteur d'expression régulière.

101

C'est essentiellement ce que vous devez faire - ou du moins, c'est la solution la plus simple. Tout ce que vous «gaspillez», c’est le coût de n invocations de méthode - vous ne contrôlerez en fait aucun cas deux fois, si vous y réfléchissez. (IndexOf reviendra dès qu'il trouvera la correspondance et vous continuerez d'avancer là où il s'était arrêté.)

Voici l'implémentation récursive (de l'idée ci-dessus idea ) en tant que méthode d'extension, reproduisant le format de la ou des méthodes-cadres:

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);
    var idx = input.IndexOf(value, startIndex);
    if (idx == -1)
        return -1;
    return input.IndexOfNth(value, idx + 1, --nth);
}

De plus, voici quelques tests unitaires (MBUnit) qui pourraient vous aider (pour prouver que c'est correct):

using System;
using MbUnit.Framework;

namespace IndexOfNthTest
{
    [TestFixture]
    public class Tests
    {
        //has 4 instances of the 
        private const string Input = "TestTest";
        private const string Token = "Test";

        /* Test for 0th index */

        [Test]
        public void TestZero()
        {
            Assert.Throws<NotSupportedException>(
                () => Input.IndexOfNth(Token, 0, 0));
        }

        /* Test the two standard cases (1st and 2nd) */

        [Test]
        public void TestFirst()
        {
            Assert.AreEqual(0, Input.IndexOfNth("Test", 0, 1));
        }

        [Test]
        public void TestSecond()
        {
            Assert.AreEqual(4, Input.IndexOfNth("Test", 0, 2));
        }

        /* Test the 'out of bounds' case */

        [Test]
        public void TestThird()
        {
            Assert.AreEqual(-1, Input.IndexOfNth("Test", 0, 3));
        }

        /* Test the offset case (in and out of bounds) */

        [Test]
        public void TestFirstWithOneOffset()
        {
            Assert.AreEqual(4, Input.IndexOfNth("Test", 4, 1));
        }

        [Test]
        public void TestFirstWithTwoOffsets()
        {
            Assert.AreEqual(-1, Input.IndexOfNth("Test", 8, 1));
        }
    }
}
18
Tod Thomson
private int IndexOfOccurence(string s, string match, int occurence)
{
    int i = 1;
    int index = 0;

    while (i <= occurence && (index = s.IndexOf(match, index + 1)) != -1)
    {
        if (i == occurence)
            return index;

        i++;
    }

    return -1;
}

ou en C # avec des méthodes d'extension

public static int IndexOfOccurence(this string s, string match, int occurence)
{
    int i = 1;
    int index = 0;

    while (i <= occurence && (index = s.IndexOf(match, index + 1)) != -1)
    {
        if (i == occurence)
            return index;

        i++;
    }

    return -1;
}
14
Schotime

Peut-être qu’il serait également intéressant de travailler avec la méthode String.Split() et de vérifier si l’occurrence demandée se trouve dans le tableau, si vous n’avez pas besoin de l’index, mais de la valeur à 

1
user3227623

Après quelques analyses comparatives, cela semble être la solution la plus simple et la plus efficace.

public static int IndexOfNthSB(string input,
             char value, int startIndex, int nth)
        {
            if (nth < 1)
                throw new NotSupportedException("Param 'nth' must be greater than 0!");
            var nResult = 0;
            for (int i = startIndex; i < input.Length; i++)
            {
                if (input[i] == value)
                    nResult++;
                if (nResult == nth)
                    return i;
            }
            return -1;
        }
1
ShadowBeast

La réponse de Tod peut être quelque peu simplifiée.

using System;

static class MainClass {
    private static int IndexOfNth(this string target, string substring,
                                        int seqNr, int startIdx = 0)
    {
        if (seqNr < 1)
        {
            throw new IndexOutOfRangeException("Parameter 'nth' must be greater than 0.");
        }

        var idx = target.IndexOf(substring, startIdx);

        if (seqNr == 1) { return idx; }

        return target.IndexOfNth(substring, --seqNr, ++idx); // skip
    }

    static void Main () {
        Console.WriteLine ("abcbcbcd".IndexOfNth("bc", 2));
    }
}

Sortie

3

0
seron