Как мне сделать сравнение строк без учета регистра?
Как сделать сравнение строк без учета регистра в Python?
Я хотел бы инкапсулировать сравнение обычной строки со строкой репозитория, используя очень простой и Pythonic способ. Я также хотел бы иметь возможность искать значения в dict, хэшированном строками, используя обычные строки python.
17 ответов
Предполагая строки 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)"
Сравнение строки без учета регистра кажется тривиальным, но это не так. Я буду использовать Python 3, так как Python 2 здесь недостаточно развит.
Первое, на что следует обратить внимание, это то, что преобразования с удалением регистра в юникоде не являются тривиальными. Есть текст для которого text.lower() != text.upper().lower()
, такие как "ß"
:
"ß".lower()
#>>> 'ß'
"ß".upper().lower()
#>>> 'ss'
Но скажем, вы хотели сравнить случайные "BUSSE"
а также "Buße"
, Черт, вы, вероятно, также хотите сравнить "BUSSE"
а также "BUẞE"
равный - это более новая форма капитала. Рекомендуемый способ заключается в использовании casefold
:
help(str.casefold)
#>>> Help on method_descriptor:
#>>>
#>>> casefold(...)
#>>> S.casefold() -> str
#>>>
#>>> Return a version of S suitable for caseless comparisons.
#>>>
Не просто использовать 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)
Использование 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, все три формы будут преобразованы в ς, и вызов метода 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 показаны в распечатках интерпретатора выше.)
Раздел 3.13 стандарта Unicode определяет алгоритмы для сопоставления без учета регистра.
X.casefold() == Y.casefold()
в Python 3 реализовано "сопоставление без учета регистра по умолчанию" (D144).
Casefolding не сохраняет нормализацию строк во всех случаях, и поэтому нормализация должна быть сделана ('å'
против 'å'
). 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) и "сопоставление без учета идентификатора" для упрощения и оптимизации сопоставления идентификаторов без учета регистра.
You can use casefold() method. The casefold() method ignores cases when comparing.
firstString = "Hi EVERYONE"
secondString = "Hi everyone"
if firstString.casefold() == secondString.casefold():
print('The strings are equal.')
else:
print('The strings are not equal.')
Output:
The strings are equal.
Я видел это решение здесь с помощью регулярных выражений.
import re
if re.search('mandy', 'Mandy Pande', re.IGNORECASE):
# is True
Хорошо работает с акцентами
In [42]: if re.search("ê","ê", re.IGNORECASE):
....: print(1)
....:
1
Однако он не работает с символами Юникода без учета регистра. Спасибо @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)
....:
Обычный подход состоит в том, чтобы заглавные или строчные буквы в строках для поиска и сравнения. Например:
>>> "hello".upper() == "HELLO".upper()
True
>>>
Как насчет преобразования в нижний регистр в первую очередь? ты можешь использовать string.lower()
,
чистое решение, которое я нашел, где я работаю с некоторыми постоянными расширениями файлов.
from pathlib import Path
class CaseInsitiveString(str):
def __eq__(self, __o: str) -> bool:
return self.casefold() == __o.casefold()
GZ = CaseInsitiveString(".gz")
ZIP = CaseInsitiveString(".zip")
TAR = CaseInsitiveString(".tar")
path = Path("/tmp/ALL_CAPS.TAR.GZ")
GZ in path.suffixes, ZIP in path.suffixes, TAR in path.suffixes, TAR == ".tAr"
# (True, False, True, True)
You can mention case=False in the str.contains()
data['Column_name'].str.contains('abcd', case=False)
def search_specificword(key, stng):
key = key.lower()
stng = stng.lower()
flag_present = False
if stng.startswith(key+" "):
flag_present = True
symb = [',','.']
for i in symb:
if stng.find(" "+key+i) != -1:
flag_present = True
if key == stng:
flag_present = True
if stng.endswith(" "+key):
flag_present = True
if stng.find(" "+key+" ") != -1:
flag_present = True
print(flag_present)
return flag_present
Вывод: search_specificword("Доступное жилье", "в основе доступного жилья в Европе") False
search_specificword("Доступное жилье", "в основе доступного жилья в Европе") True
from re import search, IGNORECASE
def is_string_match(word1, word2):
# Case insensitively function that checks if two words are the same
# word1: string
# word2: string | list
# if the word1 is in a list of words
if isinstance(word2, list):
for word in word2:
if search(rf'\b{word1}\b', word, IGNORECASE):
return True
return False
# if the word1 is same as word2
if search(rf'\b{word1}\b', word2, IGNORECASE):
return True
return False
is_match_word = is_string_match("Hello", "hELLO")
True
is_match_word = is_string_match("Hello", ["Bye", "hELLO", "@vagavela"])
True
is_match_word = is_string_match("Hello", "Bye")
False
Рассмотрите возможность использования FoldedCase из jaraco.text :
>>> from jaraco.text import FoldedCase
>>> FoldedCase('Hello World') in ['hello world']
True
И если вы хотите, чтобы словарь вводился в текст независимо от регистра, используйте FoldedCaseKeyedDict из jaraco.collections :
>>> from jaraco.collections import FoldedCaseKeyedDict
>>> d = FoldedCaseKeyedDict()
>>> d['heLlo'] = 'world'
>>> list(d.keys()) == ['heLlo']
True
>>> d['hello'] == 'world'
True
>>> 'hello' in d
True
>>> 'HELLO' in d
True
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__)
Это еще одно регулярное выражение, которое я научился любить / ненавидеть на прошлой неделе, поэтому обычно придаю значение (в данном случае да) тому, что отражает мои чувства! сделать нормальную функцию.... запросить ввод, затем использовать.... что-то = re.compile(r'foo*|spam*', yes.I)...... re.I (yes.I ниже) - то же самое, что IGNORECASE, но вы не можете совершить столько ошибок при написании!
Затем вы ищите свое сообщение с помощью регулярных выражений, но, честно говоря, это должно быть несколько страниц само по себе, но дело в том, что foo или spam передаются вместе, а case игнорируется. Затем, если любой из них найден, 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")
Если у вас есть списки со строками, и вы хотите сравнить строки в другом списке с учетом регистра. Вот мое решение.
list1 = map(lambda each:each.lower(), list1)
list2 = map(lambda each:each.lower(), list2)
После этого вы можете легко сравнить строки.
Я использовал это, чтобы сделать что-то более полезное для сравнения двух строк:
def strings_iequal(first, second):
try:
return first.upper() == second.upper()
except AttributeError:
if not first:
if not second:
return True
Обновление: Как отметил Геррит, в этом ответе есть некоторые ошибки. Это было много лет назад, и я уже не помню, для чего я его использовал. Я вспоминаю, как писал тесты, но что они теперь хорошего!