Làm thế nào để tôi làm một so sánh chuỗi không phân biệt chữ hoa chữ thường?


573

Làm thế nào tôi có thể thực hiện so sánh chuỗi không nhạy cảm trong Python?

Tôi muốn gói gọn việc so sánh các chuỗi thông thường với chuỗi kho lưu trữ bằng cách sử dụng theo cách rất đơn giản và Pythonic. Tôi cũng muốn có khả năng tìm kiếm các giá trị trong một dict được băm bởi các chuỗi bằng các chuỗi python thông thường.

Câu trả lời:


595

Giả sử chuỗi 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
Điều đó không phải lúc nào cũng hoạt động. Hãy xem xét cho exanmple rằng có hai sigmas Hy Lạp, một chỉ được sử dụng ở cuối. Chuỗi Σίσυφος ( “Sísyphos”, hoặc tốt hơn “Sísyphos”) có tất cả ba: chữ hoa ở phía trước, chữ thường thức ở cuối, và chữ thường nonfinal ở vị trí thứ ba. Nếu hai chuỗi của bạn là ΣίσυφοςΣΊΣΥΦΟΣ, thì cách tiếp cận của bạn không thành công, bởi vì những chuỗi đó được cho là cùng một trường hợp không nhạy cảm.
tchrist

52
@ Hai người bình luận cuối cùng: Tôi nghĩ thật công bằng khi giả sử cả hai chuỗi là chuỗi ascii. Nếu bạn đang tìm kiếm một câu trả lời cho một điều thú vị hơn một chút, tôi chắc chắn rằng nó ở ngoài đó (hoặc bạn có thể hỏi nó).
Harley Holcombe

16
Vấn đề: 'ß'.lower() == 'SS'.lower()là Sai.
kennytm

11
Thư Hy Lạp không phải là trường hợp đặc biệt duy nhất! Trong tiếng Anh Mỹ, ký tự "i" (\ u0069) là phiên bản chữ thường của ký tự "I" (\ u0049). Tuy nhiên, bảng chữ cái tiếng Thổ Nhĩ Kỳ ("tr-TR") bao gồm ký tự "I có dấu chấm" "" (\ u0130), là phiên bản viết hoa của "i" và "I" là phiên bản bắt chước của "i không có một dấu chấm "ký tự", "tôi" (\ u0131).
Gqqnbig

20
@HarleyHolcombe làm thế nào là an toàn (hoặc công bằng) để giả sử các chuỗi là ascii? Câu hỏi không chỉ định và nếu các chuỗi tại bất kỳ điểm nào được nhập bởi hoặc hiển thị cho người dùng, thì bạn nên hỗ trợ quốc tế hóa. Bất kể, các lập trình viên mới sẽ đọc được điều này và chúng ta nên cung cấp cho họ câu trả lời thực sự chính xác.
Ethan Reesor

529

So sánh các chuỗi trong một trường hợp không nhạy cảm có vẻ tầm thường, nhưng thực tế không phải vậy. Tôi sẽ sử dụng Python 3, vì Python 2 chưa được phát triển ở đây.

Điều đầu tiên cần lưu ý là các chuyển đổi loại bỏ trường hợp trong Unicode không phải là nhỏ. Có văn bản trong đó text.lower() != text.upper().lower(), chẳng hạn như "ß":

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

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

Nhưng hãy nói rằng bạn muốn vô tình so sánh "BUSSE""Buße". Heck, có lẽ bạn cũng muốn so sánh "BUSSE""BUẞE"bằng nhau - đó là hình thức vốn mới hơn. Cách được đề xuất là sử dụng casefold:

str. casefold ()

Trả về một bản sao của chuỗi. Các chuỗi Casefolded có thể được sử dụng để kết hợp ngẫu nhiên.

Casefold tương tự như hạ cấp nhưng tích cực hơn vì nó nhằm loại bỏ tất cả các phân biệt trường hợp trong một chuỗi. [...]

Đừng chỉ sử dụng lower. Nếu casefoldkhông có sẵn, làm .upper().lower()giúp (nhưng chỉ phần nào).

Sau đó, bạn nên xem xét các dấu. Nếu trình kết xuất phông chữ của bạn tốt, có thể bạn nghĩ "ê" == "ê"- nhưng nó không:

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

Điều này là do các điểm nhấn ở sau là một nhân vật kết hợp.

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

Cách đơn giản nhất để đối phó với điều này là unicodedata.normalize. Bạn có thể muốn sử dụng chuẩn hóa NFKD , nhưng vui lòng kiểm tra tài liệu. Sau đó, một

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

Để kết thúc, ở đây điều này được thể hiện trong các chức năng:

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
Một giải pháp tốt hơn là bình thường hóa tất cả các chuỗi của bạn khi ăn, sau đó bạn chỉ có thể thực hiện x.casefold() == y.casefold()để so sánh không phân biệt chữ hoa chữ thường (và quan trọng hơn là phân biệt chữ hoa chữ thường x == y).
abarnert

