文字列のn番目の出現のインデックスを取得しますか?


100

明らかな組み込みメソッドがない場合を除いて、文字列内のn番目の文字列を取得する最も速い方法は何ですか?

ループの各反復で開始インデックスを更新することにより、IndexOfメソッドをループできることを理解しています。しかし、この方法でそれを行うことは私にとっては無駄に思えます。


そのために正規表現を使用するので、文字列内の文字列を照合する最適な方法を使用する必要があります。これは、可能であれば私たち全員が使用する美しいDSLの1つです。VB.net の例では、コードはC#でもほとんど同じです。
ボビウム2008年

2
「ループを続けて単純なString.IndexOfを実行する」よりも正規表現バージョンを正しく実行するのが非常に難しい場合は、かなりのお金をかけるでしょう。正規表現はその場所にありますが、より単純な代替が存在する場合は使用しないでください。
Jon Skeet、

回答:


52

それは基本的にあなたがする必要があることです-または、少なくとも、それは最も簡単な解決策です。「無駄」になるのは、n回のメソッド呼び出しのコストだけです。考えてみれば、実際にケースを2回チェックすることはありません。(IndexOfは一致が見つかるとすぐに戻り、中断したところから続行します。)


2
私はあなたの権利を推測しますが、組み込みのメソッドがあるはずですが、それは一般的な出来事だと確信しています。
PeteT 2008年

4
本当に?約13年間のJavaおよびC#の開発でこれを実行しなければならなかったことを思い出せません。それは、私が実際にそれをする必要がなかったという意味ではありませんが、覚えるだけの頻度ではありません。
Jon Skeet、

Javaのといえば、私たちは持っていますStringUtils.ordinalIndexOf()。すべてのLinqおよびその他の素晴らしい機能を備えたC#には、これに対するサポートが組み込まれていません。そして、はい、パーサーとトークナイザーを扱っている場合、サポートが不可欠です。
アニー

3
@Annie:あなたは「私たちが持っている」と言います-Apache Commonsでですか?もしそうなら、Javaと同じように簡単に独自の.NET用のサードパーティライブラリを書くことができます。したがって、.NETにはないJava標準ライブラリのようなものではありません。そしてもちろんC#では、これを拡張メソッドとして追加できますstring:)
Jon Skeet

108

正規表現/((s).*?){n}/を使用して、n番目に出現するsubstringを検索できますs

C#では、次のようになります。

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

注:Regex.Escape正規表現エンジンに特別な意味を持つ文字を検索できるように、元のソリューションに追加しました。


2
あなたはエスケープする必要がありますvalueか?私の場合は、ドットを探していたmsdn.microsoft.com/en-us/library/...
russau

3
この正規表現は、ターゲット文字列に改行が含まれている場合は機能しません。直してもらえますか?ありがとう。
Ignacio Soler Garcia、2011

N番目の一致がない場合はロックされているようです。コンマで区切られた値を1000の値に制限する必要があり、csvが少ないとハングしました。したがって、@ Yogesh-おそらく、現状では素晴らしい受け入れられた答えではありません。;)この回答のバリアントを使用して(文字列から文字列へのバージョンがここにあります)、代わりにループをn番目のカウントで停止するように変更しました
ruffin

\を検索しようとすると、渡される値は "\\"であり、regex.match関数の前の一致文字列は(()。*?){2}のようになります。私はこのエラーを受け取ります:「(()。*?){2}」の解析-)が不十分です。エラーなしでバックスラッシュを探すための正しい形式は何ですか?
RichieMN、2014

3
申し訳ありませんが、マイナーな批判:正規表現のソリューションは最適ではありません。それは、正規表現をn回目に再学習する必要があるためです。正規表現を使用すると、コードは本質的に読みにくくなります。
Mark Rogers

19

それは基本的にあなたがする必要があることです-または、少なくとも、それは最も簡単な解決策です。「無駄」になるのは、n回のメソッド呼び出しのコストだけです。考えてみれば、実際にケースを2回チェックすることはありません。(IndexOfは一致が見つかるとすぐに戻り、中断したところから続行します。)

フレームワークメソッドの形式を模倣した、拡張メソッドとしての(上記のアイデアの)再帰的な実装を次に示します。

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

また、(MBUnitの)単体テストを参考にしてください(正しいことを証明するため)。

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

Westonの素晴らしいフィードバックに基づいてフォーマットとテストケースを更新しました(Westonに感謝します)。
Tod Thomson

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

またはC#で拡張メソッドを使用

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

5
私が間違っていない場合、一致する文字列が位置0で始まる場合、このメソッドは失敗します。これは、index最初に-1に設定することで修正できます。
Peter Majeed

1
nullまたは空の文字列をチェックして一致させることもできます。そうしないとスローされますが、これは設計上の決定です。

ありがとう@PeterMajeed- "BOB".IndexOf("B")0が返された場合、この関数は次のように機能しますIndexOfOccurence("BOB", "B", 1)
PeterX 2015

2
Yoursは拡張機能を備えており、正規表現と再帰の両方を回避できるため、おそらく究極のソリューションです。どちらもコードの可読性を低下させます。
Mark Rogers

コード分析が発行されます、実際@tdyen :「パブリック・メソッドの検証引数CA1062を」場合IndexOfOccurenceかどうかをチェックしませんsですnull。そしてString.IndexOf(文字列、のInt32)がスローされますArgumentNullException場合matchですnull
DavidRR 2017年

1

おそらく、String.Split()メソッドを操作して、要求されたオカレンスが配列内にあるかどうか、インデックスが必要ないがインデックスの値がどうかを確認 するのも良いでしょう。


1

いくつかのベンチマークの後、これは最も単純で最も効率的なソリューションのようです

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

System.ValueTuple ftw:

var index = line.Select((x, i) => (x, i)).Where(x => x.Item1 == '"').ElementAt(5).Item2;

それから宿題である関数を書く


0

トッドの答えは多少単純化することができます。

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 (idx < 0 || seqNr == 1) { return idx; }

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

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

出力

1
3
5
-1

0

またはdo whileループでこのようなもの

 private static int OrdinalIndexOf(string str, string substr, int n)
    {
        int pos = -1;
        do
        {
            pos = str.IndexOf(substr, pos + 1);
        } while (n-- > 0 && pos != -1);
        return pos;
    }

-4

これはそれを行うかもしれません:

Console.WriteLine(str.IndexOf((@"\")+2)+1);

2
これがどのように機能するかわかりません。これが何をするかについての簡単な説明を含めることができますか?
ボブカウフマン
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.