รับดัชนีของสตริงที่ n?


101

ถ้าฉันไม่มีเมธอดในตัวที่เห็นได้ชัดอะไรคือวิธีที่เร็วที่สุดในการทำให้สตริงเกิดnภายในสตริง

ฉันรู้ว่าฉันสามารถวนซ้ำเมธอดIndexOf ได้โดยการอัปเดตดัชนีเริ่มต้นในการวนซ้ำแต่ละครั้ง แต่การทำแบบนี้มันดูสิ้นเปลืองสำหรับฉัน


ฉันจะใช้นิพจน์ทั่วไปเพื่อที่คุณจะต้องจับคู่สตริงภายในสตริงให้เหมาะสมที่สุด นี่เป็นหนึ่งใน DSL ที่สวยงามที่เราทุกคนควรใช้เมื่อเป็นไปได้ ตัวอย่างใน VB.net รหัสเกือบจะเหมือนกันใน C #
bovium

2
ฉันจะวางเงินอย่างดีในเวอร์ชันนิพจน์ทั่วไปซึ่งยากกว่าที่จะทำให้ถูกต้องมากกว่า "ให้วนซ้ำและทำ String.IndexOf อย่างง่าย" นิพจน์ทั่วไปมีสถานที่ แต่ไม่ควรใช้เมื่อมีทางเลือกอื่นที่ง่ายกว่า
Jon Skeet

คล้ายกัน: stackoverflow.com/a/9908392/1305911
JNF

คำตอบ:


52

นั่นคือสิ่งที่คุณต้องทำหรืออย่างน้อยที่สุดก็เป็นวิธีแก้ปัญหาที่ง่ายที่สุด สิ่งที่คุณ "สูญเปล่า" ก็คือค่าใช้จ่ายในการเรียกใช้วิธีการ n - คุณจะไม่ได้ตรวจสอบกรณีใด ๆ ซ้ำสองถ้าคุณคิดเกี่ยวกับเรื่องนี้ (IndexOf จะกลับมาทันทีที่พบการแข่งขันและคุณจะไปต่อจากจุดที่ค้างไว้)


2
ฉันคิดว่าคุณถูกต้องดูเหมือนว่าควรจะมีวิธีการในตัวฉันแน่ใจว่ามันเป็นเหตุการณ์ที่เกิดขึ้น
PeteT

4
จริงๆ? ฉันจำไม่ได้ว่าเคยต้องทำในช่วง 13 ปีของการพัฒนา Java และ C # นั่นไม่ได้หมายความว่าฉันไม่เคยต้องทำเลย - แต่ก็ไม่บ่อยพอที่จะจำได้
Jon Skeet

การพูดของ Java StringUtils.ordinalIndexOf()เรามี C # พร้อม Linq และคุณสมบัติที่ยอดเยี่ยมอื่น ๆ ไม่มีการสนับสนุนในตัวสำหรับสิ่งนี้ และใช่จำเป็นอย่างยิ่งที่จะต้องได้รับการสนับสนุนหากคุณกำลังจัดการกับตัวแยกวิเคราะห์และโทเค็นไนเซอร์
Annie

3
@ แอนนี่: คุณพูดว่า "เรามี" - คุณหมายถึงใน Apache Commons หรือเปล่า? ถ้าเป็นเช่นนั้นคุณสามารถเขียนไลบรารีบุคคลที่สามของคุณเองสำหรับ. NET ได้อย่างง่ายดายสำหรับ Java ... ดังนั้นจึงไม่ใช่สิ่งที่ไลบรารีมาตรฐาน Java มีที่. NET และแน่นอนใน C # คุณสามารถเพิ่มเป็นวิธีการขยายได้ในstring:)
Jon Skeet

108

จริงๆคุณสามารถใช้การแสดงออกปกติ/((s).*?){n}/เพื่อค้นหา n-TH 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ในโซลูชันดั้งเดิมเพื่อให้สามารถค้นหาอักขระที่มีความหมายพิเศษกับเอนจิน regex ได้


2
คุณควรจะหนีvalue? ในกรณีของฉันฉันกำลังมองหา dot msdn.microsoft.com/en-us/library/…
russau