3
@abarnert Thật vậy, tùy thuộc vào ngữ cảnh - đôi khi tốt hơn là giữ nguyên nguồn nhưng việc chuẩn hóa trước cũng có thể làm cho mã sau này đơn giản hơn nhiều.
Veedrac

3
@Veedrac: Bạn nói đúng, không phải lúc nào cũng thích hợp; nếu bạn cần có thể xuất nguồn gốc không thay đổi (ví dụ: vì bạn đang xử lý tên tệp trên Linux, trong đó NKFC và NKFD đều được phép và rõ ràng là khác nhau), rõ ràng bạn không thể chuyển đổi nó trên đầu vào.
abarnert

7
Phần Tiêu chuẩn Unicode 3.13 có hai định nghĩa khác để so sánh ngẫu nhiên: (D146, chính tắc) NFD(toCasefold(NFD(str)))ở cả hai mặt và (D147, tính tương thích) NFKD(toCasefold(NFKD(toCasefold(NFD(X)))))ở cả hai mặt. Nó nói rằng bên trong NFDchỉ để xử lý một nhân vật giọng Hy Lạp nhất định. Tôi đoán đó là tất cả về các trường hợp cạnh.

2
Và một chút thú vị với bảng chữ cái Cherokee, trong đó casefold () chuyển sang chữ hoa: >>> "". Upper () 'ᏚᎢᎵᎬᎢᎬᏒ' >>> "ᏚᎢᎵᎬᎢᎬᏒ". Lower () 'ꮪꭲꮅꭼꭲꭼꮢ' >>> "" .casefold () 'ᏚᎢᎵᎬᎢᎬᏒ' >>>
bortzmeyer

60

Sử dụng Python 2, gọi .lower()từng chuỗi hoặc đối tượng Unicode ...

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

... sẽ làm việc hầu hết thời gian, nhưng thực sự không hoạt động trong các tình huống mà @tchrist đã mô tả .

Giả sử chúng ta có một tệp gọi là unicode.txtchứa hai chuỗi ΣίσυφοςΣΊΣΥΦΟΣ. Với 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

Ký tự có hai dạng chữ thường, và, và .lower()sẽ không giúp so sánh chúng không phân biệt chữ hoa chữ thường.

Tuy nhiên, kể từ Python 3, cả ba biểu mẫu sẽ phân giải thành và gọi hàm dưới () trên cả hai chuỗi sẽ hoạt động chính xác:

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

Vì vậy, nếu bạn quan tâm đến các trường hợp cạnh như ba sigmas trong tiếng Hy Lạp, hãy sử dụng Python 3.

(Để tham khảo, Python 2.7.3 và Python 3.3.0b1 được hiển thị trong bản in trình thông dịch ở trên.)


20
Để làm cho việc so sánh trở nên mạnh mẽ hơn nữa, bắt đầu với Python 3.3, bạn có thể sử dụng casefold (ví dụ: first.casefold () == second.casefold ()). Đối với Python 2, bạn có thể sử dụng PyICU (xem thêm: icu-project.org/apiref/icu4c/ mẹo )
kgriffs

42

Mục 3.13 của tiêu chuẩn Unicode định nghĩa các thuật toán cho kết hợp ngẫu nhiên.

X.casefold() == Y.casefold() trong Python 3 thực hiện "khớp lệnh không mặc định" (D144).

Casefold không bảo toàn chuẩn hóa chuỗi trong tất cả các trường hợp và do đó việc chuẩn hóa cần phải được thực hiện ( 'å'so với 'å'). D145 giới thiệu "kết hợp không chính tắc":

import unicodedata

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

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

NFD() được gọi hai lần cho các trường hợp cạnh không thường xuyên liên quan đến ký tự U + 0345.

Thí dụ:

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

Ngoài ra còn có tính năng kết hợp không tương thích (D146) cho các trường hợp, chẳng hạn như '㎒'(U + 3392) và "kết hợp không trùng khớp định danh" để đơn giản hóa và tối ưu hóa kết hợp không trùng khớp của số nhận dạng .


3
Đây là câu trả lời tốt nhất cho Python 3, bởi vì Python 3 sử dụng các chuỗi Unicode và câu trả lời mô tả cách tiêu chuẩn Unicode định nghĩa khớp chuỗi không có chuỗi.
SergiyKolesnikov

Thật không may, kể từ Python 3.6, casefold()hàm này không thực hiện xử lý trường hợp đặc biệt của chữ hoa I và chữ hoa chấm I như được mô tả trong Case Folding Properties . Do đó, việc so sánh có thể thất bại đối với các từ từ các ngôn ngữ Turkic có chứa các chữ cái đó. Ví dụ, canonical_caseless('LİMANI') == canonical_caseless('limanı')phải trả lại True, nhưng nó trả lại False. Hiện tại, cách duy nhất để giải quyết vấn đề này trong Python là viết một trình bao bọc casefold hoặc sử dụng thư viện Unicode bên ngoài, chẳng hạn như PyICU.
SergiyKolesnikov

