Jak mogę porównać ciąg znaków bez rozróżniania wielkości liter?

573

Jak mogę porównać ciąg znaków bez rozróżniania wielkości liter w Pythonie?

Chciałbym podsumować porównanie zwykłych ciągów z ciągiem repozytorium, używając bardzo prostego i Pythońskiego sposobu. Chciałbym także mieć możliwość wyszukiwania wartości w dykcie haszowanym przez ciągi znaków za pomocą zwykłych ciągów pythonowych.

Kozyarchuk
źródło

Odpowiedzi:

595

Zakładając ciągi 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)")
Harley Holcombe
źródło
71
To nie zawsze działa. Rozważmy na przykład, że istnieją dwie greckie sigmy, z których jedna jest używana tylko na końcu. Ciąg Σίσυφος („Sísyphos” lub lepiej „Síſyphos”) ma wszystkie trzy: wielkie litery z przodu, małe litery końcowe na końcu i małe litery nieskończone na trzeciej pozycji. Jeśli twoje dwa ciągi są Σίσυφοςi ΣΊΣΥΦΟΣ, oznacza to podejście się nie powiedzie, ponieważ te mają być taka sama sprawa insensitively.
tchrist
52
@ Dwóch ostatnich komentujących: Myślę, że można założyć, że oba ciągi są ciągami ascii. Jeśli szukasz odpowiedzi na coś bardziej ekscytującego, jestem pewien, że jest tam (lub możesz o to zapytać).
Harley Holcombe
16
Problem: 'ß'.lower() == 'SS'.lower()jest fałszywy.
kennytm
11
Litery greckie to nie jedyny szczególny przypadek! W języku angielskim amerykańskim znak „i” (\ u0069) to mała wersja znaku „I” (\ u0049). Jednak alfabet turecki („tr-TR”) zawiera znak „I z kropką” „İ” (\ u0130), który jest wersją główną „i”, a „I” jest wersją „i bez” kropka „znak” „ı” (\ u0131).
Gqqnbig,
20
@HarleyHolcombe w jaki sposób można bezpiecznie (lub uczciwie) założyć, że łańcuchy są ascii? Pytanie nie zostało określone, a jeśli ciągi znaków są w dowolnym momencie wprowadzane przez użytkownika lub pokazywane użytkownikowi, należy wspierać internacjonalizację. Niezależnie od tego, nowi programiści będą to czytać i powinniśmy dać im naprawdę poprawną odpowiedź.
Ethan Reesor
529

Porównywanie ciągów znaków bez rozróżniania wielkości liter wydaje się trywialne, ale nie jest. Będę używał Python 3, ponieważ Python 2 jest tutaj słabo rozwinięty.

Pierwszą rzeczą, na którą należy zwrócić uwagę, jest to, że konwersje z usuwaniem wielkości liter w Unicode nie są trywialne. Jest to tekst, dla którego text.lower() != text.upper().lower(), jak "ß":

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

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

Ale powiedzmy, że chciałeś bezmyślnie porównać "BUSSE"i "Buße". Cholera, prawdopodobnie chcesz też porównać "BUSSE"i "BUẞE"zrównać się - to nowa forma kapitału. Zalecanym sposobem jest użycie casefold:

str. casefold ()

Zwraca kopie łańcucha z kopertami. Ciągi z fałdowanymi literami mogą być używane do dopasowania bez case'ów.

Folderowanie jest podobne do tworzenia małych liter, ale jest bardziej agresywne, ponieważ ma na celu usunięcie wszystkich rozróżnień wielkości liter w ciągu. [...]

Nie używaj tylko lower. Jeśli casefoldnie jest dostępne, robienie .upper().lower()pomaga (ale tylko w pewnym stopniu).

Następnie powinieneś rozważyć akcenty. Jeśli Twój renderer czcionek jest dobry, prawdopodobnie myślisz "ê" == "ê"- ale nie:

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

Wynika to z tego, że akcent na to drugie ma charakter łączący.

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']

Najprostszym sposobem na poradzenie sobie z tym jest unicodedata.normalize. Prawdopodobnie chcesz skorzystać z normalizacji NFKD , ale sprawdź dokumentację. Potem się robi

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

Aby zakończyć, tutaj jest to wyrażone w funkcjach:

import unicodedata

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

def caseless_equal(left, right):
    return normalize_caseless(left) == normalize_caseless(right)
