Periksa apakah string berisi elemen dari daftar (string)

155

Untuk blok kode berikut:

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

Outputnya adalah:

Kasus 1:

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

Kasus 2:

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

Daftar (listOfStrings) dapat berisi beberapa item (minimal 20) dan harus diperiksa terhadap ribuan string (seperti myString).

Apakah ada cara yang lebih baik (lebih efisien) untuk menulis kode ini?

pengguna57175
sumber

Jawaban:

359

Dengan LINQ, dan menggunakan C # (saya tidak tahu banyak tentang VB hari ini):

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

atau (lebih pendek dan lebih efisien, tetapi bisa dibilang kurang jelas):

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

Jika Anda menguji kesetaraan, itu akan layak dilihat di HashSetdll, tetapi ini tidak akan membantu dengan kecocokan sebagian kecuali Anda membaginya menjadi fragmen dan menambahkan urutan kompleksitas.


pembaruan: jika Anda benar-benar bermaksud "StartsWith", maka Anda bisa mengurutkan daftar dan menempatkannya ke dalam array; kemudian gunakan Array.BinarySearchuntuk menemukan setiap item - periksa dengan pencarian untuk melihat apakah itu cocok atau sebagian cocok.

Marc Gravell
sumber
1
Alih-alih Berisi saya akan menggunakan StartsWith berdasarkan contoh-contohnya.
tvanfosson
@vanfosson - itu tergantung pada apakah contoh-contohnya sepenuhnya inklusif, tapi ya, saya setuju. Mudah diubah, tentu saja.
Marc Gravell
Sejauh mana kode ini lebih efisien pada level algoritmik? Lebih pendek dan lebih cepat jika loop di "Any" lebih cepat, tetapi masalah yang Anda harus lakukan pencocokan tepat berkali-kali adalah sama.
Torsten Marek
Anda dapat mengatur pembanding khusus jika Anda menggunakan satu set.
Fortyrunner
Yang kedua tidak terlalu efisien dengan perbedaan terukur dalam praktik.
ICR
7

ketika Anda membangun string Anda harus seperti ini

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

Ada sejumlah saran dari pertanyaan serupa sebelumnya " Cara terbaik untuk menguji string yang ada terhadap daftar besar yang sebanding ".

Regex mungkin cukup untuk kebutuhan Anda. Ekspresi akan menjadi gabungan dari semua substring kandidat, dengan |operator " " OR di antara mereka. Tentu saja, Anda harus berhati-hati terhadap karakter yang tidak terhindar ketika membangun ekspresi, atau kegagalan untuk mengkompilasinya karena kompleksitas atau keterbatasan ukuran.

Cara lain untuk melakukan ini adalah dengan membangun struktur data trie untuk mewakili semua substring kandidat (ini mungkin agak menduplikasi apa yang dilakukan pencocokan regex). Saat Anda melangkah melalui setiap karakter dalam string tes, Anda akan membuat pointer baru ke akar trie, dan memajukan pointer yang ada ke anak yang sesuai (jika ada). Anda mendapatkan kecocokan saat pointer mana saja mencapai daun.

Zach Scrivena
sumber
5

Saya menyukai jawaban Marc, tetapi membutuhkan Contains yang cocok untuk menjadi CaSe InSenSiTiVe.

Ini solusinya:

bool b = listOfStrings.Any(s => myString.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0))
WhoIsRich
sumber
Bukankah seharusnya> -1?
CSharped
1
@CSharped Tidak masalah sebagai> -1 (lebih dari minus 1) dan> = 0 (lebih atau sama dengan nol) adalah hal yang sama.
WhoIsRich
2

Berdasarkan pola Anda, satu peningkatan adalah mengubah menggunakan StartsWith alih-alih Berisi. Mulai Dengan hanya perlu beralih melalui setiap string sampai menemukan ketidakcocokan pertama daripada harus memulai kembali pencarian di setiap posisi karakter ketika menemukan satu.

Juga, berdasarkan pola Anda, sepertinya Anda mungkin dapat mengekstrak bagian pertama dari jalur untuk myString, lalu membalikkan perbandingan - mencari jalur awal myString dalam daftar string daripada sebaliknya.

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

return listOfStrings.Contains( startPath );

EDIT : Ini akan lebih cepat menggunakan ide HashSet @Marc Gravell menyebutkan karena Anda dapat mengubah Containske ContainsKeydan pencarian akan menjadi O (1), bukan O (N). Anda harus memastikan bahwa jalurnya sama persis. Perhatikan bahwa ini bukan solusi umum seperti @Marc Gravell tetapi dirancang untuk contoh Anda.

Maaf untuk contoh C #. Saya belum punya cukup kopi untuk diterjemahkan ke VB.

tvanfosson
sumber
Kembali dimulai dengan; mungkin pra-urutkan dan gunakan pencarian biner? Itu mungkin lebih cepat lagi.
Marc Gravell
2

Pertanyaan lama. Tapi karena VB.NETitu persyaratan aslinya. Menggunakan nilai yang sama dari jawaban yang diterima:

listOfStrings.Any(Function(s) myString.Contains(s))
Luis Lavieri
sumber
1

Sudahkah Anda menguji kecepatannya?

yaitu, sudahkah Anda membuat set sampel data dan membuat profil? Mungkin tidak seburuk yang Anda pikirkan.

Ini juga bisa menjadi sesuatu yang bisa Anda buat menjadi utas terpisah dan memberikan ilusi kecepatan!

Fortyrunner
sumber
0

Jika kecepatan sangat penting, Anda mungkin ingin mencari algoritma Aho-Corasick untuk set pola.

Ini adalah trie dengan tautan kegagalan, yaitu kompleksitas adalah O (n + m + k), di mana n adalah panjang teks input, m panjang kumulatif pola, dan k jumlah kecocokan. Anda hanya perlu memodifikasi algoritme untuk diakhiri setelah kecocokan pertama ditemukan.

Torsten Marek
sumber
0
myList.Any(myString.Contains);
WIRN
sumber
0

Kelemahan dari Containsmetode ini adalah tidak memungkinkan untuk menentukan tipe perbandingan yang sering penting ketika membandingkan string. Itu selalu peka terhadap budaya dan peka terhadap huruf besar-kecil. Jadi saya pikir jawaban WhoIsRich sangat berharga, saya hanya ingin menunjukkan alternatif yang lebih sederhana:

listOfStrings.Any(s => s.Equals(myString, StringComparison.OrdinalIgnoreCase))
Kepp Al
sumber