Tìm tất cả các vị trí của chuỗi con trong một chuỗi lớn hơn trong C #


82

Tôi có một chuỗi lớn cần phân tích cú pháp và tôi cần tìm tất cả các trường hợp extract"(me,i-have lots. of]punctuationvà lưu chỉ mục của mỗi chuỗi vào một danh sách.

Vì vậy, giả sử đoạn chuỗi này nằm ở đầu và giữa của chuỗi lớn hơn, cả hai sẽ được tìm thấy và chỉ mục của chúng sẽ được thêm vào List. và Listsẽ chứa 0và chỉ mục khác bất kể nó sẽ là gì.

Tôi đã chơi xung quanh và string.IndexOfnó gần như là những gì tôi đang tìm kiếm, và tôi đã viết một số mã - nhưng nó không hoạt động và tôi không thể tìm ra chính xác điều gì là sai:

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 = Danh sách
  • source = Chuỗi lớn

Bất kỳ ý tưởng tốt hơn?

Câu trả lời:


141

Đây là một ví dụ về phương pháp mở rộng cho nó:

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

Nếu bạn đặt nó vào một lớp tĩnh và nhập không gian tên với using, nó sẽ xuất hiện dưới dạng một phương thức trên bất kỳ chuỗi nào và bạn chỉ có thể làm:

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

Để biết thêm thông tin về các phương pháp mở rộng, hãy http://msdn.microsoft.com/en-us/library/bb383977.aspx

Cũng giống như vậy bằng cách sử dụng một trình lặp:

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
Tại sao không sử dụng IEnumerable <int> và chỉ mục trả về lợi nhuận thay vì danh sách chỉ mục?
— m0sa

2
@ m0sa: Điểm tốt. Đã thêm một phiên bản khác chỉ để giải trí.
— Matti Virkkunen

2
@ PedroC88: Sử dụng yieldsẽ làm cho mã "lười biếng". Nó sẽ không thu thập tất cả các chỉ mục vào một danh sách trong bộ nhớ trong phương thức. Loại hiệu quả thực tế nào đối với hiệu suất phụ thuộc vào rất nhiều yếu tố.
— Matti Virkkunen

1
@Paul: "Có thể không" như trong "không được". Nếu bạn không thích từ ngữ, bạn luôn có thể đề xuất chỉnh sửa, nhưng tôi không nghĩ nó khó hiểu đến vậy.
— Matti Virkkunen

10
Chú ý! Do thêm value.Lengthbạn có thể bỏ lỡ các trận đấu lồng nhau! Ví dụ: "Đây là thử nghiệm đối sánh NestedNestedNested!" với đối sánh cho "NestedNested" sẽ chỉ tìm thấy một chỉ mục, nhưng không tìm thấy chỉ mục lồng nhau. Để khắc phục điều này, chỉ cần thêm +=1vào vòng lặp thay vì +=value.Length.
— Christoph Meißner

20

Tại sao bạn không sử dụng lớp RegEx được tích hợp sẵn:

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

Nếu bạn cần sử dụng lại biểu thức thì hãy biên dịch nó và lưu vào bộ nhớ cache ở đâu đó. Thay đổi tham số matchString thành đối sánh Regex matchExpression trong một quá tải khác cho trường hợp sử dụng lại.


Điều này không biên dịch
— Anshul

là indexesgì? Nó không được định nghĩa ở bất cứ đâu.
— Saggio

Tệ hại của tôi đó là một tàn dư. Xóa dòng đó.
— csaam

2
Lưu ý rằng phương pháp này có cùng một lỗ hổng như câu trả lời được chấp nhận. Nếu chuỗi nguồn của bạn là "ccc" và mẫu là "cc" thì nó sẽ chỉ trả về một lần xuất hiện.
— user280498

15

sử dụng LINQ

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

2
Tuy nhiên, bạn đã quên thoát chuỗi con.
— csaam

Giải pháp này thích hợp hơn với giải pháp được chấp nhận vì độ phức tạp chu kỳ thấp hơn.
— Denny Jacob

5

Phiên bản đánh bóng + hỗ trợ bỏ qua trường hợp:

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