Veedrac
źródło
8
Lepszym rozwiązaniem jest znormalizowanie wszystkich ciągów po przyjęciu, wtedy możesz to zrobić x.casefold() == y.casefold()w przypadku porównań bez rozróżniania wielkości liter (i, co ważniejsze, x == ydla rozróżniania wielkości liter ).
abarnert
3
@abarnert Rzeczywiście, w zależności od kontekstu - czasem lepiej pozostawić nienaruszone źródło, ale wcześniejsza normalizacja może również znacznie ułatwić późniejszy kod.
Veedrac
3
@ Veedrac: Masz rację, nie zawsze jest to właściwe; jeśli musisz mieć możliwość wyjścia oryginalnego źródła bez zmian (np. ponieważ masz do czynienia z nazwami plików w Linuksie, gdzie NKFC i NKFD są dozwolone i wyraźnie powinny być inne), oczywiście nie możesz go przekształcić na wejściu…
abarnert
7
Sekcja 3.13 standardu Unicode zawiera dwie inne definicje porównań bez przypadku: (D146, kanoniczny) NFD(toCasefold(NFD(str)))po obu stronach i (D147, zgodność) NFKD(toCasefold(NFKD(toCasefold(NFD(X)))))po obu stronach. Stwierdza, że ​​wnętrze NFDsłuży wyłącznie greckiemu akcentowi. Chyba chodzi o skrzynie.
2
I trochę zabawy z alfabetem Cherokee, gdzie casefold () ma wielkie litery: >>> „ᏚᎢᎵᎬᎢᎬᏒ”. Upper () „ᏚᎢᎵᎬᎢᎬᏒ” >>> „ᏚᎢᎵᎬᎢᎬᏒ”. Lower () „ꮪꭲꮅꭼꭲꭼꮢ” >>> „ᏚᎢᎵᎬᎢᎬᏒ” .casefold () 'ᏚᎢᎵᎬᎢᎬᏒ' >>>
bortzmeyer
60

Używając Python 2, wywołując .lower()każdy ciąg znaków lub obiekt Unicode ...

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

... będzie działać przez większość czasu, ale faktycznie nie działa w sytuacjach opisanych przez @christ .

Załóżmy, że mamy plik o nazwie unicode.txtzawierający dwa ciągi Σίσυφοςi ΣΊΣΥΦΟΣ. Z 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

Znak Σ ma dwie małe litery, ς i σ, i .lower()nie pomoże w porównywaniu ich bez rozróżniania wielkości liter.

Jednak, począwszy od Pythona 3, wszystkie trzy formy rozstrzygną się na ς, a wywołanie lower () na obu ciągach będzie działać poprawnie:

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

Więc jeśli zależy Ci na przypadkach takich jak trzy sigmy w języku greckim, użyj Python 3.

(Dla porównania, Python 2.7.3 i Python 3.3.0b1 pokazano na powyższych wydrukach interpretera.)

Nathan Craike
źródło
20
Aby porównanie było jeszcze bardziej wiarygodne, począwszy od Pythona 3.3 możesz użyć casefold (np. First.casefold () == second.casefold ()). W przypadku Python 2 możesz użyć PyICU (patrz także: icu-project.org/apiref/icu4c/… )
kgriffs
42

Sekcja 3.13 standardu Unicode definiuje algorytmy dopasowania bez rozróżniania wielkości liter.

X.casefold() == Y.casefold() w Pythonie 3 implementuje „domyślne dopasowanie bez wielkości liter” (D144).

Folderowanie nie zachowuje normalizacji ciągów we wszystkich instancjach, dlatego normalizacja musi zostać wykonana ( 'å'vs. 'å'). D145 wprowadza „kanoniczne dopasowanie bez caseless”:

import unicodedata

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

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

NFD() jest wywoływany dwukrotnie w bardzo rzadkich przypadkach krawędzi zawierających znak U + 0345.

Przykład:

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

Istnieje również zgodność dopasowywania bez case case (D146) dla przypadków takich jak '㎒'(U + 3392) i „identifier caseless dopasowanie”, aby uprościć i zoptymalizować dopasowanie caseless identyfikatorów .

