Another solution, first some benchmark results, with random strings of 70 to 560 letters (I think 140 is the maximum in the actual tests):
n= 70 n=140 n=280 n=560
0.03 0.06 0.11 0.24 Kelly_linear (I might show this one later)
0.47 1.71 6.58 25.85 Kelly (this is the one I am already showing)
0.77 3.17 12.50 52.57 Jacopo_2
1.59 6.38 25.62 107.81 Jacopo_1
1.69 7.09 27.93 110.67 Fabio
1.82 7.69 30.17 120.77 BrokenBenchmark
My solution is the function Kelly below. First I double every letter, then trim first and last letter. So rybbry becomes ryybbbbrry. Essentially that gives me all the pairs, if you think spaces into it then it would be ry yb bb br ry. Then I replace pairs of different letters with what that pair shall become, but in uppercase. So I get BRbbYB. Then replace pairs of equal letters with what that pair shall become: BRBYB. Then just go back to lower case. Repeat until done.
Full benchmark code (Try it online!):
def Kelly(colours):
while len(colours) != 1:
colours = (colours
.replace('b', 'bb')
.replace('r', 'rr')
.replace('y', 'yy')
[1:-1]
.replace('br', 'Y').replace('rb', 'Y')
.replace('by', 'R').replace('yb', 'R')
.replace('ry', 'B').replace('yr', 'B')
.replace('bb', 'B')
.replace('rr', 'R')
.replace('yy', 'Y')
.lower())
return colours
def Jacopo_1(colours):
while len(colours) > 1:
new_colours = ''
for i in range(len(colours) - 1):
if colours[i:i+2] == 'rr' or colours[i:i+2] == 'by' or colours[i:i+2] == 'yb':
new_colours = new_colours + 'r'
elif colours[i:i+2] == 'bb' or colours[i:i+2] == 'ry' or colours[i:i+2] == 'yr':
new_colours = new_colours + 'b'
else:
new_colours = new_colours + 'y'
colours = new_colours
if len(colours) == 1:
if colours[0] == 'b':
return 'b'
if colours[0] == 'y':
return 'y'
if colours[0] == 'r':
return 'r'
def Jacopo_2(colours):
colours = list(colours)
length = len(colours)
for i in range(length-1):
for k in range(length - 1 - i):
colours[k] = color_sum(colours[k:k+2])
return colours[0]
def color_sum(pair):
if pair[0] == pair[1]:
return pair[0]
if 'r' in pair:
if 'b' in pair:
return 'y'
return 'b'
return 'r'
def BrokenBenchmark(colours):
result = colours
while len(result) != 1:
letters = []
for fst, snd in zip(result, result[1:]):
if fst == snd:
letters.append(fst)
else:
[letter_to_add] = list(set('ryb') - set(fst) - set(snd))
letters.append(letter_to_add)
result = ''.join(letters)
return result
def Fabio(x):
while len(x) > 1:
x = [f'''{i[0] if i[0] == i[1]
else set("rby").difference(set((i[0],
i[1]))).pop()}''' for i in [f'{x[i]}{x[i+1]}'
for i in range(len(x)-1)]]
return x[0]
lengths = 70, 140, 280, 560
funcs = [
Kelly,
Jacopo_2,
Jacopo_1,
Fabio,
BrokenBenchmark,
]
from timeit import default_timer as timer, repeat
import random
Tss = [[] for _ in funcs]
for length in lengths:
tss = [[] for _ in funcs]
for _ in range(5):
colours = ''.join(random.choices('bry', k=length))
def run():
global result
result = func(colours)
expect = None
for func, ts in zip(funcs, tss):
t = min(repeat(run, number=1))
if expect is None:
expect = result
assert result == expect
ts.append(t)
# print(*('%7.3f ms ' % (t * 1e3) for t in sorted(ts)[:3]), func.__name__)
# print()
print(*(f' n={length:3} ' for length in lengths))
for func, Ts, ts in zip(funcs, Tss, tss):
Ts.append(min(ts))
print(*('%6.2f ' % (t * 1e3) for t in Ts), func.__name__)
print()
nyou taken-1pairs and compact all of them at once. It'sO(n^2)actually with a lot strings copied over and over, so it's not strange it works so slow. Thinking how it could be improved.colours[i:i+2]to a variable, rather than performing that slice six times.