【问题标题】:Can someone help me optimize this for speed?有人可以帮我优化这个速度吗?
【发布时间】:2019-08-04 15:43:39
【问题描述】:

这里是优化代码:

import random

rules = {
    'X': {
        1: 'FXFF+',
        2: '+XXF]',
    }
}

L_string = 'FX'

def next_char(c):
    isrule = rules.get(c, c)
    if not isrule == c:
        _, choice = random.choice(list(rules.get(c).items()))
        return choice
    else:
        return isrule

for _ in range(6):
    L_string = ''.join([next_char(c) for c in L_string])

这里发生的是字符串中字符的递归替换。所以一步一步来:

  1. 从“FX”开始
  2. 遍历字符串并将每个“X”替换为随机规则,即“FXFF+”或“+XXF]”。也就是说,对于每个“X”,规则是随机的。这不是每次遍历字符串的随机规则。
  3. 重复此操作 5 次

最终结果是一个较长的字符串,由起始的“F”和规则“FXFF+”、“+XXF]”以某种随机组合组成。该表说明:

+------------+--------------------+--------------------+
| ITERATIONS |       STRING       | CHOSEN RULE VECTOR |
+------------+--------------------+--------------------+
|          1 | FFXFF+             | [rule 1]           |
|          2 | FF+XXF]FF+         | [rule 2]           |
|          3 | FF+FXFF++XXF]F]FF+ | [rule 1, rule 2]   |
|          4 | ...                | ...                |
|          5 | ...                | ...                |
+------------+--------------------+--------------------+

我读到 re.sub 是替换字符串最快的,但问题是每个字符的随机化。 Re.sub 不会为此工作。

谢谢大家!

【问题讨论】:

  • 那个字符串有多长?这真的是性能瓶颈吗?
  • 什么是性能问题?导致性能下降的实际规则数量和L_string 长度是多少?
  • 只是小小的改进:choice = random.choice(list(rules.get(c).values()))
  • 想象规则是“XXXXF”和“XXXXF”。最后字符串的长度变为 5462,这必须为几十万这样的人完成。此外,规则长度可能会在稍后增加到 10 个字符,然后我的电脑崩溃,因为字符串对于内存来说太大了
  • @OlvinR​​oght 抱歉,对于这个例子,我省略了字典的“键”值,但我的完整代码中实际上需要它。所以它的key, choice = random.choice(list(rules.get(c).items()))

标签: python string optimization replace


【解决方案1】:

假设字符 '{' 和 '}' 没有出现在您的模式中,您可以使用模板语言做一些诡计,然后去掉大括号。这在我的机器上快了 2.5 倍:

def format_based():
    rules = {
        'X': lambda: random.choice(["{F}{X}{F}{F}+", "+{X}{X}{F}{J}]"]),
        'F': lambda: 'F',
        'J': lambda: 'J',
    }
    def get_callbacks():
        while True:
            yield {k: v() for k, v in rules.items()}
    callbacks = get_callbacks()
    L_string = "{F}{X}"
    for _ in range(5):
        L_string = L_string.format(**next(callbacks))
    return re.sub('{|}', '', L_string)

【讨论】:

  • 即使有了这个改进,仍然是最快的方法stackoverflow.com/a/57348467/7167076
  • 这是最快的,我已经编辑了我的答案并将其添加到我的迷你基准中
  • 不太确定它是如何工作的,但这是我的问题 :)
  • 我确信可以进一步优化随机化和回调,例如就像@altunyurt 的回答一样。
  • 哦,可惜,现在我的规则必须用花括号括起来
【解决方案2】:

对消耗大部分运行时间的函数进行简单的约 4 倍加速。


from random import random
from math import floor

def next_char2(c):
    if c not in rules:
        return c 

    d = rules[c]
    r = floor(random() * len(d))  # was int(...) before 
    # Rules start with key 1. 
    # Random brings a float between 0 and 1, therefore you need [r + 1] as key 
    return d[r + 1]


In [6]: %timeit next_char("X")
3.42 µs ± 32.5 ns per loop (mean ± std. dev. of 7 runs, 100000 loops each)

In [7]: %timeit next_char2("X")
814 ns ± 12.4 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

Edit: Changing the int with math.floor gives a little boost

