【问题标题】:Python - efficient method to remove all non-letters and replace them with underscoresPython - 删除所有非字母并用下划线替换它们的有效方法
【发布时间】:2011-01-11 09:09:52
【问题描述】:
def format_title(title):  
  ''.join(map(lambda x: x if (x.isupper() or x.islower()) else '_', title.strip()))

更快吗?

【问题讨论】:

    标签: python string performance


    【解决方案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))
    
    >>> "abcde1234!@%^".translate(title_trans)
    'abcde________'
    
    # Using map+lambda
    $ python -m timeit '"".join(map(lambda x: x if (x.isupper() or x.islower()) else "_", "abcd1234!@#$".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))' '"abcd1234!@#$".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("_","abcd1234!@#$")'
    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()):
    

    【讨论】:

    • +1,好主意。我能想到的唯一缺点是,如果必须考虑非 ASCII 字符,这将无法在 Unicode 字符串上正常工作。
    • @Tim,unicode 也有翻译 - 虽然语义不同,让我看看我能不能让它工作......
    • @Tim,unicode 版本已更新。翻译映射是按需构建的,因此随着更多字符串的翻译,错过的机会会越来越少。
    • 这很可爱,但不是一个非常好的主意。正则表达式版本更清晰、更快捷。如果在任意输入上使用此 dict,它也将无限制地增长。 (边界检查 UTF-8 值可以避免这成为潜在的拒绝服务攻击,但并非所有应用都希望这样做,通常不需要这样做。)
    【解决方案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 标志)。

    【讨论】:

    • 您使用 timeit 的方式是一直计算打开和读取文件的时间。您应该将这些内容移至 -s 部分以获得有意义的结果
    • 谢谢,你当然是对的。由于这在两个示例中都已完成,因此两个示例中的错误应该(并且我刚刚尝试过)相同。有趣的是,我发现预编译正则表达式并没有什么不同。我还是更新了时间示例。
    • 请注意,您不能使用 re.sub 设置标志;您必须使用 re.compile() 来指定标志并在结果上调用 sub() 。 (这是一个奇怪的 API 遗漏。)删除“+”版本你的答案会更好;这不是他要求的,所以这只是分散注意力。
    • 我的答案中确实有两个版本,因此他可以选择更适合他需要的版本。有趣的是,我没有发现预编译正则表达式有很大的性能提升......
    • 问题告诉你他需要哪一个。正确答案至少应该先出现,然后再交替出现。
    【解决方案3】:

    您应该能够使用x.isalpha() 而不是(x.isupper() or x.islower())isalpha() 方法可能会为 '_' 返回 True(我不记得是否这样做),但你最终会用 '_' 替换 '_',所以不会造成任何伤害。 (感谢您指出这一点,KennyTM。)

    【讨论】:

    • 实际上,它可能会将“_”本身视为一个字母字符,所以可能不是。试试看。
    • _ 替换为_(或不替换)是无害的。
    【解决方案4】:

    出于我自己的原因对此感到好奇,我编写了一个快速脚本来测试此处列出的不同方法,同时仅删除我预期(错误地)会加快原始解决方案的 lambda。

    简短的版本是 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)
    
    • 已编辑:我添加了一个变体,显示列表理解显着改进了原始实现以及正确的正则表达式实现,表明它在正确时仍然几乎一样快。当然,str.translate 仍然胜出。

    【讨论】:

    • 正则表达式解决方案不正确,因为它将几个相邻的非字母替换为一个下划线。在字符类之后删除+,它将是正确的,尽管速度较慢。我想问题是你是否真的想在你的替换字符串中有很长的下划线......
    【解决方案5】:
    import string,sys
    letters=string.letters
    mystring = list("abc134#$@e##$%%$*&(()#def")
    for n,c in enumerate(mystring):
      if not c in letters:
        mystring[n]="_"
    print ''.join(mystring)
    

    【讨论】:

      猜你喜欢
      • 2018-11-25
      • 1970-01-01
      • 1970-01-01
      • 2012-10-26
      • 2022-12-12
      • 1970-01-01
      • 2013-08-22
      • 2011-09-20
      • 1970-01-01
      相关资源
      最近更新 更多