C #에서 더 큰 문자열에서 하위 문자열의 모든 위치 찾기


83

구문 분석해야하는 큰 문자열이 있고의 모든 인스턴스를 찾아서 extract"(me,i-have lots. of]punctuation각각의 인덱스를 목록에 저장해야합니다.

따라서이 문자열 조각이 더 큰 문자열의 시작과 중간에 있다고 가정하면 둘 다 발견되고 해당 인덱스가 List. 그리고 그것은 무엇이든 List포함 0하고 다른 색인을 포함 합니다.

나는 주위 놀았 던, 그리고는 string.IndexOf않습니다 거의 내가 찾고, 나는 몇 가지 코드를 작성했습니다 무엇을 - 그러나 그것은 작동하지 않습니다와 내가 잘못 정확히 알아낼 수 없었습니다 :

List<int> inst = new List<int>();
int index = 0;
while (index < source.LastIndexOf("extract\"(me,i-have lots. of]punctuation", 0) + 39)
{
    int src = source.IndexOf("extract\"(me,i-have lots. of]punctuation", index);
    inst.Add(src);
    index = src + 40;
}
  • inst = 목록
  • source = 큰 문자열

더 좋은 아이디어가 있습니까?

답변:


142

다음은 이에 대한 확장 방법의 예입니다.

public static List<int> AllIndexesOf(this string str, string value) {
    if (String.IsNullOrEmpty(value))
        throw new ArgumentException("the string to find may not be empty", "value");
    List<int> indexes = new List<int>();
    for (int index = 0;; index += value.Length) {
        index = str.IndexOf(value, index);
        if (index == -1)
            return indexes;
        indexes.Add(index);
    }
}

이것을 정적 클래스에 넣고를 사용하여 네임 스페이스를 가져 using오면 모든 문자열에 대한 메서드로 표시되며 다음과 같이 할 수 있습니다.

List<int> indexes = "fooStringfooBar".AllIndexesOf("foo");

확장 방법에 대한 자세한 내용은 http://msdn.microsoft.com/en-us/library/bb383977.aspx 를 참조하십시오 .

반복자를 사용하여도 동일합니다.

public static IEnumerable<int> AllIndexesOf(this string str, string value) {
    if (String.IsNullOrEmpty(value))
        throw new ArgumentException("the string to find may not be empty", "value");
    for (int index = 0;; index += value.Length) {
        index = str.IndexOf(value, index);
        if (index == -1)
            break;
        yield return index;
    }
}

8
IEnumerable <int>를 사용하고 인덱스 목록 대신 반환 인덱스를 생성하지 않는 이유는 무엇입니까?
m0sa

2
@ m0sa : 좋은 지적입니다. 재미를 위해 다른 버전을 추가했습니다.
Matti Virkkunen 2010

2
@ PedroC88 : 사용 yield하면 코드가 "게으른"상태가됩니다. 메서드 내의 메모리 내 목록에 모든 인덱스를 수집하지는 않습니다. 성능에 어떤 실질적인 효과가 있는지는 많은 요인에 따라 달라집니다.
Matti Virkkunen 2013 년

1
@Paul : "안된다"에서와 같이 "안 될 수 있습니다". 문구가 마음에 들지 않으면 언제든지 편집을 제안 할 수 있지만 이해하기 어렵다고 생각합니다.
Matti Virkkunen

10
주의! 추가로 인해 value.Length중첩 된 일치를 놓칠 수 있습니다! 예 : "이것은 NestedNestedNested 일치 테스트입니다!" "NestedNested"와 일치하는 경우 중첩 된 인덱스가 아닌 하나의 인덱스 만 찾습니다. 이 단지 추가 해결하려면 +=1대신 루프를 +=value.Length.
Christoph Meißner

20

내장 된 RegEx 클래스를 사용하지 않는 이유 :

public static IEnumerable<int> GetAllIndexes(this string source, string matchString)
{
   matchString = Regex.Escape(matchString);
   foreach (Match match in Regex.Matches(source, matchString))
   {
      yield return match.Index;
   }
}

