【问题标题】:Perfomance of concatenating strings vs joining lists? [duplicate]连接字符串与加入列表的性能? [复制]
【发布时间】:2021-11-11 18:23:29
【问题描述】:

为了好玩,我正在做一个小的 Python 练习。

我需要根据一些输入和一些逻辑打印一些看起来像这样的东西:

.P....
.I....
.D....
.Z....
BANANA
.M....
.A....

现在我在构建带有点的字符串时有些吃力。 所以我需要一个函数,它接受一个数字 n 一个数字 i 和一个字符,就像这样;

def buildstring(n,i, char):

然后返回一个长度为 n 的字符串,仅由点组成,其中第 i 个字符是给定的字符。

我目前有这个尝试:

def buildprint(n,i,char):

    start = "".join(['.']*(i))
    mid = char 
    end = "".join(['.']*(n - i-1))
    print(start+mid+end)


buildprint(10,3,"j")

产生:

...j......

这是我想要的。但是我的解决方案将按时进行评分,但我不确定这是否是连接字符串的最有效方式,因为我记得连接字符串通常是程序的缓慢部分,或者这是否会更有性能:

def buildprint(n,i,char):

    start = ['.']*(i)
    mid = [char] 
    end = ['.']*(n - i-1)
    print("".join(start+mid+end))

所以问题实际上是关于字符串连接的工作方式,而不是加入列表