@SergiyKolesnikov .casefold () hành xử theo như tôi có thể nói. Từ tiêu chuẩn: "các hoạt động vỏ mặc định được dự định sử dụng trong trường hợp không có thiết kế riêng cho các ngôn ngữ và môi trường cụ thể" . Các quy tắc vỏ cho thủ đô rải rác của Thổ Nhĩ Kỳ I và nhỏ không có chữ i nằm trong SpecialCasing.txt. "Đối với các ngôn ngữ không phải tiếng Turk, ánh xạ này thường không được sử dụng." Từ Câu hỏi thường gặp về Unicode: Q: Tại sao không có ký tự bổ sung được mã hóa để hỗ trợ vỏ độc lập cho miền địa phương cho tiếng Thổ Nhĩ Kỳ?
jfs

1
@ jf-sebastian Tôi không nói rằng các hành vi sai trái của casefold (). Sẽ là thực tế nếu nó triển khai một tham số tùy chọn cho phép xử lý đặc biệt chữ hoa và chữ hoa rải rác I. Ví dụ, cách FoldCase () trong thư viện ICU thực hiện : "Gấp trường hợp là độc lập với ngôn ngữ -sensitive, nhưng có một tùy chọn cho việc bao gồm hoặc loại trừ ánh xạ cho I chấm và i không dấu chấm được đánh dấu bằng 'T' trong CaseFolding.txt. "
SergiyKolesnikov

6

Tôi thấy giải pháp này ở đây bằng cách sử dụng regex .

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

Nó hoạt động tốt với dấu

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

Tuy nhiên, nó không hoạt động với các ký tự unicode không phân biệt chữ hoa chữ thường. Cảm ơn bạn @Rhymoid đã chỉ ra rằng theo sự hiểu biết của tôi là nó cần biểu tượng chính xác, cho trường hợp là đúng. Đầu ra như sau:

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
Thực tế là ßkhông được tìm thấy bên trong SSvới tìm kiếm case-insensitive là bằng chứng cho thấy nó không hoạt động làm việc với các ký tự Unicode nào cả .

3

Cách tiếp cận thông thường là viết hoa các chuỗi hoặc viết thường chúng để tra cứu và so sánh. Ví dụ:

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

2

Làm thế nào về việc chuyển đổi thành chữ thường đầu tiên? bạn có thể sử dụng string.lower().


4
Bạn không thể so sánh các bản đồ chữ thường của họ: ΣίσυφοςΣΊΣΥΦΟΣsẽ không kiểm tra tương đương, nhưng nên.
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
Bạn đang thay thế một ngoại lệ bằng một thông báo được in ra thiết bị xuất chuẩn, sau đó trả về Không, đó là Sai. Điều đó rất không có ích trong thực tế.
gerrit

-2

Tất cả bạn sẽ phải làm là chuyển đổi hai chuỗi thành chữ thường (tất cả các chữ cái trở thành chữ thường) và sau đó so sánh chúng (giả sử các chuỗi là chuỗi ASCII).

Ví dụ:

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.")

Câu trả lời này không thêm bất kỳ thông tin mới. Hơn nữa, nó gần giống như câu trả lời được chấp nhận .
Georgy

-3

Đây là một regex khác mà tôi đã học cách yêu / ghét trong tuần qua vì vậy thường nhập như (trong trường hợp này là có) một cái gì đó phản ánh cảm giác của tôi! tạo một hàm bình thường .... yêu cầu nhập liệu, sau đó sử dụng .... Something = re.compile (r'foo * | spam * ', yes.I) ...... re.I (yes.I bên dưới) giống như IGNORECASE nhưng bạn không thể mắc nhiều lỗi khi viết nó!

Sau đó, bạn tìm kiếm tin nhắn của mình bằng cách sử dụng regex nhưng thành thật chỉ có một vài trang, nhưng vấn đề là foo hoặc spam được kết hợp với nhau và trường hợp bị bỏ qua. Sau đó, nếu một trong hai được tìm thấy thì bị mất đi sẽ hiển thị một trong số chúng. nếu không thì thì mất đi bằng với Không. Nếu nó không bằng không trả về user_input trong trường hợp thấp hơn bằng cách sử dụng "return Mất Stewfound.lower ()"

Điều này cho phép bạn dễ dàng phù hợp hơn nhiều với bất kỳ trường hợp nào nhạy cảm với trường hợp. Cuối cùng (NCS) là viết tắt của "không ai quan tâm nghiêm túc ...!" hoặc không phân biệt chữ hoa chữ thường .... tùy theo trường hợp nào

nếu bất cứ ai có bất kỳ câu hỏi nào giúp tôi về điều này ..

    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")
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.