표현식을 재사용해야하는 경우 컴파일하고 어딘가에 캐시하십시오. 재사용 사례에 대한 다른 오버로드에서 matchString 매개 변수를 Regex matchExpression으로 변경합니다.


이 컴파일되지 않습니다
Anshul

무엇 indexes입니까? 어디에도 정의되어 있지 않습니다.
Saggio

내 잘못은 남은 것입니다. 해당 줄을 삭제하십시오.
csaam

2
이 방법에는 허용 된 답변과 동일한 결함이 있습니다. 소스 문자열이 "ccc"이고 패턴이 "cc"이면 한 번만 반환됩니다.
user280498

15

LINQ 사용

public static IEnumerable<int> IndexOfAll(this string sourceString, string subString)
{
    return Regex.Matches(sourceString, subString).Cast<Match>().Select(m => m.Index);
}

2
그래도 subString을 이스케이프하는 것을 잊었습니다.
csaam 2010

순환 복잡성이 낮기 때문에 허용되는 솔루션보다 선호됩니다.
Denny Jacob

5

세련된 버전 + 지원을 무시하는 케이스 :

public static int[] AllIndexesOf(string str, string substr, bool ignoreCase = false)
{
    if (string.IsNullOrWhiteSpace(str) ||
        string.IsNullOrWhiteSpace(substr))
    {
        throw new ArgumentException("String or substring is not specified.");
    }

    var indexes = new List<int>();
    int index = 0;

    while ((index = str.IndexOf(substr, index, ignoreCase ? StringComparison.OrdinalIgnoreCase : StringComparison.Ordinal)) != -1)
    {
        indexes.Add(index++);
    }

    return indexes.ToArray();
}

2

O (N + M)에서 KMP 알고리즘을 사용하여 효율적인 시간 복잡도로 수행 할 수 있습니다. 여기서 N은 길이 text이고 M은 길이입니다.pattern .

이것은 구현 및 사용법입니다.

static class StringExtensions
{
    public static IEnumerable<int> AllIndicesOf(this string text, string pattern)
    {
        if (string.IsNullOrEmpty(pattern))
        {
            throw new ArgumentNullException(nameof(pattern));
        }
        return Kmp(text, pattern);
    }

    private static IEnumerable<int> Kmp(string text, string pattern)
    {
        int M = pattern.Length;
        int N = text.Length;

        int[] lps = LongestPrefixSuffix(pattern);
        int i = 0, j = 0; 

        while (i < N)
        {
            if (pattern[j] == text[i])
            {
                j++;
                i++;
            }
            if (j == M)
            {
                yield return i - j;
                j = lps[j - 1];
            }

            else if (i < N && pattern[j] != text[i])
            {
                if (j != 0)
                {
                    j = lps[j - 1];
                }
                else
                {
                    i++;
                }
            }
        }
    }

