Каков наиболее эффективный способ в Python преобразовать строку во все строчные буквы, удаляя все альфа-символы без ascii?
У меня есть простая задача, которую мне нужно выполнить в Python, которая заключается в том, чтобы преобразовать строку во все строчные буквы и вычеркнуть все не-ascii не-альфа-символы.
Например:
"This is a Test" -> "thisisatest"
"[email protected]#$&( er Ra{}|?>ndom" -> "atherrandom"
У меня есть простая функция:
import string
import sys
def strip_string_to_lowercase(s):
tmpStr = s.lower().strip()
retStrList = []
for x in tmpStr:
if x in string.ascii_lowercase:
retStrList.append(x)
return ''.join(retStrList)
Но я не могу не думать, что есть более эффективный или более элегантный способ.
Спасибо!
Edit:
Спасибо всем, кто ответил. Я узнал, а в некоторых случаях повторно изучил много питона.
Ответы
Ответ 1
Другое решение (не то, что pythonic, но очень быстро) - использовать string.translate - хотя обратите внимание, что это не будет работать для unicode. Также стоит отметить, что вы можете ускорить код Даны, перемещая символы в набор (который ищет хеш, а не выполняет линейный поиск каждый раз). Ниже приведены тайминги для различных решений:
import string, re, timeit
# Precomputed values (for str_join_set and translate)
letter_set = frozenset(string.ascii_lowercase + string.ascii_uppercase)
tab = string.maketrans(string.ascii_lowercase + string.ascii_uppercase,
string.ascii_lowercase * 2)
deletions = ''.join(ch for ch in map(chr,range(256)) if ch not in letter_set)
s="[email protected]#$&( er Ra{}|?>ndom"
# From unwind filter approach
def test_filter(s):
return filter(lambda x: x in string.ascii_lowercase, s.lower())
# using set instead (and contains)
def test_filter_set(s):
return filter(letter_set.__contains__, s).lower()
# Tomalak solution
def test_regex(s):
return re.sub('[^a-z]', '', s.lower())
# Dana's
def test_str_join(s):
return ''.join(c for c in s.lower() if c in string.ascii_lowercase)
# Modified to use a set.
def test_str_join_set(s):
return ''.join(c for c in s.lower() if c in letter_set)
# Translate approach.
def test_translate(s):
return string.translate(s, tab, deletions)
for test in sorted(globals()):
if test.startswith("test_"):
assert globals()[test](s)=='atherrandom'
print "%30s : %s" % (test, timeit.Timer("f(s)",
"from __main__ import %s as f, s" % test).timeit(200000))
Это дает мне:
test_filter : 2.57138351271
test_filter_set : 0.981806765698
test_regex : 3.10069885233
test_str_join : 2.87172979743
test_str_join_set : 2.43197956381
test_translate : 0.335367566218
[Изменить] Обновлено с помощью фильтрующих решений. (Обратите внимание, что использование set.__contains__
здесь имеет большое значение, поскольку оно позволяет избежать вызова функции для лямбда.
Ответ 2
>>> filter(str.isalpha, "This is a Test").lower()
'thisisatest'
>>> filter(str.isalpha, "[email protected]#$&( er Ra{}|?>ndom").lower()
'atherrandom'
Ответ 3
Не особенно эффективное время работы, но, конечно, приятнее на бедных, усталых глазах кодера:
def strip_string_and_lowercase(s):
return ''.join(c for c in s.lower() if c in string.ascii_lowercase)
Ответ 4
Я бы:
- строчная строка
- замените все
[^a-z]
на ""
Вроде:
def strip_string_to_lowercase():
nonascii = re.compile('[^a-z]')
return lambda s: nonascii.sub('', s.lower().strip())
EDIT: Оказывается, оригинальная версия (ниже) очень медленная, хотя можно добиться некоторой производительности, превратив ее в закрытие (выше).
def strip_string_to_lowercase(s):
return re.sub('[^a-z]', '', s.lower().strip())
Мои измерения производительности с 100 000 итерациями против строки
"[email protected]#$&( er Ra{}|?>ndom"
показало, что:
-
f_re_0 took 2672.000 ms
(это оригинальная версия этого ответа)
-
f_re_1 took 2109.000 ms
(это версия закрытия, показанная выше)
-
f_re_2 took 2031.000 ms
(версия закрытия, без избыточного strip()
)
-
f_fl_1 took 1953.000 ms
(развернуть filter
/lambda
версия)
-
f_fl_2 took 1485.000 ms
( Coady filter
версия)
-
f_jn_1 took 1860.000 ms
( Dana join
версия)
Для теста я не выполнил print
результаты.
Ответ 5
Метод Python 2.x translate
Преобразовать в нижний регистр и фильтровать не-ascii символы не-альфы:
from string import ascii_letters, ascii_lowercase, maketrans
table = maketrans(ascii_letters, ascii_lowercase*2)
deletechars = ''.join(set(maketrans('','')) - set(ascii_letters))
print "[email protected]#$&( er Ra{}|?>ndom".translate(table, deletechars)
# -> 'atherrandom'
Метод Python 3 translate
Фильтр не-ascii:
ascii_bytes = "[email protected]#$&(٠٫٢٥ er Ra{}|?>ndom".encode('ascii', 'ignore')
Используйте bytes.translate()
для преобразования в нижний регистр и удаления не-альфа-байтов:
from string import ascii_letters, ascii_lowercase
alpha, lower = [s.encode('ascii') for s in [ascii_letters, ascii_lowercase]]
table = bytes.maketrans(alpha, lower*2) # convert to lowercase
deletebytes = bytes(set(range(256)) - set(alpha)) # delete nonalpha
print(ascii_bytes.translate(table, deletebytes))
# -> b'atherrandom'
Ответ 6
Подобно @Dana, но я думаю, что это звучит как работа по фильтрации, и это должно быть видно в коде. Также без необходимости явного вызова join()
:
def strip_string_to_lowercase(s):
return filter(lambda x: x in string.ascii_lowercase, s.lower())
Ответ 7
Я добавил фильтры для кода Брайана:
import string, re, timeit
# Precomputed values (for str_join_set and translate)
letter_set = frozenset(string.ascii_lowercase + string.ascii_uppercase)
tab = string.maketrans(string.ascii_lowercase + string.ascii_uppercase,
string.ascii_lowercase * 2)
deletions = ''.join(ch for ch in map(chr,range(256)) if ch not in letter_set)
s="[email protected]#$&( er Ra{}|?>ndom"
def test_original(s):
tmpStr = s.lower().strip()
retStrList = []
for x in tmpStr:
if x in string.ascii_lowercase:
retStrList.append(x)
return ''.join(retStrList)
def test_regex(s):
return re.sub('[^a-z]', '', s.lower())
def test_regex_closure(s):
nonascii = re.compile('[^a-z]')
def replacer(s):
return nonascii.sub('', s.lower().strip())
return replacer(s)
def test_str_join(s):
return ''.join(c for c in s.lower() if c in string.ascii_lowercase)
def test_str_join_set(s):
return ''.join(c for c in s.lower() if c in letter_set)
def test_filter_set(s):
return filter(letter_set.__contains__, s.lower())
def test_filter_isalpha(s):
return filter(str.isalpha, s).lower()
def test_filter_lambda(s):
return filter(lambda x: x in string.ascii_lowercase, s.lower())
def test_translate(s):
return string.translate(s, tab, deletions)
for test in sorted(globals()):
if test.startswith("test_"):
print "%30s : %s" % (test, timeit.Timer("f(s)",
"from __main__ import %s as f, s" % test).timeit(200000))
Это дает мне:
test_filter_isalpha : 1.31981746283
test_filter_lambda : 2.23935583992
test_filter_set : 0.76511679557
test_original : 2.13079176264
test_regex : 2.44295629752
test_regex_closure : 2.65205913042
test_str_join : 2.25571266739
test_str_join_set : 1.75565888961
test_translate : 0.269259640541
Похоже, что isalpha использует аналогичный алгоритм, по крайней мере, в терминах O(), для заданного алгоритма.
Edit:
Добавлен набор фильтров и переименован в функции фильтра, чтобы быть немного более понятным.
Ответ 8
>>> import string
>>> a = "[email protected]#$&( er Ra{}|?<ndom"
>>> ''.join(i for i in a.lower() if i in string.ascii_lowercase)
'otheraltndom'
делает то же самое, что и вы.
Ответ 9
Это типичное приложение сравнения списков:
import string
s = "[email protected]#$&( er Ra{}|?<ndom"
print ''.join(c for c in s.lower() if c in string.ascii_lowercase)
Он не будет отфильтровывать "<" (html-объект), как в вашем примере, но я предполагаю, что это была случайная проблема и проблема прошлого.
Ответ 10
Лично я бы использовал регулярное выражение, а затем преобразовал последнюю строку в нижний регистр. Я понятия не имею, как записать его в Python, но основная идея заключается в следующем:
- Удалить символы в строке, которые не соответствуют регистрозависимому регулярному выражению "
\w
"
- Преобразование строки в нижний регистр
или наоборот.
Ответ 11
Python 2.x:
import string
valid_chars= string.ascii_lowercase + string.ascii_uppercase
def only_lower_ascii_alpha(text):
return filter(valid_chars.__contains__, text).lower()
Работает с аргументами str
или unicode
.
>>> only_lower_ascii_alpha("Hello there 123456!")
'hellothere'
>>> only_lower_ascii_alpha(u"435 café")
u'caf'