Kiểm tra xem một chuỗi có chứa một phần tử từ danh sách (của chuỗi)


154

Đối với khối mã sau:

For I = 0 To listOfStrings.Count - 1
    If myString.Contains(lstOfStrings.Item(I)) Then
        Return True
    End If
Next
Return False

Đầu ra là:

Trường hợp 1:

myString: C:\Files\myfile.doc
listOfString: C:\Files\, C:\Files2\
Result: True

Trường hợp 2:

myString: C:\Files3\myfile.doc
listOfString: C:\Files\, C:\Files2\
Result: False

Danh sách (listOfStrings) có thể chứa một số mục (tối thiểu 20) và nó phải được kiểm tra đối với hàng ngàn chuỗi (như myString).

Có cách nào tốt hơn (hiệu quả hơn) để viết mã này không?

Câu trả lời:


358

Với LINQ và sử dụng C # (Tôi không biết VB nhiều trong những ngày này):

bool b = listOfStrings.Any(s=>myString.Contains(s));

hoặc (ngắn hơn và hiệu quả hơn, nhưng có thể nói là ít rõ ràng hơn):

bool b = listOfStrings.Any(myString.Contains);

Nếu bạn đang kiểm tra sự bình đẳng, sẽ đáng để xem HashSet, v.v., nhưng điều này sẽ không giúp với các phần khớp trừ khi bạn chia nó thành các mảnh và thêm một thứ tự phức tạp.


cập nhật: nếu bạn thực sự có nghĩa là "StartsWith", thì bạn có thể sắp xếp danh sách và đặt nó vào một mảng; sau đó sử dụng Array.BinarySearchđể tìm từng mục - kiểm tra bằng cách tra cứu để xem liệu đó là một kết hợp đầy đủ hay một phần.


1
Thay vì Chứa tôi sẽ sử dụng StartsWith dựa trên các ví dụ của anh ấy.
tvanfosson

@tvanfosson - điều đó phụ thuộc vào việc các ví dụ có bao gồm đầy đủ hay không, nhưng vâng, tôi đồng ý. Đơn giản để thay đổi, tất nhiên.
Marc Gravell

Làm thế nào đến nay mã này hiệu quả hơn ở cấp độ thuật toán? Sẽ ngắn hơn và nhanh hơn nếu các vòng lặp trong "Bất kỳ" nhanh hơn, nhưng vấn đề bạn phải thực hiện khớp chính xác nhiều lần là như nhau.
Torsten Marek

Bạn có thể thiết lập một bộ so sánh tùy chỉnh nếu bạn đang sử dụng một bộ.
Fortyrunner

Thứ hai không thực sự hiệu quả hơn bởi bất kỳ sự khác biệt có thể đo lường được trong thực tế.
ICR

7

Khi bạn xây dựng chuỗi của bạn, nó sẽ như thế này

bool inact = new string[] { "SUSPENDARE", "DIZOLVARE" }.Any(s=>stare.Contains(s));

5

Có một số gợi ý từ một câu hỏi tương tự trước đó " Cách tốt nhất để kiểm tra chuỗi hiện có đối với một danh sách lớn các so sánh ".

Regex có thể đủ cho yêu cầu của bạn. Biểu thức sẽ là sự kết hợp của tất cả các chuỗi con ứng cử viên, với |toán tử OR " " giữa chúng. Tất nhiên, bạn sẽ phải coi chừng các ký tự không được giải mã khi xây dựng biểu thức hoặc không thể biên dịch nó vì các giới hạn về độ phức tạp hoặc kích thước.

Một cách khác để làm điều này là xây dựng cấu trúc dữ liệu trie để thể hiện tất cả các chuỗi con ứng cử viên (điều này có thể phần nào trùng lặp với những gì trình so khớp regex đang làm). Khi bạn bước qua từng ký tự trong chuỗi thử nghiệm, bạn sẽ tạo một con trỏ mới đến gốc của bộ ba và chuyển các con trỏ hiện có đến con thích hợp (nếu có). Bạn nhận được một trận đấu khi bất kỳ con trỏ đạt đến một chiếc lá.


