Bagaimana saya melakukan perbandingan string case-insensitive?

573

Bagaimana saya bisa melakukan perbandingan string case insensitive dengan Python?

Saya ingin merangkum perbandingan string biasa ke string repositori menggunakan cara yang sangat sederhana dan Pythonic. Saya juga ingin memiliki kemampuan untuk mencari nilai-nilai dalam dict hash dengan string menggunakan string python biasa.

Kozyarchuk
sumber

Jawaban:

595

Dengan asumsi string 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
sumber
71
Itu tidak selalu berhasil. Pertimbangkan untuk exanmple bahwa ada dua sigma Yunani, satu hanya digunakan pada akhirnya. String Σίσυφος ("Sísyphos", atau lebih baik "Síſyphos") memiliki ketiganya: huruf besar di depan, huruf kecil di akhir, dan huruf kecil nonfinal di posisi ketiga. Jika dua string Anda Σίσυφοςdan ΣΊΣΥΦΟΣ, maka pendekatan Anda gagal, karena mereka seharusnya menjadi kasus yang sama tidak sensitif.
tchrist
52
@ Dua komentator terakhir: Saya pikir adil untuk menganggap kedua string adalah string ascii. Jika Anda mencari jawaban untuk sesuatu yang sedikit lebih mengasyikkan, saya yakin jawabannya ada di luar sana (atau Anda bisa bertanya).
Harley Holcombe
16
Masalah: 'ß'.lower() == 'SS'.lower()salah.
kennytm
11
Surat-surat Yunani bukan satu-satunya kasus khusus! Dalam bahasa Inggris AS, karakter "i" (\ u0069) adalah versi huruf kecil dari karakter "I" (\ u0049). Namun, alfabet Turki ("tr-TR") mencakup karakter "I with a dot" "İ" (\ u0130), yang merupakan versi kapital dari "i" dan "I" adalah versi captical dari "aku tanpa sebuah titik "karakter," ı "(\ u0131).
Gqqnbig
20
@ HarleyHolcombe bagaimana aman (atau adil) untuk menganggap string ascii? Pertanyaannya tidak menentukan, dan jika string pada titik apa pun dimasukkan oleh atau ditampilkan kepada pengguna, maka Anda harus mendukung internasionalisasi. Bagaimanapun, programmer baru akan membaca ini dan kita harus memberi mereka jawaban yang benar-benar benar.
Ethan Reesor
529

Membandingkan string dengan case case peka tampaknya sepele, tapi tidak. Saya akan menggunakan Python 3, karena Python 2 kurang dikembangkan di sini.

Hal pertama yang perlu diperhatikan adalah konversi penghapusan case di Unicode tidak sepele. Ada teks untuk itu text.lower() != text.upper().lower(), seperti "ß":

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

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

Tapi katakanlah Anda ingin membandingkan "BUSSE"dan "Buße". Heck, Anda mungkin juga ingin membandingkan "BUSSE"dan "BUẞE"menyamakan - itulah bentuk modal yang lebih baru. Cara yang disarankan adalah menggunakan casefold:

str. casefold ()

Kembalikan salinan string yang dilipat case. String casefolded dapat digunakan untuk pencocokan caseless.

Casefolding mirip dengan huruf kecil tetapi lebih agresif karena dimaksudkan untuk menghapus semua perbedaan huruf dalam string. [...]

Jangan hanya digunakan lower. Jika casefoldtidak tersedia, melakukan .upper().lower()bantuan (tetapi hanya sedikit).

Maka Anda harus mempertimbangkan aksen. Jika font renderer Anda bagus, Anda mungkin berpikir "ê" == "ê"- tetapi itu tidak:

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

Ini karena aksen pada yang terakhir adalah karakter yang menggabungkan.

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

Cara paling sederhana untuk mengatasinya adalah unicodedata.normalize. Anda mungkin ingin menggunakan normalisasi NFKD , tetapi jangan ragu untuk memeriksa dokumentasinya. Lalu seseorang melakukannya

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