3
Regex นี้ใช้ไม่ได้หากสตริงเป้าหมายมีการแบ่งบรรทัด คุณช่วยแก้ไขได้ไหม ขอบคุณ.
Ignacio Soler Garcia

ดูเหมือนจะล็อกหากไม่มีการจับคู่ Nth ฉันต้องการ จำกัด ค่าที่คั่นด้วยจุลภาคไว้ที่ 1,000 ค่าและสิ่งนี้จะหยุดทำงานเมื่อ csv มีน้อยลง ดังนั้น @Yogesh - อาจไม่ใช่คำตอบที่ได้รับการยอมรับมากนัก ;) ใช้ตัวแปรของคำตอบนี้ (มีเวอร์ชันสตริงเป็นสตริงที่นี่ ) และเปลี่ยนลูปเพื่อหยุดที่จำนวนที่ nแทน
ruffin

พยายามค้นหาบน \, ค่าที่ส่งผ่านคือ "\\" และสตริงที่ตรงกันจะมีลักษณะดังนี้ก่อนฟังก์ชัน regex.match: ((). *?) {2} ฉันได้รับข้อผิดพลาดนี้: กำลังแยกวิเคราะห์ "((). *?) {2}" - ไม่เพียงพอ) รูปแบบที่ถูกต้องในการค้นหาเครื่องหมายทับกลับโดยไม่มีข้อผิดพลาดคืออะไร?
RichieMN

3
ขออภัย แต่มีข้อติชมเล็กน้อย: โซลูชัน regex นั้นไม่เหมาะสมเพราะฉันต้องเรียนรู้ regexs เป็นครั้งที่ n โดยพื้นฐานแล้วโค้ดจะอ่านยากกว่าเมื่อใช้ regexes
Mark Rogers

19

นั่นคือสิ่งที่คุณต้องทำหรืออย่างน้อยที่สุดก็เป็นวิธีแก้ปัญหาที่ง่ายที่สุด สิ่งที่คุณ "สูญเปล่า" ก็คือค่าใช้จ่ายในการเรียกใช้วิธีการ n - คุณจะไม่ได้ตรวจสอบกรณีใด ๆ ซ้ำสองถ้าคุณคิดเกี่ยวกับเรื่องนี้ (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));
        }
    }
}

ฉันได้อัปเดตการจัดรูปแบบและกรณีทดสอบตามข้อเสนอแนะที่ดีเยี่ยมของเวสตัน (ขอบคุณเวสตัน)
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
คุณอาจต้องการตรวจสอบสตริงว่างหรือว่างและจับคู่หรือจะโยน แต่นั่นเป็นการตัดสินใจออกแบบ

ขอบคุณ @PeterMajeed - ถ้า"BOB".IndexOf("B")ส่งกลับ 0 ดังนั้นควรใช้ฟังก์ชันนี้สำหรับIndexOfOccurence("BOB", "B", 1)
PeterX

2
ของคุณน่าจะเป็นทางออกที่ดีที่สุดเนื่องจากมีทั้งฟังก์ชันส่วนขยายและหลีกเลี่ยง regexs และการเรียกซ้ำซึ่งทั้งสองอย่างนี้ทำให้โค้ดอ่านได้น้อยลง
Mark Rogers

@tdyen อันที่จริงการวิเคราะห์โค้ดจะออก"CA1062: ตรวจสอบข้อโต้แย้งของวิธีการสาธารณะ"หากIndexOfOccurenceไม่ตรวจสอบว่าsเป็นnullหรือไม่ และString.IndexOf (String, Int32)จะโยนArgumentNullExceptionถ้าเป็นmatch null
DavidRR

1

อาจจะเป็นการดีที่จะทำงานกับString.Split()Method และตรวจสอบว่าเหตุการณ์ที่ร้องขออยู่ในอาร์เรย์หรือไม่หากคุณไม่ต้องการดัชนี แต่เป็นค่าที่ดัชนี


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


0

คำตอบของ Tod สามารถทำให้ง่ายขึ้นได้บ้าง

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 loop

 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
ฉันไม่เห็นว่ามันจะทำงานอย่างไร คุณช่วยอธิบายสั้น ๆ เกี่ยวกับสิ่งนี้ได้หรือไม่?
Bob Kaufman
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.