Bir dizenin listeden (dizelerden) bir öğe içerip içermediğini kontrol edin


155

Aşağıdaki kod bloğu için:

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

Çıktı:

Dava 1:

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

Durum 2:

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

Listede (listOfStrings) birkaç öğe bulunabilir (en az 20) ve binlerce dizeyle (myString gibi) kontrol edilmesi gerekir.

Bu kodu yazmanın daha iyi (daha verimli) bir yolu var mı?

Yanıtlar:


359

LINQ ile ve C # kullanarak (Bugünlerde VB fazla bilmiyorum):

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

veya (daha kısa ve daha verimli, ancak tartışmasız daha az net):

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

Eşitliği test ediyor olsaydınız, vs'ye bakmaya değer olurdu HashSet, ancak bunu parçalara bölmedikçe ve karmaşıklık sırası eklemediğiniz sürece kısmi eşleşmelere yardımcı olmaz.


update: Eğer gerçekten "StartsWith" demek istiyorsanız, listeyi sıralayabilir ve bir diziye yerleştirebilirsiniz; ardından Array.BinarySearchher öğeyi bulmak için kullanın - tam veya kısmi bir eşleşme olup olmadığını görmek için aramaya bakın.


1
Contains yerine onun örneklerine dayanarak StartsWith kullanacağım.
tvanfosson

@tvanfosson - bu, örneklerin tamamen kapsayıcı olup olmadığına bağlıdır, ancak evet, kabul ediyorum. Elbette değiştirilmesi basit.
Marc Gravell

Bu kod algoritmik düzeyde ne kadar etkilidir? "Herhangi" deki döngüler daha hızlıysa daha kısa ve hızlıdır, ancak birçok kez tam eşleme yapmanız gereken sorun aynıdır.
Torsten Marek

Bir küme kullanıyorsanız, özel bir karşılaştırıcı kurabilirsiniz.
Fortyrunner

İkincisi, pratikte ölçülebilir bir farkla gerçekten daha verimli değildir.
ICR

7

dizelerinizi oluşturduğunuzda şöyle olmalıdır

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

5

Daha önce benzer bir " Çok sayıda karşılaştırılabilir listeye göre mevcut dizeyi test etmenin en iyi yolu " .

Normal ifade gereksiniminiz için yeterli olabilir. İfade, tüm aday alt dizelerin OR ile birleşimi olacaktır.| aralarında " işleci . Elbette, ifadeyi oluştururken kaçan karakterlere veya karmaşıklık veya boyut sınırlamaları nedeniyle derlenememesine dikkat etmeniz gerekir.

Bunu yapmanın başka bir yolu, tüm aday alt dizelerini temsil etmek için üç boyutlu bir veri yapısı oluşturmak olacaktır (bu, normal ifade eşleştiricinin yaptıklarını bir şekilde çoğaltabilir). Test dizesindeki her karakterin üzerinden geçerken, trie'nin köküne yeni bir işaretçi oluşturur ve mevcut işaretçileri uygun alt öğeye (varsa) ilerletirsiniz. Herhangi bir işaretçi bir yaprağa ulaştığında bir eşleşme elde edersiniz.


5

Marc'ın cevabını beğendim, ancak CaSe InSenSiTiVe olması için Contains eşleşmesi gerekiyordu.

Çözüm buydu:

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

-1'den büyük olmamalı mı?
CSharped

1
@CSharped> -1 (eksi 1'den fazla) ve> = 0 (sıfıra eşit veya sıfıra eşit) aynı şey değildir.
2016

2

Kalıplarınıza bağlı olarak, iyileştirmelerden biri Contains yerine StartsWith kullanarak değişecektir. StartsWith, her bir karakter bulduğu zaman her karakter konumunda aramayı yeniden başlatmak yerine ilk uyuşmazlığı bulana kadar her dizede yinelemeye ihtiyaç duyar.

Ayrıca, desenlerinize dayanarak, myString için yolun ilk bölümünü çıkarabilir, daha sonra karşılaştırmayı tersine çevirebilirsiniz - diğer taraftan ziyade dizeler listesinde myString'in başlangıç ​​yolunu ararsınız.

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

return listOfStrings.Contains( startPath );

DÜZENLEME : Bu değişebilir beri @Marc Gravell bahseder HashSet fikrini kullanarak daha hızlı olurdu ContainsiçinContainsKey ve arama O (1) yerine O (N) olacaktır. Yolların tam olarak eşleştiğinden emin olmanız gerekir. Bunun @Marc Gravell'in yaptığı gibi genel bir çözüm olmadığını, ancak örneklerinize göre uyarlandığını unutmayın.

C # örneği için üzgünüm. VB'ye çevirecek kadar kahve içmedim.


Re ile başlar; belki ön-sıralama ve ikili arama kullanın? Bu daha hızlı olabilir.
Marc Gravell

2

Eski bir soru. Ama VB.NETasıl gereklilik olduğu için. Kabul edilen cevabın aynı değerlerini kullanarak:

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

1

Hızı test ettiniz mi?

Örnek bir veri kümesi oluşturup profillendirdiniz mi? Düşündüğünüz kadar kötü olmayabilir.

Bu, ayrı bir iş parçacığına dönüşüp hız yanılsamasını verebileceğiniz bir şey olabilir!


0

Hız kritikse, desen setleri için Aho-Corasick algoritmasını aramak isteyebilirsiniz .

Başarısızlık bağlantıları olan bir üçlü , yani karmaşıklık O (n + m + k), burada n giriş metninin uzunluğu, m kalıpların kümülatif uzunluğu ve k eşleşme sayısı. İlk eşleşme bulunduktan sonra sonlandırmak için algoritmayı değiştirmeniz yeterlidir.



0

ContainsYöntemin dezavantajı, dizeleri karşılaştırırken genellikle önemli olan karşılaştırma türünü belirtmeye izin vermemesidir. Her zaman kültüre ve büyük / küçük harfe duyarlıdır. Bu yüzden WhoIsRich'in cevabının değerli olduğunu düşünüyorum, sadece daha basit bir alternatif göstermek istiyorum:

listOfStrings.Any(s => s.Equals(myString, StringComparison.OrdinalIgnoreCase))
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.