python中有效的字符串char消除

时间:2017-06-29 13:31:54

标签: python string performance

我有一个用于循环遍历字符串的python,如果当前的char应保留或被删除,则必须决定char-by-char。

假设我有一个函数fun(character,state),它接收一个char和一些状态参数,如果要保留char,则返回True,并返回一个新的state。例如:

def fun(c,state):
    if state%5==0:
        return False, state+1
    else:
        return True, state+2

我有两个想法如何使用此函数对字符串进行迭代(顺序),但我无法根据运行时复杂性空间使用情况来决定它们之间(或其他) 权衡:

选项1:

def option1(string):
    state = 0
    i =0
    while i<len(string):
        answer, state = fun(string[i],state)
        if not answer:
             string = string[:i]+string[i+1:]
        else:
            i+=1
    return string

选项2:

def option2(string):
    result  = ''
    state = 0
    for c in string:
        answer, state = fun(c,state)
        if answer:
             result+=c
   return result

有没有人有更好的方法/解决方案?

编辑:一些时间结果:

在一个长字符串(67976个字符)上,结果是:

  • option1 - 205 ms
  • option2 - 46 ms

看起来像用string[:i]+string[i+1:]消除第i个字符不是个好主意。

2 个答案:

答案 0 :(得分:3)

不建议使用+=进行字符串连接。 更好地使用列表并加入它。

def option2(string):
    result  = []
    state = 0
    for c in string:
        answer, state = fun(c, state)
        if answer:
             result.append(c)
   return ''.join(result)

这是一个很好的explanation为什么。 虽然在CPython中优化+=(对于某些情况?),但在其他实现中,例如PyPy,它可能会非常慢。

答案 1 :(得分:1)

在CPython中,一个好的性能经验法则是“避免在紧密循环中调用函数”。与简单的迭代计算相比,开销往往花费大量时间。在您的情况下,“强力”方法是简单地内联遍历您的字符串的循环中的逻辑:

def option4(strng):
    result  = []
    state = 0
    for c in strng:
        if state % 5 == 0:
            state += 1
        else:
            result.append(c)
            state += 2
    return ''.join(result)

这可能会让人觉得有点笨拙,更加“pythonic”的方法可能是使用python生成器,它将可迭代的初始状态作为输入并产生匹配的成员。

def filtergen(iter, initstate):
    state = initstate
    for c in iter:
        if state % 5 == 0:
            state += 1
        else:
            state += 2
            yield c

def option5(strng):
    return ''.join(filtergen(strng, 0))

这减少了呼叫开销 - 您只需要与匹配元素一样多的呼叫,并且作为附带好处,使调用者无需跟踪状态。此外,您不再需要中间列表。过滤器仍然是通用的 - 它适用于任何类型的迭代,而不仅仅是字符串。

将所有这些放在一起并衡量绩效:

s = 'T0kzcZ0x8VQY8ulgFKU8D1MlIUULRsVsNZMnXbjliUEES6sEIVUzpjxlHLG59z' * 1200

def fun(c, state):
    if state % 5 == 0:
        return False, state + 1
    else:
        return True, state + 2

def option3(strng):
    result  = []
    state = 0
    for c in strng:
        answer, state = fun(c, state)
        if answer:
             result.append(c)
    return ''.join(result)

def option4(strng):
    result  = []
    state = 0
    for c in strng:
        if state % 5 == 0:
            state += 1
        else:
            result.append(c)
            state += 2
    return ''.join(result)

def filtergen(iter, initstate):
    state = initstate
    for c in iter:
        if state % 5 == 0:
            state += 1
        else:
            state += 2
            yield c

def option5(strng):
    return ''.join(filtergen(strng, 0))

import timeit
print("string length is " + str(len(s)))
print(timeit.timeit('option3(s)', globals=globals(), number=100))
print(timeit.timeit('option4(s)', globals=globals(), number=100))
print(timeit.timeit('option5(s)', globals=globals(), number=100))

print(option3(s) == option(4) == option5(s))

在我的小笔记本电脑上,输出看起来像这样(时间以秒为单位):

pvg /tmp ➤  python3 s.py
string length is 74400
2.488818967998668
1.3291878789968905
1.268602224998176
True

'内联逻辑'和生成器都很容易胜过天真的每元素调用方法。