Проверьте, содержит ли строка элемент из списка (строк)

156

Для следующего блока кода:

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

Есть ли лучший (более эффективный) способ написания этого кода?

user57175
источник

Ответы:

360

С LINQ и с использованием C # (я не очень много знаю VB в наши дни):

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

или (короче и эффективнее, но, возможно, менее понятно):

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

Если бы вы тестировали равенство, стоило бы посмотреть на него и HashSetт. Д., Но это не поможет с частичными совпадениями, если вы не разбили его на фрагменты и не добавили порядок сложности.


обновление: если вы действительно имеете в виду «StartsWith», то вы можете отсортировать список и поместить его в массив; затем используйте Array.BinarySearchдля поиска каждого элемента - проверьте поиск, чтобы увидеть, является ли он полным или частичным совпадением.

Марк Гравелл
источник
1
Вместо Contains я бы использовал StartsWith на основе его примеров.
tvanfosson
@tvanfosson - это зависит от того, являются ли примеры полностью инклюзивными, но да, я бы согласился. Легко изменить, конечно.
Марк Гравелл
Насколько этот код более эффективен на алгоритмическом уровне? Это короче и быстрее, если циклы в «Any» быстрее, но проблема, связанная с тем, что вы должны выполнять точное сопоставление много раз, та же самая.
Торстен Марек
Вы можете настроить собственный компаратор, если вы используете набор.
Fortyrunner
Второй не очень эффективен из-за каких-либо ощутимых различий на практике.
ICR
7

когда вы строите свои строки, это должно быть так

bool inact = new string[] { "SUSPENDARE", "DIZOLVARE" }.Any(s=>stare.Contains(s));
Simi2525
источник
5

Было несколько предложений от ранее подобного вопроса « Лучший способ проверить существующую строку по большому списку сопоставимых элементов ».

Regex может быть достаточно для вашего требования. Выражение будет объединением всех подстрок-кандидатов с оператором ИЛИ " |" между ними. Конечно, вам придется следить за неэкранированными символами при построении выражения или за невозможностью его компилировать из-за сложности или ограничений по размеру.

Другим способом сделать это было бы создание структуры данных trie для представления всех подстрок-кандидатов (это может несколько дублировать то, что делает сопоставитель регулярных выражений). По мере продвижения по каждому символу в тестовой строке вы будете создавать новый указатель на корень дерева и продвигать существующие указатели к соответствующему дочернему элементу (если есть). Вы получаете совпадение, когда любой указатель достигает листа.

Зак Скривена
источник
5

Мне понравился ответ Марка, но мне нужно, чтобы соответствие Contains имело значение CASE INSENSiTiVe.

Это было решение:

bool b = listOfStrings.Any(s => myString.IndexOf(s, StringComparison.OrdinalIgnoreCase) >= 0))
WhoIsRich
источник
Разве это не должно быть> -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.

tvanfosson
источник
Re начинается с; возможно предварительно отсортировать и использовать бинарный поиск? Это может быть быстрее снова.
Марк Гравелл
2

Старый вопрос Но так как VB.NETбыло первоначальное требование. Используя те же значения принятого ответа:

listOfStrings.Any(Function(s) myString.Contains(s))
Луис Лавьери
источник
1

Вы тестировали скорость?

т.е. вы создали образец набора данных и профилировали его? Это может быть не так плохо, как вы думаете.

Это также может быть что-то, что вы можете создать в отдельном потоке и создать иллюзию скорости!

Fortyrunner
источник
0

Если скорость критична, вы можете найти алгоритм Aho-Corasick для наборов паттернов.

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

Торстен Марек
источник
0

Недостаток Containsметода в том, что он не позволяет указать тип сравнения, что часто важно при сравнении строк. Он всегда чувствителен к культуре и регистру. Поэтому я думаю, что ответ WhoIsRich является ценным, я просто хочу показать более простую альтернативу:

listOfStrings.Any(s => s.Equals(myString, StringComparison.OrdinalIgnoreCase))
Аль Кепп
источник