Игнорировать регистр в строках Python

Что является самым простым способом сравнения строк в Python, игнорируя случай?

Конечно, можно сделать (str1.lower() <= str2.lower()) и т.д., но это создало две дополнительные временные строки (с очевидными накладными расходами alloc/g-c).

Я предполагаю, что ищу эквивалент C stricmp().

[Запрошено еще несколько контекстов, поэтому я продемонстрирую тривиальный пример:]

Предположим, вы хотите отсортировать список строк. Вы просто выполняете функциюList.sort(). Это O (n * log (n)) сравнение строк и отсутствие управления памятью (поскольку все строки и элементы списка - это своего рода интеллектуальные указатели). Вы счастливы.

Теперь вы хотите сделать то же самое, но проигнорируйте случай (пусть упростит и скажет все строки являются ascii, поэтому проблемы с локалью можно игнорировать). Вы можете сделать theList.sort(key = lambda s: s.lower()), но затем вы вызываете два новых распределения на сравнение, плюс бремя сборщика мусора с дублированным (опущены). Каждый такой шум управления памятью на порядок меньше, чем простое сравнение строк.

Теперь, используя функцию stricmp(), выполняемую на месте, вы делаете: theList.sort(cmp = stricmp) и он такой же быстрый и удобный для памяти, как и списокList.sort(). Вы снова счастливы.

Проблема заключается в том, что любое нечувствительное к Python сравнение подразумевает неявную строку дублирования, поэтому я ожидал найти сравнения на основе C (возможно, в строке модуля).

Не удалось найти ничего подобного, поэтому вопрос здесь. (Надеюсь, это прояснит вопрос).

Ответ 1

В ответ на ваше разъяснение...

