slow runtime for large input values - python

This code is supposed to take a string of r (red), b (blue) and y (yellow) values and combine them, pairwise, to find one resulting colour. For example, 'rybbry' -> 'brbyb' -> 'yyrr' -> 'ybr' -> 'ry' -> 'b'. It works for small inputs, but forever for big ones. Too many nested loops?
def colour_trio(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'
Tried this as well. Still runtime is too long.
def colour_trio(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'
Here is what the tester file looks like:
def colour_trio_generator(seed):
rng = random.Random(seed)
items = ''
for n in islice(pyramid(3, 4, 1), 10000):
items += rng.choice('ryb')
yield items
if len(items) == n:
items = rng.choice('ryb')

Your code makes use of repeated string concatencation. Each addition operation on a string takes O(n) time, because strings are immutable. This operation occurs O(n^2) times, so the runtime of your algorithm is O(n^3), where n is the length of the string.
One optimization is to use a list to store the letters, and then call ' '.join(), taking the runtime from O(n^3) to O(n^2):
def colour_trio(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
print(colour_trio("rybbry")) # Prints "b"

I think this is faster than the last solution I posted and faster than other solutions, I think classes make everything tidy looking, but you can re-work it to become a single function.
PS. Thank you for the feedback #KellyBundy! Now the algorithm is correct, it took even fewer chars!
New Solution
colors = ['r', 'b', 'y']
def split(x):
return [f'{Couple(i)}' for i in [f'{x[i]}{x[i+1]}' for i in range(len(x)-1)]]
class Couple:
def __init__(self, x) -> None:
self.a = x[0]
self.b = x[1]
def __repr__(self) -> str:
if self.a == self.b:
return self.a
return (set(colors).difference(set((self.a, self.b))).pop())
def ColoursTrio(x):
while len(x) > 1:
x = split(x)
return x[0]
print(ColoursTrio('bbr'))
or even shorter (and faster):
def ColoursTrio(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]
print(ColoursTrio('bbr'))
Old Solution
This is almost a bit convoluted but I think it's faster, I also think it's very similar to the one posted by #BrokenBenchmark (and I think his is better) so I'll add his example string too!
COLORS = ['r','b','y']
class ColorsCouple:
def __init__(self, x:str, y:str) -> None:
self.x = x
self.y = y
def mix(self):
if self.x == self.y:
return f'{self.x}'
if 'y' in (self.x, self.y):
return set(COLORS).difference((self.x, self.y)).pop()
return 'y'
class Colors:
def __init__(self, colors:str) -> None:
self.colors = self.get_couples([*colors])
def get_couples(self, element):
return [(element[i],element[i+1]) for i in range(len(element)-1)]
def get_color(self):
colors = [ColorsCouple(*couples).mix() if len(couples)==2 else couples[0] for couples in self.colors]
while len(colors)>1:
colors = self.get_couples(colors)
colors = [ColorsCouple(*couples).mix() if len(couples)==2 else couples[0] for couples in colors]
return colors[0]
def __repr__(self) -> str:
return f'{self.get_color()}'
print(Colors('bbr'))
print(Colors('rybbry'))

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()

Related

Number of times two charachters appear between two strings, only 1 function, and only parameters are the 2 strings

I want to count the number of times both a and b occur, in both strings together, using recursion in Python.
For example, if the input was ('aabb', 'bbba'), the output would be (3,5) because there are three a's and five b's total.
What I have tried:
def counting(string1, string2):
if not string1:
return 0
elif string1[0]=='a':
return 1+counting(string[1:],string2)
else:
return counting(string[1:],string2)
def counting(string1, string2):
def counting_recurse(string_total):
if not string_total:
return (0, 0)
elif string_total[0]=='a':
a_count, b_count = counting_recurse(string_total[1:])
return (1+a_count, b_count)
elif string_total[0]== 'b':
a_count, b_count = counting_recurse(string_total[1:])
return (a_count, b_count +1 )
else:
return counting_recurse(string_total[1:])
return counting_recurse(string1 + string2)
This works on your example. Does this solve your scenario?
You can recurse until both strings are empty.
def counting(string1, string2):
if string1 or string2:
a, b = counting(string1 and string1[1:], string2 and string2[1:])
count = lambda x: (string1 and string1[0] == x or 0) + (string2 and string2[0] == x or 0)
return (a + count('a'), b + count('b'))
return (0, 0)
print(counting('aabb', 'bbba'))
This seems like a simpler solution
Explanation
If you've reached end of string, then return (0,0) for a count and b count
Otherwise, you add 1 + a_count if first character is a otherwise you add 0 + a_count.
Similarly, you add 1 + b_count if first character is b otherwise you add 0 + b_count.
a_count is defined as the first value in tuple(), b_count is the second value()
def counting_recurse(combine_string):
if not combine_string:
return (0, 0)
return (int(combine_string[0]=='a') +counting_recurse(combine_string[1:])[0],
int(combine_string[0]=='b') +counting_recurse(combine_string[1:])[1])
string1 = 'aabb'
string2 = 'bbba'
counting_recurse(string1+string2)
Output
(3, 5)
def counting(stri, char):
if len(stri) == 0:
return 0
elif stri[0] == char:
return 1 + counting(stri[1:], char)
else:
return counting(stri[1:], char)
def count(str1, str2):
return (counting(str1,'a'),counting(str2,'b'))
print("aabb","bbba")
Looks like having two strings as arguments to the function is unnecessarily complicated, since you count across both strings anyway. So why not simply define the recursive function for a single string and call it on the concatenation of the two strings you're interested in, e.g.
def count_ab(s):
if not s:
return 0, 0
first_a = int(s[0] == 'a')
first_b = int(s[0] == 'b')
further_a, further_b = count_ab(s[1:])
return first_a + further_a, first_b + further_b
count_ab('aabb' + 'bbba')
(3, 5)
The following works:
def non_recursive_imp2(*args: str) -> tuple:
sargs = tuple(map(str, args))
a_count = 0
b_count = 0
for sarg in sargs:
for ch in sarg:
if ch == "a":
a_count += 1
elif ch == "b":
b_count += 1
return (a_count, b_count)
import operator
def recursive_count(inpuht:str, _:str):
inpuht += _
if len(inpuht) < 2:
if inpuht == "a":
return (1, 0)
elif inpuht == "b":
return (0, 1)
return (0, 0)
else:
left_str = inpuht[:len(inpuht)//2]
right_str = inpuht[len(inpuht)//2:]
left_count = recursive_count(left_str, "")
right_count = recursive_count(right_str, "")
return tuple(map(operator.add, left_count, right_count))
However, it is easier to write code for a non-recursive implementation:
NON-RECURSIVE IMPLEMENTATIONS:
def non_recursive_imp(x:str, y:str) -> tuple:
merged = x + y
a_count = 0
b_count = 0
for ch in merged:
if ch == "a":
a_count +=1
elif ch == "b":
b_count +=1
return (a_count, b_count)

Python — Iterating Through Two Lists Simultaneously

I'm trying to make a translator for a cipher I made up, and it needs to be able to check 2 lists side by side, but my program does not return any output.
def encrypt(message, key):
for i in range(len(message)):
ans = ""
char = message[i]
if(char == 1):
for j in range(len(key)):
if(key[j] == 1):
ans = 'a'
if(key[j] == 0):
ans = 'c'
if(char == 0):
for j in range(len(key)):
if(key[j] == 1):
ans = 'd'
if(key[j] == 0):
ans = 'b'
return ans
print(encrypt(str(1010), str(1100)))
I want this to output "adcb". I think there's a problem with where I put the return statement? I don't know.
It appears you want to loop over two strings of equal length, containing only the characters '0' and '1' and you want to return a string of equal length containing an 'a' if both characters at that position are '1' for the inputs, etc.
So, something like:
def encrypt(message, key):
code = {('1', '1'): 'a', ('1', '0'): 'c', ('0', '1'): 'd', ('0', '0'): 'b'}
return ''.join(
code[(m, k)] for m, k in zip(message, key)
)
print(encrypt('1010', '1100'))
Result:
adcb
And to provide a 'fixed' version of what you were trying with a loop and indexing (which isn't very pythonic):
def encrypt(message, key):
ans = ''
for i in range(len(message)):
m = message[i]
k = key[i]
if m == '1':
if k == '1':
ans += 'a'
if k == '0':
ans += 'c'
if m == '0':
if k == '1':
ans += 'd'
if k == '0':
ans += 'b'
return ans
Note that I've already removed the superfluous parentheses and the extra loops that give a wrong result.
You could further simplify:
def encrypt(message, key):
ans = ''
for i in range(len(message)):
if message[i] == '1':
if key[i] == '1':
ans += 'a'
else:
ans += 'c'
else:
if key[i] == '1':
ans += 'd'
else:
ans += 'b'
return ans
And from there:
def encrypt(message, key):
ans = ''
for m, k in zip(message, key):
if m == '1':
if k == '1':
ans += 'a'
else:
ans += 'c'
else:
if k == '1':
ans += 'd'
else:
ans += 'b'
return ans
As an added bonus, here's a gnarly one-liner that goes just a bit too far, beyond being pythonic:
def encrypt(message, key):
return ''.join('bdca'[int(m)*2 + int(k)] for m, k in zip(message, key))
The problem is at the conditional statements char == 1 and char == 0. Since a string and a integer are two different data types, the conditional statements evaluate to False and the inner for loops never run, so the function returns no output. To resolve this, change them to char == '1' and char == '0'. Same rule applies for key[j] == 1 and key[j] == 0, because key is passed into the function as a string.

My A-star implementation seems very slow, need advice and help on what I am doing wrong

My tests of my implementations of Dijkstra and A-Star have revealed that my A-star implementation is approximately 2 times SLOWER. Usually equivalent implementations of Dijkstra and A-star should see A-star beating out Dijkstra. But that isn't the case here and so it has led me to question my implementation of A-star. So I want someone to tell me what I am doing wrong in my implementation of A-star.
Here is my code:
from copy import deepcopy
from math import inf, sqrt
import maze_builderV2 as mb
if __name__ == '__main__':
order = 10
space = ['X']+['_' for x in range(order)]+['X']
maze = [deepcopy(space) for x in range(order)]
maze.append(['X' for x in range(order+2)])
maze.insert(0, ['X' for x in range(order+2)])
finalpos = (order, order)
pos = (1, 1)
maze[pos[0]][pos[1]] = 'S' # Initializing a start position
maze[finalpos[0]][finalpos[1]] = 'O' # Initializing a end position
mb.mazebuilder(maze=maze)
def spit():
for x in maze:
print(x)
spit()
print()
mazemap = {}
def scan(): # Converts raw map/maze into a suitable datastructure.
for x in range(1, order+1):
for y in range(1, order+1):
mazemap[(x, y)] = {}
t = [(x-1, y), (x+1, y), (x, y-1), (x, y+1)]
for z in t:
if maze[z[0]][z[1]] == 'X':
pass
else:
mazemap[(x, y)][z] = [sqrt((pos[0]-z[0])**2+(pos[1]-z[1])**2),
sqrt((finalpos[0]-z[0])**2+(finalpos[1]-z[1])**2)] # Euclidean distance to destination (Heuristic)
scan()
unvisited = deepcopy(mazemap)
distances = {}
paths = {}
# Initialization of distances:
for node in unvisited:
if node == pos:
distances[node] = [0, sqrt((finalpos[0]-node[0])**2+(finalpos[1]-node[1])**2)]
else:
distances[node] = [inf, inf]
while unvisited != {}:
curnode = None
for node in unvisited:
if curnode == None:
curnode = node
elif (distances[node][0]+distances[node][1]) < (distances[curnode][0]+distances[curnode][1]):
curnode = node
else:
pass
for childnode, lengths in mazemap[curnode].items():
# Length to nearby childnode - G length, Euclidean (Heuristic) length from curnode to finalpos - H length
# G length + H length < Euclidean length to reach that childnode directly + Euclidean length to finalpos from that childnode = Better path found, update known distance and paths
if lengths[0] + lengths[1] < distances[childnode][0] + distances[childnode][1]:
distances[childnode] = [lengths[0], lengths[1]]
paths[childnode] = curnode
unvisited.pop(curnode)
def shortestroute(paths, start, end):
shortestpath = []
try:
def rec(start, end):
if end == start:
shortestpath.append(end)
return shortestpath[::-1]
else:
shortestpath.append(end)
return rec(start, paths[end])
return rec(start, end)
except KeyError:
return False
finalpath = shortestroute(paths, pos, finalpos)
if finalpath:
for x in finalpath:
if x == pos or x == finalpos:
pass
else:
maze[x[0]][x[1]] = 'W'
else:
print("This maze not solvable, Blyat!")
print()
spit()
For those who find my code too messy and can't bother to read the comments I added to help with the reading... Here is a gist of my code:
Creates a mazemap (all the coordinates and its connected neighbors along with their euclidean distances from that neighboring point to the start position (G Cost) as well as to the final position (H Cost)... in a dictionary)
start position is selected as the current node. All distances to other nodes is initialised as infinity.
For every node we compare the total path cost i.e is the G cost + H cost. The one with least total cost is selected as then next current node. Each time we select new current node, we add that node to a dictionary that keeps track of through which node it was reached, so that it is easier to backtrack and find our path.
Process continues until current node is the final position.
If anyone can help me out on this, that would be great!
EDIT: On account of people asking for the maze building algorithm, here it is:
# Maze generator - v2: Generates mazes that look like city streets (more or less...)
from copy import deepcopy
from random import randint, choice
if __name__ == "__main__":
order = 10
space = ['X']+['_' for x in range(order)]+['X']
maze = [deepcopy(space) for x in range(order)]
maze.append(['X' for x in range(order+2)])
maze.insert(0, ['X' for x in range(order+2)])
pos = (1, 1)
finalpos = (order, order)
maze[pos[0]][pos[1]] = 'S' # Initializing a start position
maze[finalpos[1]][finalpos[1]] = 'O' # Initializing a end position
def spit():
for x in maze:
print(x)
blocks = []
freespaces = [(x, y) for x in range(1, order+1) for y in range(1, order+1)]
def blockbuilder(kind):
param1 = param2 = 0
double = randint(0, 1)
if kind == 0:
param2 = randint(3, 5)
if double:
param1 = 2
else:
param1 = 1
else:
param1 = randint(3, 5)
if double:
param2 = 2
else:
param2 = 1
for a in range(blockstarter[0], blockstarter[0]+param2):
for b in range(blockstarter[1], blockstarter[1]+param1):
if (a+1, b) in blocks or (a-1, b) in blocks or (a, b+1) in blocks or (a, b-1) in blocks or (a, b) in blocks or (a+1, b+1) in blocks or (a-1, b+1) in blocks or (a+1, b-1) in blocks or (a-1, b-1) in blocks:
pass
else:
if a > order+1 or b > order+1:
pass
else:
if maze[a][b] == 'X':
blocks.append((a, b))
else:
spaces = [(a+1, b), (a-1, b), (a, b+1), (a, b-1)]
for c in spaces:
if maze[c[0]][c[1]] == 'X':
break
else:
maze[a][b] = 'X'
blocks.append((a, b))
for x in range(1, order+1):
for y in range(1, order+1):
if (x, y) in freespaces:
t = [(x+1, y), (x-1, y), (x, y+1), (x, y-1)]
i = 0
while i < len(t):
if maze[t[i][0]][t[i][1]] == 'X' or (t[i][0], t[i][1]) == pos or (t[i][0], t[i][1]) == finalpos:
del t[i]
else:
i += 1
if len(t) > 2:
blockstarter = t[randint(0, len(t)-1)]
kind = randint(0, 1) # 0 - vertical, 1 - horizontal
blockbuilder(kind)
else:
pass
# rch = choice(['d', 'u', 'r', 'l'])
b = 0
while b < len(blocks):
block = blocks[b]
t = {'d': (block[0]+2, block[1]), 'u': (block[0]-2, block[1]),
'r': (block[0], block[1]+2), 'l': (block[0], block[1]-2)}
rch = choice(['d', 'u', 'r', 'l'])
z = t[rch]
# if z[0] > order+1 or z[1] > order+1 or z[0] < 1 or z[1] < 1:
# Decreased chance of having non solvable maze being generated...
if z[0] > order-2 or z[1] > order-2 or z[0] < 2+2 or z[1] < 2+2:
pass
else:
if maze[z[0]][z[1]] == 'X':
if randint(0, 1):
set = None
if rch == 'u':
set = (z[0]+1, z[1])
elif rch == 'd':
set = (z[0]-1, z[1])
elif rch == 'r':
set = (z[0], z[1]-1)
elif rch == 'l':
set = (z[0], z[1]+1)
else:
pass
if maze[set[0]][set[1]] == '_':
# Checks so that no walls that block the entire way are formed
# Makes sure maze is solvable
sets, count = [
(set[0]+1, set[1]), (set[0]-1, set[1]), (set[0], set[1]+1), (set[0], set[1]-1)], 0
for blyat in sets:
while blyat[0] != 0 and blyat[1] != 0 and blyat[0] != order+1 and blyat[1] != order+1:
ch = [(blyat[0]+1, blyat[1]), (blyat[0]-1, blyat[1]),
(blyat[0], blyat[1]+1), (blyat[0], blyat[1]-1)]
suka = []
for i in ch:
if ch not in suka:
if maze[i[0]][i[1]] == 'X':
blyat = i
break
else:
pass
suka.append(ch)
else:
pass
else:
blyat = None
if blyat == None:
break
else:
pass
else:
count += 1
if count < 1:
maze[set[0]][set[1]] = 'X'
blocks.append(set)
else:
pass
else:
pass
else:
pass
b += 1
mazebuilder(maze, order)
spit()
Sorry for leaving this out!
Just at a quick glance, it looks like you don't have a closed set at all?? Your unvisited structure appears to contain every node in the map. This algorithm is not A* at all.
Once you fix that, make sure to change unvisited from a list to a priority queue also.

How do i check, if all characters from a set of strings are unique per index elegantly?

I'm coding TicTacToe in Python3 and try to implement a elegant way to check, if the last move ends the game. The gamefield consists of a dictionary with the position described with 2 chars as key (l = left, m = mid, r = right, t = top, b = bottom) and the Player as value (' ' = None, 'X', 'O') So i tried something like this:
from itertools import combinations
class TicTacToe:
corners = ('tl','tr','bl','br')
edges = ('tm','ml','mr','bm')
def __init__(self):
self.board = {i + j:' 'for i in ['t','m','b'] for j in ['l','m','r']}
self.currentplayer = ''
self.lastmove = ''
def printBoard(self):
vals = list(self.board.values())
print('l m r ')
print(*vals[:3],sep='|', end=' top\n')
print('-+-+-')
print(*vals[3:6],sep='|', end=' mid\n')
print('-+-+-')
print(*vals[6:],sep='|', end=' bot\n')
def roundcount(self):
return len([i for i in self.board.values() if i != ' '])
def move(self,mve):
self.currentplayer = 'O' if self.currentplayer == 'X' else 'X'
if not self.board.get(mve):
return 'impossible move'
if self.board[mve] != ' ':
return 'already taken'
self.board[mve] = self.currentplayer
self.lastmove = mve
return f'Move done! Now its {self.currentplayer}\'s turn:'
############################
#Here is the important part#
def calcwin(self):
curmoves = [k for k,v in self.board.items() if v == self.currentplayer]
cornermvs = [m for m in curmoves if m in self.corners]
edgemvs = [m for m in curmoves if m in self.edges]
if self.lastmove in self.corners:
for i in combinations(cornermvs,2):
if i[0][0]==i[1][0] and i[0][0] + 'm' in edgemvs:
return self.currentplayer
elif i[0][1]==i[1][1] and 'm' + i[0][1] in edgemvs:
return self.currentplayer
elif 'mm' in curmoves:
return self.currentplayer
if self.lastmove in self.edges:
# I gave up on this point
pass
if self.lastmove == 'mm':
pass
But if i did it this way i thought i could already just check the whole gameboard, so i came up with a much smaller approach:
def calcwin(self):
curmoves = [k for k,v in self.board.items() if v == self.currentplayer]
possiblecomb = [c for c in combinations(curmoves,3) if c[0][0] == c[1][0] == c[2][0] or c[0][1]==c[1][1]==c[2][1]]
if len(possiblecomb)>0:
return self.currentplayer
which apparently only checks for non diagonal wins:
Field:
l m r
X|X|X top
-+-+-
O|X|O mid
-+-+-
O|O|X bot
possiblecomb:
[('tl', 'tm', 'tr')]
Is it possible to include the diagonal checking as elegant? To make this work, i'd need to check if all characters per index of all 3 strings in the combination are different. Like:
Field
l m r
X|X|X top
-+-+-
O|X|O mid
-+-+-
O|O|X bot
possiblecomb:
[('tl', 'tm', 'tr'), ('tl', 'mm', 'br')]

Simplifying Python If Statements

I am a beginner with python, and I am creating a two player tic tac toe game in terminal. In total, this code takes up 139 lines, (this below being the relevant part of code I am having trouble with), however, this CheckWin function takes up around 40 lines of code, which I think is quite a lot compared to the amount of lines in this code, and considering that it performs a somewhat basic function. Basically, in the game, this function checks whether a row, column, or diagonal, has three X's or three O's, and if it does, it assigns X to the winner and O to the winner. Anyway, here is the code.
X = "X"
O = "O"
empty = " "
S = [" ", " ", " ", " ", " ", " ", " ", " ", " "]
def CheckWin(S):
global winner
winner = ""
if S[0] == S[1] == S[2] != empty:
if S[0] == X:
winner = X
if S[0] == O:
winner = O
if S[3] == S[4] == S[5] != empty:
if S[3] == X:
winner = X
if S[3] == O:
winner = O
if S[6] == S[7] == S[8] != empty:
if S[6] == X:
winner = X
if S[6] == O:
winner = O
if S[0] == S[3] == S[6] != empty:
if S[0] == X:
winner = X
if S[0] == O:
winner = O
if S[1] == S[4] == S[7] != empty:
if S[1] == X:
winner = X
if S[1] == O:
winner = O
if S[2] == S[5] == S[8] != empty:
if S[2] == X:
winner = X
if S[2] == O:
winner = O
if S[0] == S[4] == S[8] != empty:
if S[0] == X:
winner = X
if S[0] == O:
winner = O
if S[2] == S[4] == S[6] != empty:
if S[2] == X:
winner = X
if S[2] == O:
winner = O
Basically, I need help making the function much much simpler. However, I do not want to eliminate the X, O, and winner variables, nor do I want to eliminate the list index method with the list S. Even though, is there a way to simplify all these If statements, keeping these things? If so, how?
Your code looks for "trios" of positions; you might as well have an object that holds this info:
trios = ((0,1,2), (3,4,5), (6,7,8), (0,3,6), (1,4,7), (2,5,8), (0,4,8), (2,4,6))
Then CheckWin would just loop through every trio, do that check you're doing, and return a winner if the trio matches. This way, CheckWin would be less than 10 lines of code.
I don't want to give it all away because I'm sure you can do it :)
Also, you don't need a global variable called "winner" inside CheckWin; just have CheckWin return the winner (or ""), and store the result in a variable outside the function itself.
I.e.
winner = CheckWin(S)
Have you tried using a loop instead?
X, O = 'X', 'O'
S = [X,O,X,O,X,O,O,X,O] # Test values
def CheckWin(S):
index = 0
has_winner = False
while index < len(S):
print index
if index <= 6: # after this point you won't find a winner (or you run this after every turn?)
if (index % 3 == 0 and S[index] == S[index + 1] and S[index] == S[index + 2]): # horizontals
has_winner = True
elif index < 3: # verticals and diagonals (you only need the first row to know this)
if (S[index] == S[(index + 3)] and S[index] == S[index + 6]) or \
(index == 0 and S[index] == S[4] and S[index] == S[8]) or \
(index == 2 and S[index] == S[4] and S[index] == S[6]):
has_winner = True
if has_winner: # I'm using this to prevent duplicating code above (one if less)
if S[index] in [X,O]:
return S[index]
index += 1
return has_winner # If we have a winner but the X or O criterion isn't met, it returns False
winner = CheckWin(S)

Categories

Resources