n 번째 문자열의 인덱스를 얻습니까?


100

명백한 내장 방법이없는 경우가 아니면 문자열 내 에서 n 번째 문자열 을 얻는 가장 빠른 방법은 무엇 입니까?

루프가 반복 될 때마다 시작 인덱스를 업데이트 하여 IndexOf 메서드를 반복 할 수 있다는 것을 알고 있습니다. 그러나 이렇게하는 것은 나에게 낭비적인 것 같습니다.


정규식을 사용하면 문자열 내에서 문자열을 일치시키는 최적의 방법이 필요합니다. 이것은 가능한 한 우리 모두가 사용해야하는 아름다운 DSL 중 하나입니다. VB.net 의 예 에서는 코드가 C #에서 거의 동일합니다.
bovium

2
정규식 버전은 "루핑을 유지하고 간단한 String.IndexOf를 수행하는 것"보다 훨씬 더 어렵습니다. 정규 표현식은 그 자리를 차지하지만 더 간단한 대안이있을 때는 사용해서는 안됩니다.
Jon Skeet

답변:


52

이것이 기본적으로해야 할 일입니다. 또는 적어도 가장 쉬운 솔루션입니다. "낭비"하는 것은 n 개의 메서드 호출 비용입니다. 생각해 보면 실제로 두 번 확인하지 않을 것입니다. (IndexOf는 일치 항목을 찾는 즉시 반환되며 중단 된 위치부터 계속 진행됩니다.)


2
나는 당신의 권리라고 생각합니다, 그것은 내장 된 방법이 있어야하는 것처럼 보이지만 그것이 commmon 발생이라고 확신합니다.
PeteT

4
정말? Java 및 C # 개발의 약 13 년 동안이 작업을 수행해야했던 기억이 없습니다. 그렇다고 내가 정말로 그렇게 할 필요가 없다는 의미는 아니지만 기억할만큼 자주는 아닙니다.
Jon Skeet

Java에 대해 말하면 StringUtils.ordinalIndexOf(). 모든 Linq 및 기타 멋진 기능이 포함 된 C #에는 이에 대한 기본 지원 기능이 없습니다. 그리고 예, 파서 및 토크 나이저를 다루는 경우 지원을받는 것이 매우 중요합니다.
Annie

3
@Annie : "우리가 가지고있다"라고 말했죠-Apache Commons에서 의미합니까? 그렇다면 Java 용으로 할 수있는 것처럼 쉽게 .NET 용 타사 라이브러리를 작성할 수 있습니다. 따라서 .NET에는없는 Java 표준 라이브러리가있는 것이 아닙니다. 그리고 C #에서 물론 당신은에 확장 방법으로 추가 할 수 있습니다 string:
존 소총을

108

실제로 정규식 /((s).*?){n}/을 사용하여 substring의 n 번째 발생을 검색 할 수 있습니다 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
이 Regex는 대상 문자열에 줄 바꿈이 포함 된 경우 작동하지 않습니다. 고칠 수 있습니까? 감사.
Ignacio Soler Garcia 2011 년

N 번째 일치가 없으면 잠긴 것 같습니다. 쉼표로 구분 된 값을 1000 개 값으로 제한해야했는데 csv가 더 적을 때 중단되었습니다. 그래서 @Yogesh-아마도 좋은 대답은 아닙니다. )의 변형 사용 이 답변을 (이 문자열 버전에 문자열입니다 여기 )와 n 번째 수에서 정지 루프를 변경 하는 대신.
ruffin

\ 검색을 시도하면 전달 된 값은 "\\"이고 일치 문자열은 regex.match 함수 앞에 다음과 같이 표시됩니다 : ((). *?) {2}. 이 오류가 발생합니다 : "((). *?) {2}"-충분하지 않음)의 구문 분석. 오류없이 백 슬래시를 찾기위한 올바른 형식은 무엇입니까?
RichieMN

3
미안하지만 약간의 비판이 있습니다. 정규식 솔루션은 차선책입니다. n 번째로 정규식을 다시 배워야하기 때문입니다. 코드는 정규식을 사용할 때 본질적으로 읽기가 더 어렵습니다.
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));
        }
    }
}

Weston의 훌륭한 피드백을 기반으로 형식 및 테스트 사례를 업데이트했습니다 (Weston에게 감사드립니다).
토드 톰슨

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

2
확장 기능이 있고 정규식과 재귀를 피할 수 있기 때문에 아마도 궁극적 인 솔루션 일 것입니다.
Mark Rogers

코드 분석이 발급됩니다, 사실 @tdyen "공공 방법의 유효성 인수 CA1062을" 경우 IndexOfOccurence경우 확인하지 않습니다 s이다 null. 그리고 경우 String.indexOf (문자열, INT32)이 발생합니다 ArgumentNullException경우 match입니다 null.
DavidRR

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

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 루프로 이와 같은 것

 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.