【问题讨论】:

  • '.'*i + char + '.'*(n-i-1)
  • "".join(start+mid+end) 会更糟,因为您仍在与+ 连接,但随后将使用join 重新迭代生成的字符串。你的第一种方法要好得多。对于初始部分,您不需要join+。您可以直接将字符串相乘:'.' * i。不过,我仍然会在最后一部分坚持使用+。那应该没关系。
  • "".join(['.']*(i) 应该只是 '.'*i
  • 这不会影响运行时间吗?我曾经听说过加入更快,因为它只是 O(n) 而连接有时是 O(n*n) 或其他东西
  • 尽管有人告诉我,字符串连接实际上已经在最新版本的 Python 中进行了优化。我记得一个关于在串联过程中在幕后改变字符串的问题。

标签: python string performance


【解决方案1】:

我会使用完全不同的方法,使用字符串插值和 f-string

def buildprint(n,i,char):
    start = "".join(['.']*(i))
    mid = char
    end = "".join(['.']*(n - i-1))
    print(start+mid+end)


def build_f_string(total_char, dots, char):
    total_char -= dots + 1
    print(f'{"." * dots}{char}{"." * total_char}')

test_1 = (10, 3, "j")
buildprint(*test_1)
build_f_string(*test_1)

一个班轮

build_f_string = lambda total_char, dots, char : print(f'{"." * dots}{char}{"." * (total_char - (dots + 1 ))}')
build_f_string(*test_1)

性能

  1. 做一个定时器函数

     from time import perf_counter
     from functools import wraps
    
    
     def timer(runs):
         def _timer(f,):
             @wraps(f)
             def wrapper(*args, **kwargs):
                 start = perf_counter()
                 for test in range(runs):
                     res = f(*args, **kwargs)
                 end = perf_counter()
                 print(f'{f.__name__}: Total Time: {end - start}')
                 return res
    
             return wrapper
         return _timer
    
  2. 装饰我们的功能

         TEST_RUNS = 100_000
    
    
         @timer(runs=TEST_RUNS)
         def buildprint(n, i, char):
             start = "".join(['.'] * (i))
             mid = char
             end = "".join(['.'] * (n - i - 1))
             return start + mid + end
    
    
         @timer(runs=TEST_RUNS)
         def build_f_string(total_char, dots, char):
            return f'{"." * dots}{char}{"." * (total_char - dots + 1)}'
    
  3. 运行测试

     test_1 = (10, 3, "j")
     buildprint(*test_1)
     build_f_string(*test_1)
    
     # Output
    
     # buildprint: Total Time: 0.06150109999999999
     # build_f_string: Total Time: 0.027191400000000004
    

进入下一个阶段

我们可以使用 Python Deque

双端队列是堆栈和队列的概括(名称发音为“deck”,是“双端队列”的缩写)。双端队列支持线程安全、内存高效的从双端队列的任一侧追加和弹出,在任一方向上的 O(1) 性能大致相同。

def build_withdeque(n, i, char):
    hyper_list = deque([char]) # initialize the deque with the char, is in the middle already
    hyper_list.extendleft(["." for _ in range(i)]) # Add left side
    hyper_list.extend(["." for _ in range(n - i - 1)])
    return "".join(hyper_list)

运行测试

    from collections import deque
    TEST_RUNS = 10_00_000
    
    @timer(runs=TEST_RUNS)
    def build_withdeque(n, i, char):
        hyper_list = deque([char]) # initialize the deque with the char, is in the middle already
        hyper_list.extendleft(["." for _ in range(i)]) # Add left side
        hyper_list.extend(["." for _ in range(n - i - 1)])
        return "".join(hyper_list)
    
    test_1 = (10, 3, "j")
    buildprint(*test_1)
    build_f_string(*test_1)
    build_withdeque(*test_1)
    
    # buildprint: Total Time: 0.546178
    # build_f_string: Total Time: 0.29445559999999993
    # build_withdeque: Total Time: 1.4074019

还是不太好..

预建列表

@timer(runs=TEST_RUNS)
def build_list_complete(n, i, char):
    return ''.join(["." * i, char, "." * (n - i - 1)])



test_1 = (10, 3, "j")
buildprint(*test_1)
build_f_string(*test_1)
build_withdeque(*test_1)
build_list_complete(*test_1)

# buildprint: Total Time: 0.5440142
# build_f_string: Total Time: 0.3002815999999999
# build_withdeque: Total Time: 1.4215970999999998
# build_list_complete: Total Time: 0.30323829999999985

最终结果

# TEST_RUNS = 10_00_000

test_1 = (10, 3, "j")
buildprint(*test_1)
build_f_string(*test_1)
build_withdeque(*test_1)
build_list_complete(*test_1)

# buildprint: Total Time: 0.6512364
# build_f_string: Total Time: 0.2695955000000001
# build_withdeque: Total Time: 14.0086889
# build_list_complete: Total Time: 3.049139399999998

别等这么快

正如@MisterMiyagi 指出的那样,使用return "." * i + char + ("." * (n - i + 1)) 可能等同于使用return f'{"." * dots}{char}{"." * (total_char - dots + 1)}'

但实际上它更快

看看这个:

def SuperHyperMegaUltraFastButBasicallyLikeFStringThatMisterMiyagiSayd(n, i, char):
    return "." * i + char + ("." * (n - i + 1))

运行一些重复 100_00_000 次的测试。

@timer(runs=TEST_RUNS)
def SuperHyperMegaUltraFastButBasicallyLikeFStringThatMisterMiyagiSaid(n, i, char):
    return "." * i + char + ("." * (n - i + 1))


test_1 = (10, 3, "j")
buildprint(*test_1)
build_f_string(*test_1)
build_withdeque(*test_1)
build_list_complete(*test_1)

    
    buildprint: Total Time: 5.3067210000000005
    build_f_string: Total Time: 2.721678
    build_withdeque: Total Time: 14.302031600000001
    build_list_complete: Total Time: 3.0364287999999995        SuperHyperMegaUltraFastButBasicallyLikeFStringThatMisterMiyagiSayd: Total Time: 2.440598699999999


    buildprint: Total Time: 5.3067210000000005
    build_f_string: Total Time: 2.721678
    build_withdeque: Total Time: 14.302031600000001
    build_list_complete: Total Time: 3.0364287999999995
    SuperHyperMegaUltraFastButBasicallyLikeFStringThatMisterMiyagiSayd 

Total Time: 2.440598699999999

f-string 字符串连接显然是赢家,除非另有证明

【讨论】:

  • “f-string”的优势是因为build_f_string使用了更高效的字符串操作,而不是因为包裹f-string。对我来说,在 buildprint 中仅使用 return "." * i + char + ("." * (n - i + 1)) 与 f-string 变体的性能几乎相同。
  • @MisterMiyagi AS 测试证明,f 字符串更快。
  • 这就像说走路比开车快,因为你必须先给汽车加油。如果让字符串连接和 f-string 使用相同的子字符串,它们是可比较的。
  • @MisterMiyagi 如何让字符串连接和 f 字符串使用相同的子字符串?什么意思?
  • 如上所述,使用return "." * i + char + ("." * (n - i + 1))return f'{"." * dots}{char}{"." * (total_char - dots + 1)}' 相当。
猜你喜欢
  • 2012-02-05
  • 2019-05-18
  • 2017-12-24
  • 2020-06-26
  • 1970-01-01
  • 1970-01-01
  • 2017-02-04
  • 2010-09-12
  • 2010-10-03
相关资源
最近更新 更多