Як зробити порівняння рядків з нечутливим до регістру?


573

Як я можу зробити порівняння рядків нечутливих до регістру в Python?

Я хотів би інкапсулювати порівняння звичайних рядків із рядком сховища, використовуючи дуже простий та піфонічний спосіб. Я також хотів би мати можливість шукати значення в диктату хешировані рядками, використовуючи звичайні рядки python.

Відповіді:


595

Припускаючи рядки ASCII:

string1 = 'Hello'
string2 = 'hello'

if string1.lower() == string2.lower():
    print("The strings are the same (case insensitive)")
else:
    print("The strings are NOT the same (case insensitive)")

71
Це не завжди працює. Поміркуйте для прикладу, що є дві грецькі сигми, одна використовується лише в кінці. У рядку Σίσυφος («Sísyphos», а краще «Síſyphos») є всі три: велика літера в передній частині, нижня фінальна кінець в кінці, а нижня велика літера - не третя частина. Якщо ваші дві нитки є Σίσυφοςі ΣΊΣΥΦΟΣ, то ваш підхід провалюється, тому що вони повинні бути одним і тим же випадком безчутливим.
tchrist

52
@ Останні два коментатори: Я думаю, що справедливо вважати, що обидві рядки є рядками ascii. Якщо ви шукаєте відповідь на щось трохи захоплююче, я впевнений, що він там (або можете запитати).
Harley Holcombe

16
Проблема: 'ß'.lower() == 'SS'.lower()помилково.
kennytm

11
Грецькі букви - не єдиний особливий випадок! В американській англійській мові символ "i" (\ u0069) - це малу версію символу "I" (\ u0049). Однак турецький ("tr-TR") алфавіт включає символ "I з крапкою" "İ" (\ u0130), який є основною версією "i", а "I" є кептичною версією "i without крапка "символ", "ı" (\ u0131).
Gqqnbig

20
@HarleyHolcombe Як безпечно (або справедливо) вважати, що струни є ascii? Питання не вказувало, і якщо рядки в будь-який момент введені або показані користувачеві, то ви повинні підтримувати інтернаціоналізацію. Незважаючи на це, нові програмісти будуть читати це, і ми повинні дати їм справді правильну відповідь.
Ethan Reesor

529

Порівняння рядків у нечутливому випадку видається тривіальним, але це не так. Я буду використовувати Python 3, оскільки Python 2 тут недостатньо розвинений.

Перше, що слід зауважити, - це те, що конверсії для видалення випадків у Unicode не є тривіальними. Є текст, для якого text.lower() != text.upper().lower()такі "ß":

"ß".lower()
#>>> 'ß'

"ß".upper().lower()
#>>> 'ss'

Але скажімо, ви хотіли безвідмовно порівнювати "BUSSE"і "Buße". Чорт забирай, напевно, ти теж хочеш порівняти "BUSSE"і "BUẞE"дорівнювати - це новіша форма капіталу. Рекомендований спосіб casefold:

вул. casefold ()

Повернути копію рядка зі складеною регістром копії. Рядки, складені в корпусі, можуть використовуватися для беззмістового зіставлення.

Складання корпусів подібне до нижнього корпусу, але агресивніше, оскільки воно призначене для видалення всіх відмінків у рядку. [...]

Не просто використовувати lower. Якщо casefoldнедоступний, .upper().lower()допомагає (але лише дещо).

Тоді слід розглянути акценти. Якщо рендерінг вашого шрифту хороший, ви, напевно, думаєте "ê" == "ê"- але це не так:

"ê" == "ê"
#>>> False

Це тому, що наголос на останньому є поєднувальним характером.

import unicodedata

[unicodedata.name(char) for char in "ê"]
#>>> ['LATIN SMALL LETTER E WITH CIRCUMFLEX']

[unicodedata.name(char) for char in "ê"]
#>>> ['LATIN SMALL LETTER E', 'COMBINING CIRCUMFLEX ACCENT']

Найпростіший спосіб впоратися з цим unicodedata.normalize. Можливо, ви хочете використовувати нормалізацію NFKD , але сміливо перегляньте документацію. Тоді так і роблять

unicodedata.normalize("NFKD", "ê") == unicodedata.normalize("NFKD", "ê")
#>>> True

Для завершення тут це виражається у функціях:

import unicodedata

def normalize_caseless(text):
    return unicodedata.normalize("NFKD", text.casefold())

def caseless_equal(left, right):
    return normalize_caseless(left) == normalize_caseless(right)

8
Краще рішення - нормалізувати всі свої рядки щодо споживання, тоді можна просто зробити x.casefold() == y.casefold()для порівняння, що не враховує регістр (і, що ще важливіше, x == yдля регістру).
abarnert

3
@abarnert Дійсно, залежно від контексту - іноді краще залишити джерело недоторканим, але попередня нормалізація також може зробити пізніше код набагато простішим.
Ведрак

3
@Veedrac: Ти маєш рацію, це не завжди доречно; якщо вам потрібно мати змогу виводити вихідне джерело без змін (наприклад, оскільки ви маєте справу з назви файлів на Linux, де NKFC і NKFD дозволено і явно мають бути різними), очевидно, ви не можете перетворити його на вході ...
abarnert

