Python - эффективный метод удаления всех не-букв и замены их символами подчеркивания

def format_title(title):  
  ''.join(map(lambda x: x if (x.isupper() or x.islower()) else '_', title.strip()))

Что-нибудь быстрее?

Ответ 1

Более быстрый способ сделать это - использовать str.translate() Это примерно в 50 раз быстрее, чем ваш путь.

# You only need to do this once
>>> title_trans=''.join(chr(c) if chr(c).isupper() or chr(c).islower() else '_' for c in range(256))

>>> "[email protected]%^".translate(title_trans)
'abcde________'

# Using map+lambda
$ python -m timeit '"".join(map(lambda x: x if (x.isupper() or x.islower()) else "_", "[email protected]#$".strip()))'
10000 loops, best of 3: 21.9 usec per loop

# Using str.translate
$ python -m timeit -s 'titletrans="".join(chr(c) if chr(c).isupper() or chr(c).islower() else "_" for c in range(256))' '"[email protected]#$".translate(titletrans)'
1000000 loops, best of 3: 0.422 usec per loop

# Here is regex for a comparison
$ python -m timeit -s 'import re;transre=re.compile("[\W\d]+")' 'transre.sub("_","[email protected]#$")'
100000 loops, best of 3: 3.17 usec per loop

Вот версия для unicode

# coding: UTF-8

def format_title_unicode_translate(title):
    return title.translate(title_unicode_trans)

class TitleUnicodeTranslate(dict):
    def __missing__(self,item):
        uni = unichr(item)
        res = u"_"
        if uni.isupper() or uni.islower():
            res = uni
        self[item] = res
        return res
title_unicode_trans=TitleUnicodeTranslate()

print format_title_unicode_translate(u"Metallica Μεταλλικα")

Обратите внимание, что греческие буквы считаются верхними и нижними, поэтому они не заменяются. Если они должны быть заменены, просто измените условие на

        if item<256 and (uni.isupper() or uni.islower()):

Ответ 2

import re
title = re.sub("[\W\d]", "_", title.strip())

должен быть быстрее.

Если вы хотите заменить последовательность смежных не-букв одним подчеркиванием, используйте

title = re.sub("[\W\d]+", "_", title.strip())

что еще быстрее.

Я просто провел сравнение времени:

C:\>python -m timeit -n 100 -s "data=open('test.txt').read().strip()" "''.join(map(lambda x: x if (x.isupper() or x.islower()) else '_', data))"
100 loops, best of 3: 4.51 msec per loop

C:\>python -m timeit -n 100 -s "import re; regex=re.compile('[\W\d]+'); data=open('test.txt').read().strip()" "title=regex.sub('_',data)"
100 loops, best of 3: 2.35 msec per loop

Это также будет работать и с строками Unicode (под Python 3, \W соответствует любой символ, который не является символом слова Unicode. В Python 2 вам необходимо дополнительно установить для него флаг UNICODE).

Ответ 3

Вместо (x.isupper() or x.islower()) вы сможете использовать x.isalpha(). Метод isalpha() может возвращать True для '_' (я не помню, если он это делает или нет), но затем вы просто замените '_' на '_', чтобы не нанести вреда. (Спасибо, что указали это, KennyTM.)

Ответ 4

Интересно, что по моим собственным причинам я написал быстрый script, чтобы протестировать различные подходы, перечисленные здесь, а также просто удалить лямбду, которую я ожидал (неправильно), ускорит исходное решение.

Короткий вариант заключается в том, что метод str.translate удаляет других. В отличие от решения регулярных выражений, в то время как второе, верно, как написано выше.

Вот моя тестовая программа:

import re
from time import time


def format_title(title):
    return ''.join(map(lambda x: x if (x.isupper() or x.islower()) else "_",
                       title.strip()))


def format_title_list_comp(title):
    return ''.join([x if x.isupper() or x.islower() else "_" for x in
                    title.strip()])


def format_title_list_comp_is_alpha(title):
    return ''.join([x if x.isalpha() else "_" for x in title.strip()])


def format_title_is_alpha(title):
    return ''.join(map(lambda x: x if x.isalpha() else '_', title.strip()))


def format_title_no_lambda(title):

    def trans(c):
        if c.isupper() or c.islower():
            return c
        return "_"

    return ''.join(map(trans, title.strip()))


def format_title_no_lambda_is_alpha(title):

    def trans(c):
        if c.isalpha():
            return c
        return "_"

    return ''.join(map(trans, title.strip()))


def format_title_re(title):
    return re.sub("[\W\d]+", "_", title.strip())


def format_title_re_corrected(title):
    return re.sub("[\W\d]", "_", title.strip())


TITLE_TRANS = ''.join(chr(c) if chr(c).isalpha() else '_' for c in range(256))


def format_title_with_translate(title):
    return title.translate(TITLE_TRANS)


ITERATIONS = 200000
EXAMPLE_TITLE = "abc123def_$%^!FOO BAR*bazx-bif"


def timetest(f):
    start = time()
    for i in xrange(ITERATIONS):
        result = f(EXAMPLE_TITLE)
    diff = time() - start
    return result, diff


baseline_result, baseline_time = timetest(format_title)


def print_result(f, result, time):
    if result == baseline_result:
        msg = "CORRECT"
    else:
        msg = "INCORRECT"
    diff = time - baseline_time
    if diff < 0:
        indicator = ""
    else:
        indicator = "+"
    pct = (diff / baseline_time) * 100
    print "%s: %0.3fs %s%0.3fs [%s%0.4f%%] (%s - %s)" % (
        f.__name__, time, indicator, diff, indicator, pct, result, msg)


print_result(format_title, baseline_result, baseline_time)

print "----"

for f in [format_title_is_alpha,
          format_title_list_comp,
          format_title_list_comp_is_alpha,
          format_title_no_lambda,
          format_title_no_lambda_is_alpha,
          format_title_re,
          format_title_re_corrected,
          format_title_with_translate]:
    alt_result, alt_time = timetest(f)
    print_result(f, alt_result, alt_time)

И вот результаты:

format_title: 3.121s +0.000s [+0.0000%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
----
format_title_is_alpha: 2.336s -0.785s [-25.1470%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_list_comp: 2.369s -0.751s [-24.0773%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_list_comp_is_alpha: 1.735s -1.386s [-44.4021%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_no_lambda: 2.992s -0.129s [-4.1336%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_no_lambda_is_alpha: 2.377s -0.744s [-23.8314%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_re: 1.290s -1.831s [-58.6628%] (abc_def__FOO_BAR_bazx_bif - INCORRECT)
format_title_re_corrected: 1.338s -1.782s [-57.1165%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
format_title_with_translate: 0.098s -3.022s [-96.8447%] (abc___def_____FOO_BAR_bazx_bif - CORRECT)
  • EDITED: я добавил вариант, который показывает, что понимание списков значительно улучшает первоначальную реализацию, а также правильную реализацию регулярного выражения, которая показывает, что она по-прежнему почти так же быстро, когда она правильная. Конечно, str.translate все еще выигрывает руки.

Ответ 5

import string,sys
letters=string.letters
mystring = list("abc134#[email protected]##$%%$*&(()#def")
for n,c in enumerate(mystring):
  if not c in letters:
    mystring[n]="_"
print ''.join(mystring)