Вы можете использовать ctypes для выполнения функции c "strcasecmp". Ctypes включен в Python 2.5. Он предоставляет возможность вызова в DLL и разделяемые библиотеки, такие как libc. Вот краткий пример (Python on 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: Нечувствительные к регистру строки является рецептом для создания нестандартного строкового класса. Возможно, это немного покончит с чем-то быстрым, но может предоставить вам общий способ обработки нечувствительных к регистру строк, если вы планируете часто их использовать.

Ответ 2

Вот пример, показывающий, что использование 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-модуль не дублирует строки?

Примечание. Строковый метод lower() также имеет то преимущество, что он зависит от языка. Что-то, что вы, вероятно, не будете правы при написании собственного "оптимизированного" решения. Несмотря на это, из-за ошибок и недостающих возможностей в Python, такое сравнение может привести к неправильным результатам в контексте unicode.

Ответ 3

Используете ли вы это сравнение в очень часто исполняемом пути высокопроизводительного приложения? В качестве альтернативы, вы используете это для строк размером мегабайт? Если нет, то вам не стоит беспокоиться о производительности и просто использовать метод .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/extending/extending.html

Ответ 4

В вашем вопросе подразумевается, что вам не нужен Юникод. Попробуйте следующий фрагмент кода; если это сработает для вас, вы закончили:

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 "повторяющихся" строк.

Ответ 5

Я не могу найти какой-либо другой встроенный способ сделать нечувствительное к регистру сравнение: рецепт кулинарной книги python использует более низкий ().

Однако вы должны быть осторожны при использовании более низкого для сравнения из-за проблемы Турецкий I. К сожалению, обработка Python для турецких Is не является хорошей. ı преобразуется в I, но я не преобразован в ı. İ преобразуется в i, но я не преобразуется в İ.

Ответ 6

Нет встроенного эквивалента этой функции, которую вы хотите.

Вы можете написать свою собственную функцию, которая конвертирует в .lower() каждый символ за раз, чтобы избежать дублирования обеих строк, но я уверен, что он будет очень интенсивным и крайне неэффективным.

Если вы не работаете с чрезвычайно длинными строками (так долго, что может вызвать проблему с памятью, если они дублируются), я бы оставил ее простой и использовал

str1.lower() == str2.lower()

Вы будете в порядке

Ответ 7

Когда что-то не поддерживается в стандартной библиотеке, я всегда ищу пакет PyPI. Благодаря виртуализации и вездесущности современных дистрибутивов Linux я больше не избегаю расширений Python. Кажется, что PyICU соответствует законопроекту: fooobar.com/questions/7868/...

Теперь есть опция, которая является чистым питоном. Он хорошо протестирован: 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

Поскольку я использовал совпадение вместо поиска, мне не нужно было добавлять карету (^) в регулярное выражение.

Примечание: Это проверяет равенство, которое иногда требуется. Я также не зашел так далеко, чтобы сказать, что мне это нравится.

Ответ 8

Этот вопрос задает две очень разные вещи:

  • Что является самым простым способом сравнения строк в Python, игнорируя случай?
  • Я предполагаю, что ищу эквивалент C stricmp().

Так как # 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, я не уверен, как это будет вести себя с unicode.

Ответ 9

Вот как вы это сделаете с помощью re:

import re
p = re.compile('^hello$', re.I)
p.match('Hello')
p.match('hello')
p.match('HELLO')

Ответ 10

Рекомендуемая идиома для сортировки списков значений с использованием дорогих вычисляемых ключей относится к так называемому "украшенному шаблону". Он состоит только в создании списка кортежей (ключ, значение) из исходного списка и сортировки этого списка. Тогда тривиально удалить ключи и получить список отсортированных значений:

>>> 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, они также хешируются, поэтому их можно использовать как словарные ключи, установить элементы и т.д.

Ответ 11

Я уверен, что вам нужно либо использовать .lower(), либо использовать регулярное выражение. Я не знаю о встроенной функции сравнения строк без учета регистра.

Ответ 12

Для случайных или даже повторных сравнений несколько дополнительных строковых объектов не должны иметь значения, если это не произойдет в самом внутреннем цикле вашего основного кода или у вас недостаточно данных, чтобы фактически заметить влияние производительности. Смотрите, делаете ли вы: делать вещи "глупым" способом гораздо глупо, если вы также делаете это меньше.

Если вы серьезно хотите, чтобы сравнивать много и много текста без учета регистра, вы могли бы как-то сохранить строчные версии строк под рукой, чтобы избежать завершения и повторного создания или нормализовать весь набор данных в нижнем регистре. Это, конечно, зависит от размера набора данных. Если относительно немного игл и большой стог сена, замена иголок скомпилированными объектами регулярного выражения является одним из решений. Если трудно сказать, не видя конкретного примера.

Ответ 13

Вы можете перевести каждую строку в нижний регистр один раз - лениво только тогда, когда вам это нужно, или как префасса для сортировки, если вы знаете, что будете сортировать всю коллекцию строк. Существует несколько способов привязать этот сравнительный ключ к фактическим сортированным данным, но эти методы должны быть рассмотрены в отдельной проблеме.

Обратите внимание, что этот метод может использоваться не только для обработки проблем с верхним/нижним регистром, но и для других типов сортировки, таких как сортировка по конкретному языку или сортировка заголовков в стиле "библиотеки", которая игнорирует ведущие статьи и в противном случае нормализует данные до сортировка.

Ответ 14

Просто используйте метод str().lower(), если важна высокая производительность - в этом случае напишите этот метод сортировки как расширение C.

"Как написать расширение Python" кажется приличным вступлением..

Более интересно, Это руководство сравнивается с использованием библиотеки ctypes и написанием внешнего модуля C (ctype довольно существенно медленнее, чем C расширение).

Ответ 15

import re
if re.match('tEXT', 'text', re.IGNORECASE):
    # is True

Ответ 16

Вы можете подклассифицировать str и создать свой собственный класс строкой ввода-вывода, но IMHO будет крайне неразумным и создать гораздо больше проблем, чем это стоит.