    private static int[] LongestPrefixSuffix(string pattern)
    {
        int[] lps = new int[pattern.Length];
        int length = 0;
        int i = 1;

        while (i < pattern.Length)
        {
            if (pattern[i] == pattern[length])
            {
                length++;
                lps[i] = length;
                i++;
            }
            else
            {
                if (length != 0)
                {
                    length = lps[length - 1];
                }
                else
                {
                    lps[i] = length;
                    i++;
                }
            }
        }
        return lps;
    }

그리고 이것은 그것을 사용하는 방법의 예입니다 :

static void Main(string[] args)
    {
        string text = "this is a test";
        string pattern = "is";
        foreach (var index in text.AllIndicesOf(pattern))
        {
            Console.WriteLine(index); // 2 5
        }
    }

검색 시작 인덱스가 각 반복에서 이전 일치의 끝으로 설정되는 최적의 IndexOf 구현과 비교할 때이 성능은 어떻습니까?
caesay

IndexOf를 AllIndicesOf와 비교하는 것은 출력이 다르기 때문에 올바르지 않습니다. 각 반복에서 IndexOf 방법을 사용하면 시간 복잡도가 O (N ^ 2 M)로 엄청나게 증가하는 반면 최적의 복잡도는 O (N + M)입니다. KMP는 순진한 접근 방식과 유사하게 작동하지 않으며 처음부터 검색하지 않도록 미리 계산 된 배열 (LPS)을 사용합니다. KMP 알고리즘을 읽는 것이 좋습니다. Wikipedia의 "배경"섹션의 마지막 단락은 O (N)에서 작동하는 방법을 설명합니다.
M.Khooryani

1
public List<int> GetPositions(string source, string searchString)
{
    List<int> ret = new List<int>();
    int len = searchString.Length;
    int start = -len;
    while (true)
    {
        start = source.IndexOf(searchString, start + len);
        if (start == -1)
        {
            break;
        }
        else
        {
            ret.Add(start);
        }
    }
    return ret;
}

다음과 같이 호출하십시오.

List<int> list = GetPositions("bob is a chowder head bob bob sldfjl", "bob");
// list will contain 0, 22, 26

1

@Matti Virkkunen의 좋은 답변

public static List<int> AllIndexesOf(this string str, string value) {
    if (String.IsNullOrEmpty(value))
        throw new ArgumentException("the string to find may not be empty", "value");
    List<int> indexes = new List<int>();
    for (int index = 0;; index += value.Length) {
        index = str.IndexOf(value, index);
        if (index == -1)
            return indexes;
        indexes.Add(index);
        index--;
    }
}

그러나 이것은 AOOAOOA와 같은 테스트 케이스를 다룹니다.

AOOA 및 AOOA

출력 0 및 3


1

정규식없이 문자열 비교 유형 사용 :

string search = "123aa456AA789bb9991AACAA";
string pattern = "AA";
Enumerable.Range(0, search.Length)
   .Select(index => { return new { Index = index, Length = (index + pattern.Length) > search.Length ? search.Length - index : pattern.Length }; })
   .Where(searchbit => searchbit.Length == pattern.Length && pattern.Equals(search.Substring(searchbit.Index, searchbit.Length),StringComparison.OrdinalIgnoreCase))
   .Select(searchbit => searchbit.Index)

그러면 {3,8,19,22}가 반환됩니다. 빈 패턴은 모든 위치와 일치합니다.

여러 패턴의 경우 :

string search = "123aa456AA789bb9991AACAA";
string[] patterns = new string[] { "aa", "99" };
patterns.SelectMany(pattern => Enumerable.Range(0, search.Length)
   .Select(index => { return new { Index = index, Length = (index + pattern.Length) > search.Length ? search.Length - index : pattern.Length }; })
   .Where(searchbit => searchbit.Length == pattern.Length && pattern.Equals(search.Substring(searchbit.Index, searchbit.Length), StringComparison.OrdinalIgnoreCase))
   .Select(searchbit => searchbit.Index))

그러면 {3, 8, 19, 22, 15, 16}이 반환됩니다.


1

@csam 은 이론상 정확하지만 그의 코드는 준수 하지 않고

public static IEnumerable<int> IndexOfAll(this string sourceString, string matchString)
{
    matchString = Regex.Escape(matchString);
    return from Match match in Regex.Matches(sourceString, matchString) select match.Index;
}

그의 코드가 틀렸다면 당신은 그것을 수정하기 위해 그의 포스트를 편집 할 수있었습니다
caesay

나는 그것을 알아 차리지 못했다. 내가 틀렸다고 생각하지는 않지만 내가 틀렸을 경우를 대비해 그렇게하는 것을 꺼려한다는 것을 인정해야합니다.
arame3333

큰 문자열에 정규식을 사용하는 것은 좋지 않습니다. 이 접근 방식은 많은 메모리를 필요로합니다.
W92

1

적어도 두 개의 제안 된 솔루션이 겹치는 검색 히트를 처리하지 않는다는 것을 알았습니다. 녹색 확인 표시가있는 항목은 확인하지 않았습니다. 다음은 겹치는 검색 적중을 처리하는 것입니다.