In [10]: %timeit next_char2("X")                                                                
740 ns ± 8.57 ns per loop (mean ± std. dev. of 7 runs, 1000000 loops each)

可能有很大的优化空间。也许某个地方的记忆可能会给整个代码带来巨大的提升。

【讨论】:

    【解决方案3】:

    新的递归方法,大约快 1.6 倍,另一种方法,在我的 PC 上大约快 3.312 倍

    import re
    from random import random, choice
    from timeit import timeit
    from math import floor
    
    # --- ORIGINAL ---
    rules = {
        'X': {
            1: 'FXFF+',
            2: '+XXF]',
        }
    }
    
    def next_char(c):
        isrule = rules.get(c, c)
        if not isrule == c:
            _, _choice = choice(list(rules.get(c).items()))
            return _choice
        else:
            return isrule
    
    # --- ORIGINAL END ---
    
    def next_char2(c):
        if c not in rules:
            return c
    
        d = rules[c]
        r = floor(random() * len(d))  # was int(...) before
        # Rules start with key 1.
        # Random brings a float between 0 and 1, therefore you need [r + 1] as key
        return d[r + 1]
    
    choices=['FXFF+', '+XXF]']
    def next_substring(s, n):
        if s == '' or n == 0:
            return s
    
        first_char = s[:1]
        rest = s[1:]
    
        if first_char == 'X':
            first_char = choice(choices)
    
        if len(first_char) == 1:
            return first_char + (next_substring(rest, n) if 'X' in rest else rest)
        else:
            return (next_substring(first_char, n-1) if 'X' in first_char else first_char) + (next_substring(rest, n) if 'X' in rest else rest)
    
    format_rules = {
        'X': lambda: choice(["{F}{X}{F}{F}+", "+{X}{X}{F}]"]),
        'F': lambda: 'F',
        'J': lambda: 'J',
    }
    
    def format_based():
        def get_callbacks():
            while True:
                yield {k: v() for k, v in format_rules.items()}
        callbacks = get_callbacks()
        L_string = "{F}{X}"
        for _ in range(6):
            L_string = L_string.format(**next(callbacks))
        return re.sub(r'{|}', '', L_string)
    
    
    def method1():
        s = 0
        for i in range(100_000):
            L_string = 'FX'
            for _ in range(6):
                L_string = ''.join([next_char(c) for c in L_string])
            s += len(L_string)
        return s
    
    def method1b():
        s = 0
        for i in range(100_000):
            L_string = 'FX'
            for _ in range(6):
                L_string = ''.join([next_char2(c) for c in L_string])
            s += len(L_string)
        return s
    
    
    def method2():
        s = 0
        for i in range(100_000):
            L_string = 'FX'
            L_string = ''.join(next_substring(c, 6) if c=='X' else c for c in L_string)
            s += len(L_string)
        return s
    
    def method3():
        s = 0
        for i in range(100_000):
            L_string = format_based()
            s += len(L_string)
        return s
    
    rules2 = [
        ('FXFF+', '+XXF]')      # X=0
    ]
    
    def new_method2(s='FX'):
        final = [s]
        s = ''
        for _ in range(6):
            for c in final[-1]:
                if c == 'X':
                    s += rules2[0][floor(random() * len(rules2[0]))]    # rules2[0] because X=0
                else:
                    s += c
            final.append(s)
            s = ''
        return final[-1]
    
    def method4():
        s = 0
        for i in range(100_000):
            L_string = new_method2('FX')
            s += len(L_string)
        return s
    
    print('Average length of result string (100_000 runs):')
    print('{: <20}{: >20}'.format('Original:', method1() / 100_000))
    print('{: <20}{: >20}'.format('New method:', method2() / 100_000 ))
    print('{: <20}{: >20}'.format('@hilberts method:', method3() / 100_000 ))
    print('{: <20}{: >20}'.format('new_method2 method:', method4() / 100_000 ))
    print('{: <20}{: >20}'.format('altunyurt method:', method1b() / 100_000 ))
    
    print('{: <20}{: >20}'.format('Timing original:', timeit(lambda: method1(), number=1)))
    print('{: <20}{: >20}'.format('Timing new method:', timeit(lambda: method2(), number=1)))
    print('{: <20}{: >20}'.format('Timing @hilberts method:', timeit(lambda: method3(), number=1)))
    print('{: <20}{: >20}'.format('new_method2 method:', timeit(lambda: method4(), number=1)))
    print('{: <20}{: >20}'.format('altunyurt method:', timeit(lambda: method1b(), number=1)))
    

    结果:

    Average length of result string (100_000 runs):
    Original:                       85.17692
    New method:                     85.29112
    @hilberts method:               85.20096
    new_method2 method:             84.88892
    altunyurt method:               85.07668
    Timing original:       4.563865200005239
    Timing new method:    2.6940059370026574
    Timing @hilberts method:  1.9866539289942011
    new_method2 method:   1.3680451929976698
    altunyurt method:     1.7981422250013566
    

    编辑:添加了@hilberts 方法

    EDIT2:添加了另一种新方法,比原始方法快约 3.32 倍

    EDIT3:添加了@altunyurt 方法

    【讨论】:

    • 赢家是@hilberts方法!!
    • @ZackJoubert 是的,它也很原创 :)
    • @ZackJoubert 添加了新方法,比原来的方法快约 3.32 倍。也许有帮助。
    • 您介意根据@altunyurt 的答案计时所有其他算法吗?
    • @ZackJoubert 添加。到目前为止,我的速度最快。
    【解决方案4】:

    这会生成没有替换的输出:

    import random
    
    result = []
    write = result.append
    
    def X(level):
        if level == 0:
            write('X')
            return
        if random.randint(0,1):
            # X -> FXFF+
            write('F')
            X(level-1)
            write('FF+')
        else:
            # X -> +XXF]
            write('+')
            X(level-1)
            X(level-1)
            write('F]')
    
    def start():
        write('F')
        X(5)  # 5 = recursion depth
    
    start()
    print(''.join(result))
    

    【讨论】:

    • 哇!速度提高 50% 以上。但是,如果您有多个变量,比如“X”和“Y”,该怎么办?
    • 您可以为“Y”添加另一个函数。 X 可以调用Y(level-1),反之亦然。速度是通过将数据结构的规则重写为程序指令来实现的,因此灵活性稍差。
    【解决方案5】:

    您可以尝试使用带有替换值的re.sub 作为根据您的情况生成随机规则的函数。您可以将字符串写入磁盘中的文件并 set buffering 读取文件时,然后将替换值写入另一个文件,然后重命名文件并删除旧文件。希望这会有所帮助:)。

    import re
    import random
    rules = {
        'X': ['FXFF+','+XXF]'],
        "Y" : ['A','B']
    }
    
    L_string = 'FX'
    
    def next_char(c):
        return random.choice(rules[c.group()])
    
    for _ in range(6):
        L_string = re.sub('|'.join(rules.keys()),next_char,L_string)
        print(L_string)
    

    输出

    FFXFF+
    FFFXFF+FF+
    FFF+XXF]FF+FF+
    FFF++XXF]+XXF]F]FF+FF+
    FFF+++XXF]FXFF+F]+FXFF+FXFF+F]F]FF+FF+
    FFF++++XXF]FXFF+F]FFXFF+FF+F]+F+XXF]FF+F+XXF]FF+F]F]FF+FF+
    

    已编辑

    我进行了编辑以使其快速并通过递归调用生成最后一个字符串。请原谅我使用全局变量:P。

    import re
    import random
    rules = {
        'X': ['XX','XY'],
        'Y' : ['A', 'B']
    }
    
    L_string = 'FXY'
    depth = 6
    result = ''
    index = 0 
    
    def go(d,currentstring) :
        global result,depth
        if (d < depth):
            for c in currentstring:
                if c in rules:
                    go(d + 1,random.choice(rules[c])) 
                else:
                    result += c
        else:
            result += currentstring
    go(0,L_string)
    print(result)
    

    【讨论】:

    • 只是为了检查一下,这会为每个“X”随机化一个规则,对吗?
    • 如果有多个替换字符正确地说“X”和“Y”,每个都有两条规则,这将不起作用?
    • 是的,它为每个X 随机化,第二条评论等待一秒钟我会修改并更新它以工作
    • @ZackJoubert 我已经修改了,是我的错我以为你只有 X 作为规则,我已经概括了。
    • 谢谢,我会尽快检查速度
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2013-12-12
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多