4

Tôi thích câu trả lời của Marc, nhưng cần có Chứa phù hợp là CaSe InSenSiTiVe.

Đây là giải pháp:

bool b = listOfStrings.Any(s => myString.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0))

Không nên> -1?
CSharped

1
@CSharped Không quan trọng vì> -1 (nhiều hơn âm 1) và> = 0 (nhiều hơn hoặc bằng 0) là điều tương tự.
WhoIsRich

2

Dựa trên các mẫu của bạn, một cải tiến sẽ là thay đổi sang sử dụng StartsWith thay vì Chứa. StartsWith chỉ cần lặp qua từng chuỗi cho đến khi tìm thấy sự không khớp đầu tiên thay vì phải khởi động lại tìm kiếm ở mọi vị trí ký tự khi tìm thấy một chuỗi.

Ngoài ra, dựa trên các mẫu của bạn, có vẻ như bạn có thể trích xuất phần đầu tiên của đường dẫn cho myString, sau đó đảo ngược so sánh - tìm đường dẫn bắt đầu của myString trong danh sách các chuỗi thay vì ngược lại.

string[] pathComponents = myString.Split( Path.DirectorySeparatorChar );
string startPath = pathComponents[0] + Path.DirectorySeparatorChar;

return listOfStrings.Contains( startPath );

EDIT : Đây sẽ là thậm chí nhanh hơn bằng cách sử dụng ý tưởng HashSet @Marc Gravell đề cập kể từ khi bạn có thể thay đổi Containsđể ContainsKeyvà tra cứu sẽ là O (1) thay vì O (N). Bạn sẽ phải đảm bảo rằng các đường dẫn khớp chính xác. Lưu ý rằng đây không phải là một giải pháp chung như của @Marc Gravell nhưng được điều chỉnh theo các ví dụ của bạn.

Xin lỗi cho ví dụ C #. Tôi chưa có đủ cà phê để dịch sang VB.


Bắt đầu lại với; có lẽ sắp xếp trước và sử dụng tìm kiếm nhị phân? Điều đó có thể nhanh hơn một lần nữa.
Marc Gravell

2

Câu hỏi cũ. Nhưng vì VB.NETlà yêu cầu ban đầu. Sử dụng cùng các giá trị của câu trả lời được chấp nhận:

listOfStrings.Any(Function(s) myString.Contains(s))

1

Bạn đã kiểm tra tốc độ chưa?

tức là bạn đã tạo một tập hợp dữ liệu mẫu và định hình nó chưa? Nó có thể không tệ như bạn nghĩ.

Đây cũng có thể là thứ bạn có thể sinh ra thành một sợi riêng biệt và tạo ảo giác về tốc độ!


0

Nếu tốc độ là quan trọng, bạn có thể muốn tìm kiếm thuật toán Aho-Corasick cho các bộ mẫu.

Đó là một trie với các liên kết thất bại, nghĩa là độ phức tạp là O (n + m + k), trong đó n là độ dài của văn bản đầu vào, m là độ dài tích lũy của các mẫu và k số lượng khớp. Bạn chỉ cần sửa đổi thuật toán để chấm dứt sau khi tìm thấy kết quả khớp đầu tiên.



0

Hạn chế của Containsphương thức là nó không cho phép chỉ định loại so sánh thường quan trọng khi so sánh các chuỗi. Nó luôn nhạy cảm về văn hóa và phân biệt chữ hoa chữ thường. Vì vậy, tôi nghĩ rằng câu trả lời của WhoIsRich là có giá trị, tôi chỉ muốn hiển thị một thay thế đơn giản hơn:

listOfStrings.Any(s => s.Equals(myString, StringComparison.OrdinalIgnoreCase))
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 cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.