ฉันจะทำการเปรียบเทียบสตริงตัวพิมพ์เล็กและตัวพิมพ์ใหญ่ได้อย่างไร?


573

ฉันจะทำการเปรียบเทียบสตริงที่ไม่สนใจขนาดตัวพิมพ์ใน Python ได้อย่างไร?

ฉันต้องการสรุปการเปรียบเทียบสตริงปกติกับสตริงที่เก็บข้อมูลโดยใช้วิธีที่ง่ายและ Pythonic ฉันอยากจะมีความสามารถในการค้นหาค่าใน dict hashed โดยสายโดยใช้สายหลามปกติ

คำตอบ:


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
มันไม่ได้ผลเสมอไป ลองคิดดูว่ามี sigmas ของกรีกสองอันใช้แค่ตอนจบเท่านั้น สตริงΣίσυφος (“ Sísyphos” หรือดีกว่า“ Síſyphos”) มีทั้งสาม: ตัวพิมพ์ใหญ่ที่ด้านหน้าตัวพิมพ์เล็กสุดท้ายในตอนท้ายและตัวพิมพ์เล็กที่ไม่ใช่ตำแหน่งสุดท้าย หากทั้งสองสายของคุณเป็นΣίσυφοςและΣΊΣΥΦΟΣแล้ววิธีการของคุณล้มเหลวเพราะสิ่งเหล่านั้นควรจะเป็นกรณีเดียวกัน insensitively
tchrist

52
@ ผู้แสดงความคิดเห็นสองคนสุดท้าย: ฉันคิดว่ามันยุติธรรมที่จะถือว่าทั้งสองสายนั้นเป็นสตริง ascii หากคุณกำลังมองหาคำตอบสำหรับสิ่งที่น่าตื่นเต้นกว่านี้ฉันแน่ใจว่ามันอยู่ที่นั่น (หรือคุณสามารถถามได้)
Harley Holcombe

16
ปัญหา: 'ß'.lower() == 'SS'.lower()เป็นเท็จ
kennytm

11
ตัวอักษรกรีกไม่ได้เป็นกรณีพิเศษเท่านั้น! ในภาษาอังกฤษแบบอเมริกันตัวอักษร "i" (\ u0069) เป็นตัวพิมพ์เล็กของตัวอักษร "I" (\ u0049) อย่างไรก็ตามตัวอักษรภาษาตุรกี ("tr-TR") รวมถึง "I with a dot" ตัวอักษร "İ" (\ u0130) ซึ่งเป็นเวอร์ชั่นตัวพิมพ์ใหญ่ของ "i" และ "I" เป็นรุ่น captical ของ "i ที่ไม่มี จุด "อักขระ" ı "(\ u0131)
Gqqnbig

20
@HarleyHolcombe เป็นวิธีที่ปลอดภัย (หรือยุติธรรม) ที่จะถือว่าสตริงเป็น ascii? คำถามไม่ได้ระบุและหากสตริงอยู่ที่จุดใด ๆ ที่ป้อนหรือแสดงต่อผู้ใช้คุณควรสนับสนุนความเป็นสากล โปรแกรมเมอร์ใหม่จะอ่านข้อความนี้และเราควรให้คำตอบที่ถูกต้องแก่พวกเขา
Ethan Reesor

529

การเปรียบเทียบสตริงในกรณีที่ไม่มีความรู้สึกนั้นดูเล็กน้อย แต่ไม่ใช่ ฉันจะใช้ Python 3 เนื่องจาก Python 2 อยู่ระหว่างการพัฒนาที่นี่

สิ่งแรกที่ควรทราบก็คือการแปลงการลบขนาดตัวอักษรใน Unicode นั้นไม่สำคัญ มีข้อความที่ต้องการtext.lower() != text.upper().lower()เช่น"ß":

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

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