jfs
źródło
3
To najlepsza odpowiedź dla Pythona 3, ponieważ Python 3 wykorzystuje ciągi Unicode, a odpowiedź opisuje, w jaki sposób standard Unicode definiuje dopasowanie ciągów bez rozróżniania wielkości liter.
SergiyKolesnikov,
Niestety, począwszy od Pythona 3.6, casefold()funkcja ta nie implementuje specjalnego traktowania wielkich liter I i kropkowanych wielkich liter I, jak opisano w Właściwości składania spraw . Dlatego porównanie może się nie powieść dla słów z języków tureckich zawierających te litery. Na przykład canonical_caseless('LİMANI') == canonical_caseless('limanı')musi wrócić True, ale zwraca False. Obecnie jedynym sposobem na poradzenie sobie z tym problemem w Pythonie jest napisanie opakowania folderów lub użycie zewnętrznej biblioteki Unicode, takiej jak PyICU.
SergiyKolesnikov,
@SergiyKolesnikov .casefold () zachowuje się tak, jak powinien, o ile wiem. Od standardowego: „domyślne operacje na obudowie są przeznaczone do użycia w przypadku braku dostosowania do konkretnych języków i środowisk” . Zasady tworzenia okładek dla tureckiej stolicy z kropkami I i małych bez kropek znajdują się w SpecialCasing.txt. „W przypadku języków innych niż język turecki takie mapowanie zwykle nie jest używane”. Z często zadawanych pytań
jfs
1
@ jf-sebastian Nie powiedziałem, że casefold () źle się zachowuje. Byłoby po prostu praktyczne, gdyby zaimplementował opcjonalny parametr, który umożliwił specjalne traktowanie wielkich i kropkowanych wielkich liter I. Na przykład sposób, w jaki robi to foldCase () w bibliotece ICU : „Składanie wielkości liter jest niezależne od ustawień regionalnych, a nie kontekstowe -wrażliwe, ale istnieje opcja włączenia lub wyłączenia mapowań dla kropkowanego I i bez kropkowego i, które są oznaczone literą „T” w CaseFolding.txt. ”
SergiyKolesnikov,
6

Widziałem to rozwiązanie tutaj za pomocą wyrażenia regularnego .

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

Działa dobrze z akcentami

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

Jednak nie działa ze znakami Unicode bez rozróżniania wielkości liter. Dziękuję @Rhymoid za zwrócenie uwagi, że zgodnie z moim zrozumieniem potrzebuję dokładnego symbolu, aby sprawa była prawdziwa. Dane wyjściowe są następujące:

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)
....:
Shiwangi
źródło
4
Fakt, że ßnie znajduje się w zasięgu SSze bez uwzględniania wielkości liter wyszukiwania dowody, że to nie działa pracę z znaków Unicode w ogóle .
3

Zwykle stosuje się pisanie wielkimi literami lub pisanie małymi literami w celu wyszukiwania i porównań. Na przykład:

>>> "hello".upper() == "HELLO".upper()
True
>>> 
Andru Luvisi
źródło
2

Co powiesz na najpierw konwersję na małe litery? możesz użyć string.lower().

Camilo Díaz Repka
źródło
4
Nie można porównać ich małych liter: Σίσυφοςi ΣΊΣΥΦΟΣnie przetestowałby odpowiednika, ale powinien.
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__)
Patrick Harrington
źródło
3
Zastępujesz wyjątek wiadomością wypisaną na standardowe wyjście, a następnie zwracasz Brak, co jest False. Jest to bardzo nieprzydatne w praktyce.
gerrit
-2

Wszystko, co musisz zrobić, to przekonwertować dwa ciągi na małe litery (wszystkie litery stają się małe), a następnie porównać je (zakładając, że ciągi są ciągami ASCII).

Na przykład:

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.")
Rohit Karthik
źródło
Ta odpowiedź nie dodaje żadnych nowych informacji. Co więcej, jest prawie taka sama jak zaakceptowana odpowiedź .
Georgy
-3

To kolejne wyrażenie, które nauczyłem się kochać / nienawidzić w ciągu ostatniego tygodnia, więc zwykle importuj jako (w tym przypadku tak) coś, co odzwierciedla to, jak się czuję! wykonaj normalną funkcję .... zapytaj o dane wejściowe, a następnie użyj .... coś = re.compile (r'foo * | spam * ', yes.I) ...... re.I (yes.I poniżej) jest taki sam jak IGNORECASE, ale nie możesz popełnić tylu błędów, jak to pisze!

Następnie przeszukujesz swoją wiadomość za pomocą wyrażeń regularnych, ale szczerze mówiąc, powinno to być kilka osobnych stron, ale chodzi o to, że foo lub spam są połączone razem i wielkość liter jest ignorowana. Następnie, jeśli którykolwiek zostanie znaleziony, lost_n_found wyświetli jeden z nich. jeśli żadna z nich, parametr lost_n_found nie jest równy None. Jeśli nie jest równa none, zwróć user_input małymi literami, używając „return lost_n_found.lower ()”

To pozwala znacznie łatwiej dopasować wszystko, co rozróżnia małe i wielkie litery. Wreszcie (NCS) oznacza „nikt nie dba poważnie ...!” lub nie rozróżnia wielkości liter ... cokolwiek

jeśli ktoś ma jakieś pytania, napisz mi o tym ..

    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")
Ali Paul
źródło