    public static List<int> GetPositions(this string source, string searchString)
    {
        List<int> ret = new List<int>();
        int len = searchString.Length;
        int start = -1;
        while (true)
        {
            start = source.IndexOf(searchString, start +1);
            if (start == -1)
            {
                break;
            }
            else
            {
                ret.Add(start);
            }
        }
        return ret;
    }

0
public static Dictionary<string, IEnumerable<int>> GetWordsPositions(this string input, string[] Susbtrings)
{
    Dictionary<string, IEnumerable<int>> WordsPositions = new Dictionary<string, IEnumerable<int>>();
    IEnumerable<int> IndexOfAll = null;
    foreach (string st in Susbtrings)
    {
        IndexOfAll = Regex.Matches(input, st).Cast<Match>().Select(m => m.Index);
        WordsPositions.Add(st, IndexOfAll);

    }
    return WordsPositions;
}

-1

더 큰 문자열 내에서 문자열의 여러 인스턴스를 찾는 데 사용한 코드를 기반으로하면 코드는 다음과 같습니다.

List<int> inst = new List<int>();
int index = 0;
while (index >=0)
{
    index = source.IndexOf("extract\"(me,i-have lots. of]punctuation", index);
    inst.Add(index);
    index++;
}

여기에는 두 가지 문제가 있습니다. 첫째, 항상 유효한 결과가 아닌 결과 목록에 -1을 추가합니다. 둘째, indexOf-1 및 index++. 결과 가 -1 이면 a while (true)와 함께 사용합니다 . break;IndexOf
b-pos465

-1

예제를 찾아서 함수에 통합했습니다.

    public static int solution1(int A, int B)
    {
        // Check if A and B are in [0...999,999,999]
        if ( (A >= 0 && A <= 999999999) && (B >= 0 && B <= 999999999))
        {
            if (A == 0 && B == 0)
            {
                return 0;
            }
            // Make sure A < B
            if (A < B)
            {                    
                // Convert A and B to strings
                string a = A.ToString();
                string b = B.ToString();
                int index = 0;

                // See if A is a substring of B
                if (b.Contains(a))
                {
                    // Find index where A is
                    if (b.IndexOf(a) != -1)
                    {                            
                        while ((index = b.IndexOf(a, index)) != -1)
                        {
                            Console.WriteLine(A + " found at position " + index);
                            index++;
                        }
                        Console.ReadLine();
                        return b.IndexOf(a);
                    }
                    else
                        return -1;
                }
                else
                {
                    Console.WriteLine(A + " is not in " + B + ".");
                    Console.ReadLine();

                    return -1;
                }
            }
            else
            {
                Console.WriteLine(A + " must be less than " + B + ".");
               // Console.ReadLine();

                return -1;
            }                
        }
        else
        {
            Console.WriteLine("A or B is out of range.");
            //Console.ReadLine();

            return -1;
        }
    }

    static void Main(string[] args)
    {
        int A = 53, B = 1953786;
        int C = 78, D = 195378678;
        int E = 57, F = 153786;

        solution1(A, B);
        solution1(C, D);
        solution1(E, F);

        Console.WriteLine();
    }

보고:

위치 2에서 53 찾음

78 위치 4
에서 발견 78 위치 7에서 발견

57은 153786에 없습니다


1
안녕 Mark, 나는 당신이 stackoverflow를 처음 사용하는 것을 알고 있습니다. 이 답변은이 오래된 질문에 아무것도 추가하지 않으며 이미 훨씬 더 나은 답변이 있습니다. 앞으로 이와 같은 질문에 답할 경우, 귀하의 답변에 다른 답변에 아직없는 정보 나 가치가 포함 된 이유를 설명해주세요.
caesay
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.