Untuk menyelesaikan, di sini ini dinyatakan dalam fungsi:

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
sumber
8
Solusi yang lebih baik adalah menormalkan semua string Anda pada asupan, maka Anda bisa melakukan x.casefold() == y.casefold()perbandingan case-sensitive (dan, yang lebih penting, x == yuntuk case-sensitive).
abarnert
3
@abarnert Memang, tergantung pada konteks - terkadang lebih baik membiarkan sumbernya tetap utuh tetapi normalisasi dimuka juga dapat membuat kode yang lebih baru menjadi lebih sederhana.
Veedrac
3
@Veedrac: Anda benar, itu tidak selalu sesuai; jika Anda harus dapat menampilkan sumber asli tidak berubah (misalnya, karena Anda berurusan dengan nama file di Linux, di mana NKFC dan NKFD keduanya diizinkan dan secara eksplisit dianggap berbeda), jelas Anda tidak dapat mengubahnya pada input ...
abarnert
7
Unicode Standard section 3.13 memiliki dua definisi lain untuk perbandingan yang tidak memiliki casing: (D146, kanonik) NFD(toCasefold(NFD(str)))di kedua sisi dan (D147, kompatibilitas) NFKD(toCasefold(NFKD(toCasefold(NFD(X)))))di kedua sisi. Ini menyatakan batin NFDsemata-mata untuk menangani karakter aksen Yunani tertentu. Saya kira itu semua tentang kasus tepi.
2
Dan sedikit bersenang-senang dengan alfabet Cherokee, di mana casefold () menjadi huruf besar: >>> "ᏚᎢᎵᎬᎢᎬᏒ". Upper () 'ᏚᎢᎵᎬᎢᎬᏒ' >>> "ᏚᎢᎵᎬᎢᎬᏒ". Lower () 'ꮪꭲꮅꭼꭲꭼꮢ' >>> "ᏚᎢᎵᎬᎢᎬᏒ" .casefold () 'ᏚᎢᎵᎬᎢᎬᏒ' >>>
bortzmeyer
60

Menggunakan Python 2, memanggil .lower()setiap string atau objek Unicode ...

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

... akan bekerja sebagian besar waktu, tetapi memang tidak bekerja dalam situasi yang dijelaskan @tchrist .

Asumsikan kita memiliki file bernama unicode.txtmengandung dua string Σίσυφοςdan ΣΊΣΥΦΟΣ. Dengan 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

Karakter Σ memiliki dua bentuk huruf kecil, ς dan σ, dan .lower()tidak akan membantu membandingkannya dengan huruf besar-kecil.

Namun, pada Python 3, ketiga formulir akan memutuskan untuk ς, dan memanggil lebih rendah () pada kedua string akan bekerja dengan benar:

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

Jadi jika Anda peduli tentang kasus tepi seperti tiga sigma dalam bahasa Yunani, gunakan Python 3.

(Untuk referensi, Python 2.7.3 dan Python 3.3.0b1 ditunjukkan dalam cetakan juru bahasa di atas.)

Nathan Craike
sumber
20
Untuk membuat perbandingan lebih kuat, dimulai dengan Python 3.3 Anda dapat menggunakan casefold (mis. First.casefold () == second.casefold ()). Untuk Python 2 Anda dapat menggunakan PyICU (lihat juga: icu-project.org/apiref/icu4c/… )
kgriff
42

Bagian 3.13 dari standar Unicode mendefinisikan algoritma untuk pencocokan tanpa casing.

X.casefold() == Y.casefold() dalam Python 3 mengimplementasikan "standar pencocokan kosong" (D144).

Casefolding tidak mempertahankan normalisasi string dalam semua kasus dan oleh karena itu normalisasi perlu dilakukan ( 'å'vs 'å'). D145 memperkenalkan "pencocokan santai kanonik":

import unicodedata

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

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

NFD() dipanggil dua kali untuk kasus tepi yang sangat jarang yang melibatkan karakter U + 0345.

Contoh:

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

Ada juga kompatibilitas pencocokan Caseless (D146) untuk kasus-kasus seperti '㎒'(U + 3392) dan "pengidentifikasi pencocokan Caseless" untuk menyederhanakan dan mengoptimalkan pencocokan Caseless pengidentifikasi .