7
Розділ 3.13 Unicode Standard має два інші визначення для безпідставних порівнянь: (D146, канонічне) NFD(toCasefold(NFD(str)))з обох сторін та (D147, сумісність) NFKD(toCasefold(NFKD(toCasefold(NFD(X)))))з обох сторін. В ній зазначається, що внутрішня NFD- це лише керування певним символом грецького акценту. Я здогадуюсь, це все про крайові корпуси.

2
І трохи весело з алфавітом Cherokee, де casefold () переходить у верхній регістр: >>> "ᏚᎢᎵᎬᎢᎬᏒ". .casefold () 'ᏚᎢᎵᎬᎢᎬᏒ' >>>
bortzmeyer

60

Використання Python 2, виклик .lower()кожного рядка або об'єкта Unicode ...

string1.lower() == string2.lower()

... працюватиме більшу частину часу, але насправді не працює в ситуаціях, описаних @tchrist .

Припустимо, у нас є файл з назвою, unicode.txtщо містить два рядки Σίσυφοςта ΣΊΣΥΦΟΣ. З Python 2:

>>> utf8_bytes = open("unicode.txt", 'r').read()
>>> print repr(utf8_bytes)
'\xce\xa3\xce\xaf\xcf\x83\xcf\x85\xcf\x86\xce\xbf\xcf\x82\n\xce\xa3\xce\x8a\xce\xa3\xce\xa5\xce\xa6\xce\x9f\xce\xa3\n'
>>> u = utf8_bytes.decode('utf8')
>>> print u
Σίσυφος
ΣΊΣΥΦΟΣ

>>> first, second = u.splitlines()
>>> print first.lower()
σίσυφος
>>> print second.lower()
σίσυφοσ
>>> first.lower() == second.lower()
False
>>> first.upper() == second.upper()
True

Символ Σ має дві малі форми, ς і σ, і .lower()не допоможе порівняти їх з урахуванням регістру.

Однак, як і в Python 3, усі три форми будуть вирішуватись на ς, а виклик нижче () в обох рядках буде працювати правильно:

>>> s = open('unicode.txt', encoding='utf8').read()
>>> print(s)
Σίσυφος
ΣΊΣΥΦΟΣ

>>> first, second = s.splitlines()
>>> print(first.lower())
σίσυφος
>>> print(second.lower())
σίσυφος
>>> first.lower() == second.lower()
True
>>> first.upper() == second.upper()
True

Тож якщо вам цікаві крайові випадки, такі як три сигми грецькою мовою, використовуйте Python 3.

(Для довідки, Python 2.7.3 та Python 3.3.0b1 показані у роздруківках інтерпретатора вище.)


20
Щоб зробити порівняння ще більш надійним, починаючи з Python 3.3, ви можете використовувати casefold (наприклад, first.casefold () == second.casefold ()). Для Python 2 можна використовувати PyICU (див. Також: icu-project.org/apiref/icu4c/… )
kgriffs

42

Розділ 3.13 стандарту Unicode визначає алгоритми для безвідповідної відповідності.

X.casefold() == Y.casefold() в Python 3 реалізує "відповідність випадкових випадків за замовчуванням" (D144).

Складання складових випадків не зберігає нормалізацію рядків у всіх випадках, а тому нормалізацію потрібно зробити ( 'å'проти 'å'). D145 вводить "канонічне безрезультатне узгодження":

import unicodedata

def NFD(text):
    return unicodedata.normalize('NFD', text)

def canonical_caseless(text):
    return NFD(NFD(text).casefold())

NFD() викликається двічі для дуже рідкісних крайових випадків із символом U + 0345.

Приклад:

>>> 'å'.casefold() == 'å'.casefold()
False
>>> canonical_caseless('å') == canonical_caseless('å')
True

Існують також сумісність безвідповідної відповідності (D146) для таких випадків, як '㎒'(U + 3392) та "ідентифікатор безвідповідної відповідності" для спрощення та оптимізації безвідкладного зіставлення ідентифікаторів .


3
Це найкраща відповідь для Python 3, оскільки Python 3 використовує рядки Unicode, а відповідь описує, як стандарт Unicode визначає беззмістову відповідність рядків.
СергійКолесніков

На жаль, як і в Python 3.6, ця casefold()функція не реалізує спеціальну обробку великого регістру I і пунктирної великої літери I, як описано в Case Folding Properties . Тому порівняння може не вдатися до слів з тюркських мов, які містять ці літери. Наприклад, canonical_caseless('LİMANI') == canonical_caseless('limanı')повинен повернутися True, але він повертається False. В даний час єдиний спосіб вирішити це в Python - це написати обгортку обкладинки корпусів або використовувати зовнішню бібліотеку Unicode, наприклад PyICU.
СергійКолесніков