แต่สมมติว่าคุณต้องการที่จะเปรียบเทียบ caselessly และ"BUSSE" "Buße"เฮคคุณอาจต้องการเปรียบเทียบ"BUSSE"และ"BUẞE"เท่ากันนั่นคือรูปแบบเงินทุนใหม่กว่า วิธีที่แนะนำคือการใช้casefold:

STR เคส ()

ส่งคืนสำเนาของสตริง สตริงตัวพิมพ์เล็กอาจใช้สำหรับการจับคู่แบบไม่เป็นชุด

Casefolding คล้ายกับตัวพิมพ์เล็ก แต่มีความดุดันมากกว่าเนื่องจากมีจุดประสงค์เพื่อลบความแตกต่างของตัวอักษรทั้งหมดในสตริง [ ... ]

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 แน่นอนขึ้นอยู่กับบริบท - บางครั้งมันก็เป็นการดีที่จะปล่อยให้ซอร์สนั้นไม่บุบสลาย
Veedrac

3
@Veedrac: ถูกต้องมันไม่เหมาะสมเสมอไป ถ้าคุณต้องการที่จะส่งออกต้นฉบับไม่เปลี่ยนแปลง (เช่นเนื่องจากคุณกำลังจัดการกับชื่อไฟล์บน Linux โดยที่ทั้ง NKFC และ NKFD ได้รับอนุญาตและควรจะแตกต่างอย่างชัดเจน) แน่นอนคุณไม่สามารถแปลงมันในอินพุต ...
abarnert

7
มาตรฐาน Unicode มาตรา 3.13 มีข้อกำหนดสองประการสำหรับการเปรียบเทียบแบบไม่ตั้งใจ: (D146, บัญญัติ) NFD(toCasefold(NFD(str)))ทั้งสองด้านและ (D147, ความเข้ากันได้) NFKD(toCasefold(NFKD(toCasefold(NFD(X)))))ทั้งสองด้าน มันระบุด้านในNFDเป็นเพียงการจัดการกับตัวละครสำเนียงกรีกบางอย่าง ฉันเดาว่ามันเกี่ยวกับเคสขอบ

2
และสนุกไปกับตัวอักษร Cherokee ที่ casefold () ไปที่ตัวพิมพ์ใหญ่: >>> "ᏚᎢᎵᎬᎢᎬᏒ". upper () 'ᏚᎢᎵᎬᎢᎬᏒ' >>> "ᏚᎢᎵᎬᎢᎬᏒ". lower () 'lower') '>>>' ᏚᎢᎵᎬᎢᎬᏒ ' .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

อักขระ has มีรูปแบบตัวพิมพ์เล็กสองแบบคือςและσและ.lower()จะไม่ช่วยเปรียบเทียบรูปแบบตัวพิมพ์ใหญ่ - เล็ก

อย่างไรก็ตามใน Python 3 ทั้งสามรูปแบบจะแก้ไขเป็นςและการเรียก lower () บนทั้งสองสายจะทำงานอย่างถูกต้อง:

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

Casefolding ไม่รักษามาตรฐานของสตริงในทุกกรณีและดังนั้นจึงต้องทำ Normalization ( 'å'vs. 'å') 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

นอกจากนี้ยังมีการทำงานร่วมกัน caseless จับคู่ (D146) สำหรับกรณีเช่น'㎒'(U + 3392) และ "ระบุ caseless ตรงกับ" เพื่อลดความซับซ้อนและเพิ่มประสิทธิภาพcaseless การจับคู่ของตัวบ่งชี้


3
นี่คือคำตอบที่ดีที่สุดสำหรับ Python 3 เนื่องจาก Python 3 ใช้สายอักขระ Unicode และคำตอบจะอธิบายว่ามาตรฐาน Unicode กำหนดการจับคู่แบบสตริงอย่างไร
SergiyKolesnikov

