Bir dizgenin n'inci oluşumunun dizinini alın?


101

Açık bir yerleşik yöntemi kaçırmadıysam , bir dizedeki bir dizenin n. Oluşumunu elde etmenin en hızlı yolu nedir?

Döngünün her yinelemesinde başlangıç ​​dizinini güncelleyerek IndexOf yöntemini döngüye alabileceğimi fark ettim . Ama bunu bu şekilde yapmak bana savurgan geliyor.


Bunun için normal ifadeler kullanırdım, o zaman dize içindeki dizeyi en uygun şekilde eşleştirmeniz gerekir. Bu, mümkün olduğunda hepimizin kullanması gereken güzel DSL'lerden birinde. VB.net'teki bir örnek , kod C # 'da hemen hemen aynıdır.
bovium

2
Normal ifadeler sürümüne iyi para yatırırdım, doğru elde etmek "döngüye devam et ve basit String.IndexOf" yapmaktan çok daha zor. Normal ifadelerin yeri vardır, ancak daha basit alternatifler varken kullanılmamalıdır.
Jon Skeet

Yanıtlar:


52

Temelde yapmanız gereken bu - veya en azından en kolay çözüm bu. "Boşa harcayacağınız" tek şey n yöntem çağrısının maliyetidir - eğer düşünürseniz aslında herhangi bir vakayı iki kez kontrol etmeyeceksiniz. (IndexOf, eşleşmeyi bulur bulmaz geri dönecek ve kaldığı yerden devam edeceksiniz.)


2
Sanırım haklısınız, yerleşik bir yöntem olması gerekiyor gibi görünüyor, eminim bu bir ortak olaydır.
PeteT

4
Gerçekten mi? Yaklaşık 13 yıllık Java ve C # geliştirme sürecinde bunu yapmak zorunda olduğumu hiç hatırlamıyorum. Bu, bunu gerçekten yapmak zorunda olmadığım anlamına gelmez - ama hatırlayacak kadar sık ​​değil.
Jon Skeet

Java'dan bahsetmişken, var StringUtils.ordinalIndexOf(). Linq ve diğer harika özelliklerin tümü ile C #, bunun için yerleşik bir desteğe sahip değildir. Ve evet, ayrıştırıcılar ve belirteçler ile uğraşıyorsanız, desteğini almanız çok zorunludur.
Annie

3
@Annie: "Elimizde" diyorsun - Apache Commons'ta mı demek istiyorsun? Öyleyse, kendi üçüncü taraf kitaplığınızı Java için olabildiğince kolay bir şekilde .NET için yazabilirsiniz ... yani bu, Java standart kitaplığında .NET'in sahip olmadığı bir şey değildir. Ve tabii ki C # 'da bunu bir uzatma yöntemi olarak ekleyebilirsiniz string:)
Jon Skeet

108

Normal ifadeyi /((s).*?){n}/, alt dizenin n'inci oluşumunu aramak için gerçekten kullanabilirsiniz s.

C # 'da şöyle görünebilir:

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

Not:Regex.Escape Regex motoru için özel anlamı olan karakterleri aramaya izin vermek için orijinal çözüme ekledim .


2
kaçmanız gerekir mi value? Benim durumumda bir nokta arıyordum msdn.microsoft.com/en-us/library/…
russau

3
Hedef dize satır sonu içeriyorsa bu Normal ifade çalışmaz. Düzeltebilir misin? Teşekkürler.
Ignacio Soler Garcia

N. eşleşme yoksa kilitlenmiş gibi görünüyor. Virgülle ayrılmış bir değeri 1000 değerle sınırlamam gerekiyordu ve bu, csv'de daha az olduğunda askıda kaldı. Yani @Yogesh - muhtemelen olduğu gibi kabul edilen büyük bir cevap değil. ;) Bu cevabın bir varyantını kullanarak (burada dize sürümüne bir dize var ) ve bunun yerine döngü n . Sayımda duracak şekilde değiştirildi .
ruffin

\ Üzerinde arama yapmaya çalışırken, iletilen değer "\\" olur ve eşleşme dizesi regex.match işlevinden önce şuna benzer: ((). *?) {2}. Şu hatayı alıyorum: "((). *?) {2}" - Yeterli değil). Ters eğik çizgileri hatasız aramak için doğru format nedir?
RichieMN

3
Maalesef küçük bir eleştiri: normal ifade çözümleri yetersizdir, çünkü o zaman normal ifadeleri n'inci kez yeniden öğrenmem gerekir. Normal ifadeler kullanıldığında kodun okunması esasen daha zordur.
Mark Rogers

19

Temelde yapmanız gereken bu - veya en azından en kolay çözüm bu. "Boşa harcayacağınız" tek şey n yöntem çağrısının maliyetidir - eğer düşünürseniz aslında herhangi bir vakayı iki kez kontrol etmeyeceksiniz. (IndexOf, eşleşmeyi bulur bulmaz geri dönecek ve kaldığı yerden devam edeceksiniz.)

Çerçeve yöntem (ler) inin biçimini taklit eden bir genişletme yöntemi olarak yinelemeli uygulama (yukarıdaki fikrin ):

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

Ayrıca, size yardımcı olabilecek (doğru olduğunu kanıtlamak için) bazı (MBUnit) birim testleri şunlardır:

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

Biçimlendirme ve test durumlarımı Weston'ın harika geri bildirimlerine göre güncelledim (teşekkürler 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;
}

veya C # uzantı yöntemleriyle

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
Eğer yanılmıyorsam, eşleşecek dizge 0 konumunda başlarsa bu yöntem başarısız olur ve bu indexbaşlangıçta -1 olarak ayarlanarak düzeltilebilir .
Peter Majeed

1
Ayrıca boş veya boş dizeleri kontrol etmek isteyebilirsiniz ve eşleşir veya atar, ancak bu bir tasarım kararıdır.

Teşekkürler @PeterMajeed - "BOB".IndexOf("B")0 döndürürse, bu işlev için de öyle olmalıdırIndexOfOccurence("BOB", "B", 1)
PeterX

2
Hem bir uzantı işlevine sahip olduğu hem de her ikisi de kodu daha az okunabilir kılan düzenli ifadelerden ve özyinelemeden kaçındığı için muhtemelen nihai çözüm sizinki.
Mark Rogers

Kod Analizi çıkaracağını Nitekim @tdyen : "kamu yöntemlerden Doğrula argümanları CA1062" eğer IndexOfOccurencekontrol etmez solduğunu null. Ve String.indexOf (String, Int32) atacağım ArgumentNullExceptioneğer matcholduğunu null.
DavidRR

1

Belki de String.Split()Yöntem ile çalışmak ve dizinde istenen oluşumun dizide olup olmadığını kontrol etmek, dizine ihtiyacınız yoksa, ancak dizindeki değere ihtiyacınız varsa iyi olur.


1

Bazı karşılaştırmalardan sonra, bu en basit ve en verimli çözüm gibi görünüyor

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;

ondan bir işlev yazmak ev ödevidir


0

Tod'un cevabı bir şekilde basitleştirilebilir.

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

Çıktı

1
3
5
-1

0

Veya do while döngüsünde bunun gibi bir şey

 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

Bunu yapabilir:

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

2
Bunun nasıl çalışacağını anlamıyorum. Bunun ne yaptığına dair kısa bir açıklama ekleyebilir misiniz?
Bob Kaufman
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.