Nó có thể được thực hiện với độ phức tạp thời gian hiệu quả bằng cách sử dụng thuật toán KMP trong O (N + M) trong đó N là độ dài của textvà M là độ dài của pattern.

Đây là cách triển khai và sử dụng:

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

và đây là một ví dụ về cách sử dụng nó:

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

Hiệu suất của điều này như thế nào so với triển khai IndexOf tối ưu, trong đó chỉ mục bắt đầu tìm kiếm được đặt thành cuối của kết quả khớp trước đó trên mỗi lần lặp?
— caesay

So sánh IndexOf với AllIndicesOf là không chính xác vì đầu ra của chúng khác nhau. Sử dụng phương pháp IndexOf trong mỗi lần lặp, làm tăng đáng kể độ phức tạp về thời gian lên O (N ^ 2 M), trong khi độ phức tạp tối ưu là O (N + M). KMP không hoạt động tương tự như cách tiếp cận ngây thơ, nó sử dụng một mảng được tính toán trước (LPS) để tránh tìm kiếm ngay từ đầu. Khuyên bạn nên đọc thuật toán KMP. Các đoạn cuối của phần "Nền" trong Wikipedia giải thích cách nó hoạt động trong 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;
}

Gọi nó như thế này:

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

1

Xin chào câu trả lời hay của @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--;
    }
}

Nhưng điều này bao gồm các trường hợp kiểm tra như AOOAOOA trong đó chuỗi con

là AOOA và AOOA

Đầu ra 0 và 3


1

Không có Regex, sử dụng kiểu so sánh chuỗi:

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)

Điều này trả về {3,8,19,22}. Mẫu trống sẽ phù hợp với tất cả các vị trí.

Đối với nhiều mẫu:

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

Điều này trả về {3, 8, 19, 22, 15, 16}


1

@csam đúng về mặt lý thuyết, mặc dù mã của anh ấy sẽ không tuân thủ và có thể bị khúc xạ thành

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

nếu mã của anh ấy không chính xác, bạn có thể đã chỉnh sửa bài đăng của anh ấy để sửa nó
— caesay 13/12/12

Tôi đã không nhận thấy điều đó. Tôi phải thừa nhận là đã miễn cưỡng làm điều đó, đề phòng trường hợp tôi sai, mặc dù tôi không nghĩ là mình làm vậy.
— arame3333 13/12/12

đó không phải là ý kiến ​​hay khi sử dụng regex cho chuỗi lớn. Cách tiếp cận cần rất nhiều bộ nhớ.
— W92,

1

Tôi nhận thấy rằng ít nhất hai giải pháp được đề xuất không xử lý các lần truy cập tìm kiếm chồng chéo. Tôi đã không kiểm tra cái được đánh dấu bằng dấu kiểm màu xanh lá cây. Đây là một trong những xử lý các lần truy cập tìm kiếm chồng chéo:

    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

Dựa trên mã tôi đã sử dụng để tìm nhiều trường hợp của một chuỗi trong một chuỗi lớn hơn, mã của bạn sẽ trông giống như sau:

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

Có hai vấn đề ở đây: Thứ nhất, bạn luôn thêm -1 vào danh sách kết quả của mình, đây không phải là kết quả hợp lệ. Thứ hai, mã không kết thúc do indexOftrả về -1 và index++. Tôi sẽ sử dụng a while (true)với a break;nếu kết quả IndexOflà -1.
— b-pos465

-1

Tôi đã tìm thấy ví dụ này và kết hợp nó vào một hàm:

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

Lợi nhuận:

53 được tìm thấy ở vị trí 2

78 được tìm thấy ở vị trí 4
78 được tìm thấy ở vị trí 7

57 không có trong 153786


1
Xin chào Mark, tôi thấy rằng bạn là người mới sử dụng stackoverflow. Câu trả lời này không thêm bất cứ điều gì cho câu hỏi cũ này, đã có nhiều câu trả lời tốt hơn. Nếu trả lời một câu hỏi như thế này trong tương lai, vui lòng cố gắng giải thích tại sao câu trả lời của bạn chứa một số thông tin hoặc giá trị chưa có trong các câu trả lời khác.
— caesay 24/02/19
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookie và Chính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.