Перевірте, чи містить рядок елемент зі списку (рядків)


155

Для наступного блоку коду:

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

Вихід:

Випадок 1:

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

Випадок 2:

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

Список (listOfStrings) може містити кілька елементів (мінімум 20), і його потрібно перевірити на тисячі рядків (наприклад, myString).

Чи є кращий (більш ефективний) спосіб написання цього коду?

Відповіді:


359

Із LINQ, і з використанням C # (я не знаю VB багато сьогодні):

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

або (коротший і ефективніший, але, можливо, менш зрозумілий):

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

Якщо ви перевіряли рівність, варто було б переглянути HashSetі т. Д., Але це не допоможе з частковими збігами, якщо ви не розділите його на фрагменти і не додасте порядок складності.


оновлення: якщо ви дійсно маєте на увазі "StartsWith", то ви можете сортувати список і помістити його в масив; потім використовуйте Array.BinarySearchдля пошуку кожного елемента - перевірте пошук, щоб переконатися, що це повністю або часткове збіг.


1
Замість контейнерів я б використав StartsWith на основі його прикладів.
tvanfosson

@tvanfosson - це залежить від того, чи повністю включені приклади, але так, я погоджуся. Зрозуміло, просто змінити.
Марк Гравелл

Наскільки цей код більш ефективний на алгоритмічному рівні? Це коротше і швидше, якщо цикли в "Будь-які" швидші, але проблема, з якою вам доведеться виконати точну відповідність багато разів, однакова.
Торстен Марек

Ви можете встановити спеціальний компаратор, якщо використовуєте набір.
Fortyrunner

Другий насправді не є більш ефективним за будь-якої вимірюваної різниці на практиці.
ICR

7

коли ви будуєте свої рядки, це має бути таким

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

5

З попереднього аналогічного запитання " Найкращий спосіб перевірити наявність існуючих рядків на великий список порівнянних " було кілька пропозицій .

Regex може бути достатнім для вашої потреби. Вираз буде об'єднанням усіх підрядних рядків-кандидатів з оператором АБО " |" АБО " . Звичайно, вам доведеться стежити за немальованими символами під час побудови виразу або не в змозі його скласти через складність чи обмеження розміру.

Інший спосіб зробити це було б побудувати структуру даних трие, щоб представити всі кандидатські підрядки (це може дещо дублювати те, що робить збірник регулярних виразів). Під час проходження кожного символу в тестовому рядку, ви створили б новий покажчик на корінь трие і пересунули наявні вказівники на відповідну дочірню (якщо така є). Ви отримуєте відповідність, коли будь-який вказівник доходить до листочка.


5

Мені сподобалась відповідь Марка, але мені потрібна відповідність вмісту, щоб бути CaSe InSenSiTiVe.

Це було рішення:

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

Чи не повинно бути> -1?
CSharped

1
@CSharped Не має значення> -1 (більше мінус 1) і> = 0 (більше або дорівнює нулю) - це одне і те ж.
WhoIsRich

2

Виходячи з ваших моделей, одним із вдосконалень було б змінити використання StartsWith замість Contains. StartsWith потрібно лише повторювати кожен рядок до тих пір, поки він не знайде першу невідповідність замість того, щоб перезапустити пошук на кожній позиції символу, коли вона знайде її.

Крім того, виходячи з ваших шаблонів, схоже, ви зможете витягти першу частину шляху для myString, а потім порівняти порівняння - шукаючи початковий шлях myString у списку рядків, а не навпаки.

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

return listOfStrings.Contains( startPath );

EDIT : Це буде ще швидше за допомогою ідеї HashSet @Marc Gravell згадує, оскільки ви можете змінити ContainsнаContainsKey і пошук буде O (1) замість O (N). Вам слід було б переконатися, що шляхи точно відповідають. Зауважте, що це не є загальним рішенням, як у @Marc Gravell, але адаптовано до ваших прикладів.

Вибачте за приклад C #. Мені не вистачило кави для перекладу на VB.


Повторно починається; можливо, попередньо сортуйте та використовуйте двійковий пошук? Це може бути швидше знову.
Марк Гравелл

2

Старе питання. Але оскільки VB.NETбула первісна вимога. Використовуючи однакові значення прийнятої відповіді:

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

1

Ви протестували швидкість?

тобто Ви створили зразковий набір даних і профілювали їх? Це може бути не так погано, як ви думаєте.

Це також може бути щось, що ви могли б породити в окрему нитку і створити ілюзію швидкості!


0

Якщо швидкість є критичною, ви можете шукати алгоритм Aho-Corasick для наборів шаблонів.

Це трійка з ланками відмов, тобто складність становить O (n + m + k), де n - довжина вхідного тексту, m сукупна довжина шаблонів і k кількість збігів. Вам просто потрібно змінити алгоритм, який потрібно припинити після того, як буде знайдено перший збіг.



0

Недолік Containsметоду полягає в тому, що він не дозволяє вказувати тип порівняння, що часто важливо при порівнянні рядків. Це завжди залежно від культури та чутливості до регістру. Тому я вважаю, що відповідь WhoIsRich є цінною, я просто хочу показати більш просту альтернативу:

listOfStrings.Any(s => s.Equals(myString, StringComparison.OrdinalIgnoreCase))
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.