@SergiyKolesnikov .casefold () поводиться як слід наскільки я можу сказати. Зі стандарту: "операції з корпусом за замовчуванням призначені для використання за відсутності адаптації для певних мов та середовищ" . Правила корпусу для турецької пунктирної столиці I та безлічі малих я знаходяться в SpecialCasing.txt "Для нетурківських мов таке відображення зазвичай не використовується." З питання про Unicode FAQ: Питання: Чому не закодовані додаткові символи для підтримки турецької мови, незалежної від локалі?
jfs

1
@ jf-sebastian Я не сказав, що casefold () погано поводиться. Це було б практично, якби він реалізував необов'язковий параметр, який дав змогу спеціально обробити великі регістри та пунктирні великі регістри I. Наприклад, те, як це робить foldCase () у бібліотеці ICU : " Складання файлів не залежить від локалі та не є контекстом -чутливий, але є варіант включення чи виключення відображень для пунктирного я та безлітніх i, які позначені "T" у CaseFolding.txt. "
СергійКолесніков

6

Я бачив це рішення тут, використовуючи регулярний вираз .

import re
if re.search('mandy', 'Mandy Pande', re.IGNORECASE):
# is True

Добре справляється з акцентами

In [42]: if re.search("ê","ê", re.IGNORECASE):
....:        print(1)
....:
1

Однак це не працює з символами Unicode, не залежними від регістру. Дякую @Rhymoid за те, що я зазначив, що як я зрозумів, що йому потрібен точний символ, щоб випадок був правдивим. Вихід такий:

In [36]: "ß".lower()
Out[36]: 'ß'
In [37]: "ß".upper()
Out[37]: 'SS'
In [38]: "ß".upper().lower()
Out[38]: 'ss'
In [39]: if re.search("ß","ßß", re.IGNORECASE):
....:        print(1)
....:
1
In [40]: if re.search("SS","ßß", re.IGNORECASE):
....:        print(1)
....:
In [41]: if re.search("ß","SS", re.IGNORECASE):
....:        print(1)
....:

4
Той факт , що ßне знайдений в SSс нечутливим до регістру пошуку є доказом того, що він не працює роботу з символами Unicode на всіх .

3

Звичайний підхід полягає у великій обробці рядків або малих регістрів для пошуку та порівнянь. Наприклад:

>>> "hello".upper() == "HELLO".upper()
True
>>> 

2

Як щодо перетворення на малі регістри спочатку? ви можете використовувати string.lower().


4
Ви не можете порівняти їхні малі карти: Σίσυφοςі ΣΊΣΥΦΟΣне перевіряли б еквівалент, але слід.
tchrist

-2
def insenStringCompare(s1, s2):
    """ Method that takes two strings and returns True or False, based
        on if they are equal, regardless of case."""
    try:
        return s1.lower() == s2.lower()
    except AttributeError:
        print "Please only pass strings into this method."
        print "You passed a %s and %s" % (s1.__class__, s2.__class__)

3
Ви замінюєте виняток повідомленням, надрукованим на stdout, потім повертаєте None, що є помилковим. Це дуже корисно на практиці.
Геррі

-2

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

Наприклад:

string1 = "Hello World"
string2 = "hello WorlD"

if string1.lower() == string2.lower():
    print("The two strings are the same.")
else:
    print("The two strings are not the same.")

Ця відповідь не додає нової інформації. Більше того, це майже те саме, що прийнята відповідь .
Георгій

-3

Це ще один регулярний вираз, який я навчився любити / ненавидіти за останній тиждень, тому зазвичай імпортую як (у даному випадку так) щось, що відображає, як я відчуваю! зробіть нормальну функцію .... запитайте про введення, а потім використовуйте .... щось = re.compile (r'foo * | спам * ', так.І) ...... re.I (так.І нижче) - це те саме, що IGNORECASE, але ви не можете зробити стільки помилок, пишучи це!

Потім ви шукаєте своє повідомлення за допомогою регексу, але чесно, що має бути кілька сторінок, але справа в тому, що foo або спам з'єднані, а випадок ігнорується. Тоді якщо будь-який буде знайдений, то Lost_n_found відобразить один із них. якщо ні тоді Lost_n_found не дорівнює None. Якщо його значення не дорівнює жодному, поверніть user_input в нижньому регістрі, використовуючи "return lost_n_found.lower ()"

Це дозволяє набагато легше співставити все, що залежно від регістру. І нарешті (NCS) означає "ніхто не піклується серйозно ..." чи не враховує регістр .... залежно від того, що залежно

якщо у когось є питання, будь ласка, зверніть увагу на це ..

    import re as yes

    def bar_or_spam():

        message = raw_input("\nEnter FoO for BaR or SpaM for EgGs (NCS): ") 

        message_in_coconut = yes.compile(r'foo*|spam*',  yes.I)

        lost_n_found = message_in_coconut.search(message).group()

        if lost_n_found != None:
            return lost_n_found.lower()
        else:
            print ("Make tea not love")
            return

    whatz_for_breakfast = bar_or_spam()

    if whatz_for_breakfast == foo:
        print ("BaR")

    elif whatz_for_breakfast == spam:
        print ("EgGs")
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.