แต่น่าเสียดายที่เป็นของงูใหญ่ 3.6 ที่casefold()ฟังก์ชั่นไม่ใช้การรักษากรณีพิเศษของตัวพิมพ์ใหญ่ I และจุดพิมพ์ใหญ่ที่ผมได้อธิบายไว้ในกรณีคุณสมบัติพับ ดังนั้นการเปรียบเทียบอาจล้มเหลวสำหรับคำจากภาษาเตอร์กิกที่มีตัวอักษรเหล่านั้น ยกตัวอย่างเช่นcanonical_caseless('LİMANI') == canonical_caseless('limanı')ต้องกลับแต่มันกลับTrue Falseปัจจุบันวิธีเดียวที่จะจัดการกับสิ่งนี้ใน Python ก็คือการเขียนตัวห่อหุ้ม casefold หรือใช้ไลบรารี Unicode ภายนอกเช่น PyICU
SergiyKolesnikov

@SergiyKolesnikov .casefold () ทำงานเท่าที่ควรเท่าที่ฉันจะบอกได้ จากมาตรฐาน: "เริ่มต้นปลอกการดำเนินงานมีวัตถุประสงค์เพื่อใช้ในกรณีที่ไม่มีการตัดเย็บโดยเฉพาะอย่างยิ่งสำหรับภาษาและสภาพแวดล้อม" กฎการวางท่อสำหรับเมืองหลวงประตุรกีและ I ขนาดเล็กที่ไม่มีจุดฉันอยู่ใน SpecialCasing.txt "สำหรับภาษาที่ไม่ใช้เตอร์กิชปกติไม่ใช้การจับคู่นี้" จาก Unicode คำถามที่พบบ่อย: Q: ทำไมไม่มีตัวอักษรพิเศษเข้ารหัสเพื่อรองรับเคสที่ไม่ขึ้นกับสถานที่สำหรับภาษาตุรกี?
jfs

1
@ jf-sebastian ฉันไม่ได้พูดว่า casefold () misbehaves มันจะเป็นจริงถ้ามันใช้พารามิเตอร์ทางเลือกที่เปิดใช้งานการรักษาพิเศษของตัวพิมพ์ใหญ่และจุดใหญ่ I. ตัวอย่างเช่นวิธีfoldCase () ในห้องสมุด ICU ทำมัน : "Case-folding เป็นสถานที่ที่ไม่ขึ้นกับบริบท - มีความละเอียดอ่อน แต่มีตัวเลือกว่าจะรวมหรือไม่รวมการแมปสำหรับจุดที่ฉันและ dotless i ที่ทำเครื่องหมายด้วย 'T' ใน CaseFolding.txt "
SergiyKolesnikov

6

ผมเห็นการแก้ปัญหานี้ที่นี่ใช้regex

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

มันทำงานได้ดีกับสำเนียง

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

อย่างไรก็ตามมันไม่สามารถใช้ได้กับตัวอักษร unicode case-insensitive ขอบคุณ @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 ซึ่งเป็น False นั่นเป็นประโยชน์อย่างมากในทางปฏิบัติ
gerrit

-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

นี่เป็นอีก regex ที่ฉันได้เรียนรู้ที่จะรัก / เกลียดในช่วงสัปดาห์ที่แล้วดังนั้นมักจะนำเข้าเป็น (ในกรณีนี้ใช่) สิ่งที่สะท้อนให้เห็นถึงความรู้สึกของฉัน! ทำให้ฟังก์ชั่นปกติ .... ขอให้ป้อนข้อมูลจากนั้นใช้ .... something = re.compile (r'foo * | spam * ', yes.I) ...... re.I (ใช่ ด้านล่าง) เหมือนกับ IGNORECASE แต่คุณไม่สามารถเขียนข้อผิดพลาดได้มากมาย!

จากนั้นคุณค้นหาข้อความของคุณโดยใช้ regex แต่โดยสุจริตว่าควรมีสองสามหน้าในตัวของมันเอง แต่ประเด็นก็คือท่อหรือ 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")
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.