New method with recursion, approx ~1.6x faster and another method, approx ~3.312x faster on my PC
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)))
The results:
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
EDIT: Added @hilberts method
EDIT2: Added another new method, ~3.32x faster than original
EDIT3: Added @altunyurt method