jfs
sumber
3
Ini adalah jawaban terbaik untuk Python 3, karena Python 3 menggunakan string Unicode dan jawabannya menjelaskan bagaimana standar Unicode mendefinisikan pencocokan string tanpa kabel.
SergiyKolesnikov
Sayangnya, pada Python 3.6, casefold()fungsi tersebut tidak mengimplementasikan perlakuan kasus khusus huruf besar I dan huruf besar bertitik I seperti yang dijelaskan dalam Case Folding Properties . Oleh karena itu, perbandingannya mungkin gagal untuk kata-kata dari bahasa Turki yang berisi surat-surat itu. Misalnya, canonical_caseless('LİMANI') == canonical_caseless('limanı')harus kembali True, tetapi kembali False. Saat ini, satu-satunya cara untuk menangani hal ini dengan Python adalah dengan menulis bungkus casefold atau menggunakan pustaka Unicode eksternal, seperti PyICU.
SergiyKolesnikov
@SergiyKolesnikov .casefold () berperilaku sebagaimana mestinya sejauh yang saya tahu. Dari standar: "operasi casing default dimaksudkan untuk digunakan tanpa adanya penyesuaian untuk bahasa dan lingkungan tertentu" . Aturan casing untuk modal bertitik Turki I dan dotless small i ada di SpecialCasing.txt. "Untuk bahasa non-Turki, pemetaan ini biasanya tidak digunakan." Dari FAQ Unicode: T: Mengapa tidak ada karakter tambahan yang disandikan untuk mendukung casing lokal-independen untuk bahasa Turki?
jfs
1
@ jf-sebastian saya tidak mengatakan casefold () keliru. Akan lebih praktis jika menerapkan parameter opsional yang memungkinkan perlakuan khusus huruf besar dan huruf besar -besaran I. Misalnya, cara foldCase () di perpustakaan ICU melakukannya : "Case-folding bersifat lokal-independen dan bukan konteks -peka, tetapi ada opsi untuk memasukkan atau mengecualikan pemetaan untuk titik-titik I dan dotless i yang ditandai dengan 'T' di CaseFolding.txt. "
SergiyKolesnikov
6

Saya melihat solusi ini di sini menggunakan regex .

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

Ini bekerja dengan baik dengan aksen

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

Namun, itu tidak berfungsi dengan karakter unicode case-insensitive. Terima kasih @Rhoidoid untuk menunjukkan bahwa karena pemahaman saya adalah bahwa ia membutuhkan simbol yang tepat, agar kasus ini benar. Outputnya adalah sebagai berikut:

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
sumber
4
Fakta bahwa ßtidak ditemukan dalam SSdengan case-insensitive pencarian bukti bahwa hal itu tidak bekerja kerja dengan karakter Unicode sama sekali .
3

Pendekatan yang biasa digunakan adalah huruf besar string atau huruf kecil untuk pencarian dan perbandingan. Sebagai contoh:

>>> "hello".upper() == "HELLO".upper()
True
>>> 
Andru Luvisi
sumber
2

Bagaimana kalau mengkonversi ke huruf kecil dulu? Anda dapat menggunakan string.lower().

Camilo Díaz Repka
sumber
4
Anda tidak dapat membandingkan peta huruf kecil mereka: Σίσυφοςdan ΣΊΣΥΦΟΣtidak akan menguji yang setara, tetapi harus.
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
sumber
3
Anda mengganti pengecualian dengan pesan yang dicetak ke stdout, lalu mengembalikan None, yang False. Itu sangat tidak membantu dalam praktik.
gerrit
-2

Yang harus Anda lakukan adalah mengubah dua string menjadi huruf kecil (semua huruf menjadi huruf kecil) dan kemudian membandingkannya (dengan asumsi string adalah string ASCII).

Sebagai contoh:

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
sumber
Jawaban ini tidak menambahkan informasi baru. Apalagi, hampir sama dengan jawaban yang diterima .
Georgy
-3

Ini adalah regex lain yang telah saya pelajari untuk sukai / benci selama seminggu terakhir jadi biasanya diimpor sebagai (dalam hal ini ya) sesuatu yang mencerminkan bagaimana perasaan saya! buat fungsi normal .... minta input, lalu gunakan .... something = re.compile (r'foo * | spam * ', yes.I) ...... re.I (yes.I di bawah) sama dengan IGNORECASE tetapi Anda tidak dapat membuat banyak kesalahan saat menuliskannya!

Anda kemudian mencari pesan Anda menggunakan regex tetapi jujur ​​itu harus beberapa halaman sendiri, tetapi intinya adalah bahwa foo atau spam disalurkan bersama dan case diabaikan. Kemudian jika salah satu ditemukan maka Lost_n_found akan menampilkan salah satunya. jika tidak ada yang hilang maka ditemukan sama dengan Tidak ada. Jika tidak sama dengan tidak mengembalikan user_input dalam huruf kecil menggunakan "return lost_n_found.lower ()"

Ini memungkinkan Anda untuk lebih mudah mencocokkan apa pun yang peka terhadap huruf besar-kecil. Terakhir (NCS) berarti "tidak ada yang peduli dengan serius ...!" atau tidak case sensitif .... mana

jika ada yang punya pertanyaan buat saya tentang ini ..

    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
sumber