Как проще всего сравнивать строки в Python без учета регистра?
Конечно, можно сделать (str1.lower () <= str2.lower ()) и т. д., Но это создало две дополнительные временные строки (с очевидными накладными расходами alloc / g-c).
Думаю, я ищу эквивалент stricmp () в C.
[Требуется еще немного контекста, поэтому я продемонстрирую тривиальный пример:]
Предположим, вы хотите отсортировать длинный список строк. Вы просто выполняете theList.sort (). Это сравнение строк O (n * log (n)) без управления памятью (поскольку все строки и элементы списка - это своего рода умные указатели). Вы счастливы.
Теперь вы хотите сделать то же самое, но не обращайте внимания на регистр (давайте упростим и скажем все строки в формате ascii, поэтому проблемы с локалью можно игнорировать). Вы можете выполнить theList.sort (key = lambda s: s.lower ()), но тогда вы вызовете два новых распределения на сравнение, плюс нагрузка на сборщик мусора дублированными (опущенные) струны. Каждый такой шум управления памятью на порядки медленнее, чем простое сравнение строк.
Теперь, используя функцию, аналогичную stricmp (), вы выполняете: theList.sort (cmp = stricmp) и он такой же быстрый и удобный для памяти, как theList.sort (). Вы снова счастливы.
Проблема в том, что любое сравнение на основе Python без учета регистра включает неявную строку дублирования, поэтому я ожидал найти сравнения на основе C (возможно, в строке модуля).
Ничего подобного найти не удалось, отсюда и вопрос. (Надеюсь, это проясняет вопрос).
ваши предположения ошибочны. list.sort () с ключом = означает ли нет «два новых выделения на одно сравнение». (list.sort с cmp =, с другой стороны, делает вызывает аргумент для каждого сравнения)
попытался переименовать вопрос из Ignore case in python strings
в What's closest to stricmp in Python for 7-bit ascii string comparison?
, чтобы более точно отразить фактический вопрос оператора. основная проблема: unicode - это тоже «строка», но этот вопрос может ошибиться с полностью; см. комментарии tchrist
связанные: Как мне свернуть строку в Python 2?
Я почти уверен, что вам нужно либо использовать .lower (), либо использовать регулярное выражение. Мне неизвестна встроенная функция сравнения строк без учета регистра.
Вот как вы это сделаете с re:
import re
p = re.compile('^hello$', re.I)
p.match('Hello')
p.match('hello')
p.match('HELLO')
Регулярные выражения без учета регистра могут использоваться только для проверки равенства (True / False), но не для сравнения (меньше / равно / больше)
Вы можете создать подкласс str
и создать свой собственный строковый класс без учета регистра, но ИМХО, это было бы крайне неразумно и создаст гораздо больше проблем, чем оно того стоит.
Для случайных или даже повторяющихся сравнений несколько дополнительных строковых объектов не должны иметь значения, если это не произойдет во внутреннем цикле вашего основного кода или у вас недостаточно данных, чтобы действительно заметить влияние на производительность. Посмотрите, если вы это сделаете: делать что-то «глупым» способом будет гораздо менее глупо, если вы также будете делать это меньше.
Если вы серьезно хотите продолжать сравнивать много-много текста без учета регистра, вы можете каким-то образом держать под рукой строчные версии строк, чтобы избежать завершения и повторного создания, или нормализовать весь набор данных в нижнем регистре. Конечно, это зависит от размера набора данных. Если имеется относительно немного иголок и большой стог сена, замена иголок скомпилированными объектами регулярного выражения является одним из решений. Если Трудно сказать, не увидев конкретного примера.
Нет встроенного эквивалента той функции, которую вы хотите.
Вы можете написать свою собственную функцию, которая преобразует в .lower () каждый символ за раз, чтобы избежать дублирования обеих строк, но я уверен, что это будет очень интенсивно и крайне неэффективно.
Если вы не работаете с очень длинными строками (настолько длинными, что при дублировании могут возникнуть проблемы с памятью), я бы оставил это простым и использовал
str1.lower() == str2.lower()
Ты будешь в порядке
«Никогда не говори никогда» :) «Нет встроенного эквивалента» является абсолютным; «Я не знаю встроенного аналога» было бы ближе к истине. locale.strcoll с учетом регистра LC_COLLATE (как en_US) является встроенным.
Это неверный ответ. Единственный правильный способ - str1.fold() == str2.fold()
, но для этого требуется расширение класса строки Python по умолчанию, который поддерживает полный регистр Unicode строки. Это недостающая функция.
@tchrist unclearr: есть ли такое расширение?
Используете ли вы это сравнение в очень часто выполняемом пути высокопроизводительного приложения? Или вы запускаете это на строках размером в мегабайты? Если нет, то вам не стоит беспокоиться о производительности и просто использовать метод .lower ().
Следующий код демонстрирует, что выполнение сравнения без учета регистра путем вызова .lower () для двух строк, каждая из которых имеет размер почти мегабайт, занимает около 0,009 секунды на моем настольном компьютере с частотой 1,8 ГГц:
from timeit import Timer
s1 = "1234567890" * 100000 + "a"
s2 = "1234567890" * 100000 + "B"
code = "s1.lower() < s2.lower()"
time = Timer(code, "from __main__ import s1, s2").timeit(1000)
print time / 1000 # 0.00920499992371 on my machine
Если это действительно чрезвычайно важный и критичный для производительности раздел кода, то я рекомендую написать функцию на C и вызывать ее из кода Python, поскольку это позволит вам выполнять действительно эффективный поиск без учета регистра. Подробности о написании модулей расширения C можно найти здесь: https://docs.python.org/exnding/exnding.html
вот как вы передаете материал в класс Timer. спасибо за решение совсем другого моего зуда :)
Это совершенно неверно. Невозможно обнаружить, что ΣΤΙΓΜΑΣ и στιγμας одинаковы без учета регистра. Вы не должны использовать casemapping для сравнения регистра в Unicode. Вы должны использовать casefolding. Это разные вещи. Σ, σ, ς все такие же, как и S, ſ, с (что это вообще с s? :) и Μ, μ, µ. Есть бесчисленное множество других подобных обстоятельств, например, как weiß, WEIẞ, weiss, WEISS все одинаковы, или действенно, действенно. Вы необходимо использовать casefolds,, потому что casemap не работают.
Я не могу найти другого встроенного способа сравнения без учета регистра: рецепт поваренной книги питона использует lower ().
Однако вы должны быть осторожны при использовании более низкого значения для сравнения из-за Турецкая проблема I. К сожалению, Python не очень хорошо справляется с турецкими языками. ı преобразуется в I, но не преобразуется в ı. İ преобразуется в i, но i не преобразуется в İ.
Как вы видели, Python не очень надежно обрабатывает Unicode. В картах дела на это не обращают внимания. Очень грустный.
Вы можете перевести каждую строку в нижний регистр один раз - лениво, только когда вам это нужно, или как предварительный переход к сортировке, если вы знаете, что сортируете всю коллекцию строк. Есть несколько способов привязать этот ключ сравнения к фактическим сортируемым данным, но эти методы следует рассматривать в отдельном выпуске.
Обратите внимание, что этот метод можно использовать не только для обработки проблем с верхним / нижним регистром, но и для других типов сортировки, таких как сортировка по локали или сортировка заголовков «в стиле библиотеки», которая игнорирует ведущие статьи и иным образом нормализует данные перед их сортировкой.
вопрос является более общим, чем сам пример (на самом деле, в реальных сценариях жизни вы не хотите, чтобы вас беспокоили, прикрепляя строчную версию к каждой строке, которая может понадобиться icmp () позже), но даже в этом тривиальном примере вы не используете не хочу удваивать память только для того, чтобы иметь возможность сортировать ...
В ответ на ваше разъяснение ...
Вы можете использовать ctypes для выполнения функции c "strcasecmp". Ctypes включен в Python 2.5. Он предоставляет возможность обращаться к dll и разделяемым библиотекам, таким как libc. Вот краткий пример (Python в Linux; см. Ссылку для справки по Win32):
from ctypes import *
libc = CDLL("libc.so.6") // see link above for Win32 help
libc.strcasecmp("THIS", "this") // returns 0
libc.strcasecmp("THIS", "THAT") // returns 8
может также захотеть сослаться на документация strcasecmp
Не совсем уверен, что это быстрее или медленнее (не тестировал), но это способ использовать функцию C для сравнения строк без учета регистра.
~~~~~~~~~~~~~~
Код ActiveState - рецепт 194371: строки без учета регистра - это рецепт создания строкового класса без учета регистра. Это может быть немного излишне kill для чего-то быстрого, но может предоставить вам общий способ обработки нечувствительных к регистру строк, если вы планируете часто их использовать.
Я хорошо знаю этот рецепт, но за кулисами у него просто есть дубликат в нижнем регистре для каждой строки, что нехорошо (как объяснено в тривиальном примере, который я добавил)
Решение ctype - это то, что я искал, спасибо. Для справки, вот код win32: from ctypes import * clib = cdll.LoadLibrary ("msvcrt") theList = ["abc", "ABC", "def", "DEF"] * 1000000 theList.sort (cmp = clib._stricmp)
это намного медленнее. смотри мой ответ!
Я считаю, что это дает неправильный ответ для строк с нулями.
Нет, это неправильно. Единственное правильное решение - сравнить их регистры Unicode. Иначе облажаетесь.
@tchrist в качестве примечания, строка 'unicode' не является предметом вопроса. Тем не менее, ваш комментарий, который, кажется, указывает любому, кто приходит сюда с надеждой на Unicode в правильном направлении, неоценим. Я пытаюсь отредактировать заголовок вопроса, чтобы более точно отразить его.
Для сортировки списков значений с использованием дорогостоящих в вычислении ключей рекомендуется использовать так называемый «декорированный шаблон». Он состоит просто в построении списка кортежей (ключей, значений) из исходного списка и сортировке этого списка. Затем просто удалить ключи и получить список отсортированных значений:
>>> original_list = ['a', 'b', 'A', 'B']
>>> decorated = [(s.lower(), s) for s in original_list]
>>> decorated.sort()
>>> sorted_list = [s[1] for s in decorated]
>>> sorted_list
['A', 'a', 'B', 'b']
Или, если вам нравятся однострочные:
>>> sorted_list = [s[1] for s in sorted((s.lower(), s) for s in original_list)]
>>> sorted_list
['A', 'a', 'B', 'b']
Если вы действительно беспокоитесь о стоимости вызова lower (), вы можете просто хранить кортежи (пониженная строка, исходная строка) везде. Кортежи - это самый дешевый вид контейнеров в Python, они также являются хешируемыми, поэтому их можно использовать в качестве ключей словаря, членов набора и т. д.
кортежи дешевы, но дублирование строк - нет ...
это также то, что делает сортировка python с аргументом key =.
Это 7-битный образ мышления, который совершенно не подходит для данных Unicode. Вы должны использовать либо полный регистр Unicode, либо основную силу сортировки в соответствии с алгоритмом сортировки Unicode. Да, в любом случае это означает новые копии строки, но, по крайней мере, тогда вы можете провести двоичное сравнение вместо того, чтобы рыться в таблицах для каждой кодовой точки.
Ваш вопрос подразумевает, что вам не нужен Unicode. Попробуйте следующий фрагмент кода; если это сработает для вас, все готово:
Python 2.5.2 (r252:60911, Aug 22 2008, 02:34:17)
[GCC 4.3.1] on linux2
Type "help", "copyright", "credits" or "license" for more information.
>>> import locale
>>> locale.setlocale(locale.LC_COLLATE, "en_US")
'en_US'
>>> sorted("ABCabc", key=locale.strxfrm)
['a', 'A', 'b', 'B', 'c', 'C']
>>> sorted("ABCabc", cmp=locale.strcoll)
['a', 'A', 'b', 'B', 'c', 'C']
Уточнение: в случае, если это не очевидно на первый взгляд, locale.strcoll кажется вам нужной функцией, избегая "повторяющихся" строк str.lower или locale.strxfrm.
Глобальная настройка locale.setlocale () явно излишняя (слишком глобальная).
Я не знаю, что такое «очевидное излишество», а «глобальный» параметр может быть настолько локализован, насколько вам нравится (кроме случаев, когда вы работаете с потоками и вам нужно, чтобы некоторые потоки были локализованы, а некоторые нет по какой-то причине).
Это единственное решение, которое дает результаты, которые могут правильно взаимодействовать с нечувствительными к регистру утилитами, такими как сортировка Unix с параметром -f. Например, str.lower заставляет A_ сортировать перед AA.
Вы не можете использовать локали POSIX и strcoll, потому что они ненадежны для разных платформ. Вы должны использовать регистр Unicode, который гарантированно будет работать одинаково везде.
Вот тест, показывающий, что использование str.lower
быстрее, чем предложенный метод принятого ответа (libc.strcasecmp
):
#!/usr/bin/env python2.7
import random
import timeit
from ctypes import *
libc = CDLL('libc.dylib') # change to 'libc.so.6' on linux
with open('/usr/share/dict/words', 'r') as wordlist:
words = wordlist.read().splitlines()
random.shuffle(words)
print '%i words in list' % len(words)
setup = 'from __main__ import words, libc; gc.enable()'
stmts = [
('simple sort', 'sorted(words)'),
('sort with key=str.lower', 'sorted(words, key=str.lower)'),
('sort with cmp=libc.strcasecmp', 'sorted(words, cmp=libc.strcasecmp)'),
]
for (comment, stmt) in stmts:
t = timeit.Timer(stmt=stmt, setup=setup)
print '%s: %.2f msec/pass' % (comment, (1000*t.timeit(10)/10))
типичное время на моей машине:
235886 words in list
simple sort: 483.59 msec/pass
sort with key=str.lower: 1064.70 msec/pass
sort with cmp=libc.strcasecmp: 5487.86 msec/pass
Итак, версия с str.lower
не только самая быстрая, но и самая портативная и питоничная из всех предлагаемых здесь решений.
Я не анализировал использование памяти, но в оригинальном плакате все еще не было веских причин для беспокойства. Кроме того, кто сказал, что вызов модуля libc не дублирует никаких строк?
NB: строковый метод lower()
также имеет то преимущество, что он зависит от локали. Что-то, что вы, вероятно, не поймете при написании собственного «оптимизированного» решения. Тем не менее, из-за ошибок и отсутствующих функций в Python такое сравнение может дать неверные результаты в контексте Юникода.
Конечно, память - это проблема, поскольку более 99,9% времени .lower () приходится на выделение памяти. Кроме того, на машинах (Windows), которые я проверял, подход key = _stricmp был в 4-5 раз быстрее и без памяти.
В 4-5 раз быстрее, чем .lower-method, означает, что он в 2 раза быстрее, чем простой случай сортировки. как это может быть?!?
@hop все слова в списке слов, который вы проверяете, уже в нижнем регистре. Это может дать вам результаты, далекие от результатов Павла.
@hop снова: забудь. Я попытался отсортировать тот же список с помощью str.upper, и результаты примерно такие же.
@virgil: также, в моем регионе более двух третей всех слов начинаются с заглавной буквы;)
@hop представленный вами анализ действительно хорош. это показывает (особенно мне), что иногда лучше не считать циклы и байты в уме.
Вопрос для уточнения: что вы подразумеваете под str.lower
, зависит от локали? Я спрашиваю, потому что в вашем ответе нет другого упоминания о вещах, связанных с локалью, например, позвонив в locale.setlocale
.
@ ΤΖΩΤΖΙΟΥ: из документации: «Для 8-битных строк этот метод зависит от локали». Мне не нужно явно устанавливать locale, так как это обрабатывается моей системной конфигурацией.
@hop: мой собственный тест показывает, что метод libc в 3 раза быстрее, чем .lower (). На самом деле вы демонстрируете, что сортировка с помощью ключа более эффективна, чем сортировка с помощью cmp.
@hop: тогда ваше утверждение "str.lower - самый быстрый на сегодняшний день" вводит в заблуждение.
Это тоже неправильно, потому что, если вы не используете регистр Unicode, вы получите самые разные ответы.
@tchrist: ну да, но 1) это относится ко всем решениям и 2) с помощью strcasecmp вы даже не можете решить эту проблему.
@hop: проверьте bugs.python.org на наличие ошибок Unicode. Я только что представил кучу тестовых примеров, показывающих, где Python дает сбой, не используя casefolding. Если мне нужно выбирать между быстрым и правильным, я знаю, какой из них выберу каждый раз.
@tchrist: я не совсем понимаю, к чему вы идете. какое конкретное предлагаемое решение?
@hop: должен быть строковый метод, обеспечивающий регистр Unicode, тогда вы просто выполняете простое сравнение. Не связывайтесь с уродливыми антипортативными локали, черт возьми, они так же плохи, как страницы кода GAG в µsløth. string.lower()
, string.title()
, string.upper()
у вас уже есть, но вам нужен string.fold()
, чтобы вы могли использовать str1.fold() == str2.fold()
. Его нет в Python API. Обратите внимание, как это работает в отделении интенсивной терапии. Потому что это то, что вам нужно; это как, вы выполняете нечувствительные к регистру сравнения текста Unicode, не углубляясь в алгоритм сортировки Unicode.
@tchrist: есть ли еще решение настоящий, которое вы можете предложить? некоторые из нас должны отправить код сейчас же. Я очень ценю ваши знания и участие в этой теме, но использование key=
с вашим fold()
все равно лучше, чем использование cmp=
с вашим fold()
, а PyICU все равно будет лучше, чем взломать что-то вместе с ctypes, и что? является? ваш? точка?
@tchrist: Ладно, раз уж тебе нужно было перейти на личную тему, это последнее, что я скажу по этому поводу. Вопрос касался проблем с распределением памяти, этот ответ - о том, чтобы показать, что оптимизация для дублирования строк не улучшит, а снизит производительность, и у ВАС даже нет ни малейшего доказательства того, что во всем этом участвует человеческий язык. OP может сравнивать строки base64 для всего, что вы знаете. Так что перестань быть таким ханжеским, пожалуйста.
Вероятно, лучше не называть чей-то ответ «глупым».
@DutrowLLC: да, наверное, ты не прав.
Этот тест, похоже, полностью игнорирует время, которое версия .lower()
тратит на сборщик мусора .. верно?
Включение gc еще больше ухудшает относительную производительность метода libc. Я обновил свой ответ, чтобы запустить timeit () с включенным gc.
Просто используйте метод str().lower()
, если не важна высокая производительность - в этом случае напишите этот метод сортировки как расширение C.
«Как написать расширение Python» кажется неплохим вступлением ..
Что еще более интересно, Это руководство сравнивает использование библиотеки ctypes с написанием внешнего модуля C (ctype значительно медленнее, чем расширение C).
Когда что-то плохо поддерживается в стандартной библиотеке, я всегда ищу пакет PyPI. С виртуализацией и повсеместным распространением современных дистрибутивов Linux я больше не избегаю расширений Python. PyICU, похоже, отвечает всем требованиям: https://stackoverflow.com/a/1098160/3461
Теперь есть вариант - чистый питон. Хорошо протестировано: https://github.com/jtauber/pyuca
Старый ответ:
Мне нравится решение с регулярными выражениями. Вот функцию, которую вы можете скопировать и вставить в любую функцию благодаря поддержке блочной структуры Python.
def equals_ignore_case(str1, str2):
import re
return re.match(re.escape(str1) + r'\Z', str2, re.I) is not None
Так как я использовал совпадение вместо поиска, мне не нужно было добавлять курсор (^) к регулярному выражению.
Примечание: Проверяет только равенство, что иногда бывает необходимо. Я бы также не стал говорить, что мне это нравится.
[Я бы хотел, чтобы для этого был виртуальный штамп] Не используйте $
, используйте \Z
. Прочтите фантастическое руководство, чтобы узнать, что на самом деле делает $
; не полагайтесь на легенды, догадки или что-то еще.
Я изменил это. Я также включил функцию вики сообщества для своего ответа. Спасибо.
Подходит только для проверки равенства, что не совсем то же самое, что сравнение двух строк и определение того, является ли одна из них меньше, равна или больше другой.
@martineau, спасибо. Я добавил примечание, а также немного поискал и нашел решение, которое, как мне кажется, мне было бы более комфортно, и обновил свой ответ с его помощью. Однако это не полный ответ. Надеюсь, кто-нибудь (я, если я дойду до этого) узнает, как работает одна из этих библиотек, и предоставит образец кода.
Да, похоже, что расширение pyuca (Python Unicode Collation Algorithm) может работать, потому что в отчете, на котором оно основано - Алгоритм сортировки Unicode (UCA) - говорится: «Различия в регистре (верхний и нижний регистры) обычно игнорируются».
В этом вопросе задаются две разные вещи:
Поскольку на № 1 уже дан очень хороший ответ (например: str1.lower () <str2.lower ()), я отвечу на № 2.
def strincmp(str1, str2, numchars=None):
result = 0
len1 = len(str1)
len2 = len(str2)
if numchars is not None:
minlen = min(len1,len2,numchars)
else:
minlen = min(len1,len2)
#end if
orda = ord('a')
ordz = ord('z')
i = 0
while i < minlen and 0 == result:
ord1 = ord(str1[i])
ord2 = ord(str2[i])
if ord1 >= orda and ord1 <= ordz:
ord1 = ord1-32
#end if
if ord2 >= orda and ord2 <= ordz:
ord2 = ord2-32
#end if
result = cmp(ord1, ord2)
i += 1
#end while
if 0 == result and minlen != numchars:
if len1 < len2:
result = -1
elif len2 < len1:
result = 1
#end if
#end if
return result
#end def
Используйте эту функцию только тогда, когда это имеет смысл, поскольку во многих случаях метод строчных букв будет лучше.
Я работаю только со строками ascii, я не уверен, как это будет вести себя с юникодом.
import re
if re.match('tEXT', 'text', re.IGNORECASE):
# is True
Эквивалент PHP: strcasecmp - nl3.php.net/strcasecmp