There is a robot at the top-left corner of an N*M grid. The robot can move up, down, left and right, but cannot visit the same cell more than once in each traversal. How do I find the total number of ways the robot can reach the bottom-right corner?
(the robot does not need to visit every cell for a path to be valid)
I think there is a recursive solution to this but I can't get it somehow.
Here's what I've got so far:
def initialize(row, cols):
grid = [ [ 0 for c in range(cols) ] for r in range(rows) ]
pos_r, pos_c = 0, 0
grid[pos_r][pos_c] = 1 # set start cell as visited
dst_x, dst_y = len(grid)-1, len(grid[0])-1 # coords of bottom-right corner
print move_robot(grid, dst_x, dst_y, pos_r, pos_c)
def move_robot(grid, dst_x, dst_y, pos_r, pos_c, prev_r=None, prev_c=None):
num_ways = 0
if reached_dst(dst_x, dst_y, pos_r, pos_c):
undo_move(grid, pos_r, pos_c)
undo_move(grid, prev_r, prev_c)
return 1
else:
moves = get_moves(grid, pos_r, pos_c)
if len(moves) == 0:
undo_move(grid, prev_r, prev_c)
return 0
for move in moves:
prev_r = pos_r
prev_c = pos_c
pos_r = move[0]
pos_c = move[1]
update_grid(grid, pos_r, pos_c)
num_ways += move_robot(grid, dst_x, dst_y, pos_r, pos_c, prev_r, prev_c)
return num_ways
if __name__ == '__main__':
initialize(4, 4)
I left out some function definitions for brevity. Get_moves retrieves the all legal moves, checking whether each move would still be on the board and whether the cell has already been visited. Update_grid sets the specified cell to '1', which means visited. Undo_move does the opposite, setting the specified cell to '0'.
I get the right answer for the simplest possible case (2*2 grid), but for larger grids the output is always too low. What's wrong with my code, and is there a simpler way of doing this?
The recursion is pretty straight forward but one should be careful to create copies of the matrix while recursing in order to receive good results:
from copy import copy, deepcopy
def calc(i, j, mat):
if i < 0 or j < 0 or i >= len(mat) or j >= len(mat[0]):
return 0 # out of borders
elif mat[i][j] == 1:
return 0 # this cell has already been visited
elif i == len(mat)-1 and j == len(mat[0])-1:
return 1 # reached destination (last cell)
else:
mat[i][j] = 1 # mark as visited
# create copies of the matrix for the recursion calls
m1 = deepcopy(mat)
m2 = deepcopy(mat)
m3 = deepcopy(mat)
m4 = deepcopy(mat)
# return the sum of results of the calls to the cells:
# down + up + right + left
return calc(i+1, j, m1) + calc(i-1, j, m2) + calc(i, j+1, m3) + calc(i, j-1, m4)
def do_the_robot_thing(m, n):
# an un-visited cell will be marked with "0"
mat = [[0]*n for x in xrange(m)]
return calc(0, 0, mat)
print(do_the_robot_thing(3, 3))
OUTPUT:
12
Related
I am trying to solve this question from the USACO website. Problem Link: http://www.usaco.org/index.php?page=viewproblem2&cpid=1061
Farmer John has recently expanded the size of his farm, so from the perspective of his cows it is effectively now infinite in size! The cows think of the grazing area of the farm as an infinite 2D grid of square "cells", each filled with delicious grass (think of each cell as a square in an infinite chessboard). Each of Farmer John's N cows (1≤N≤50) starts out in a different cell; some start facing north, and some start facing east.
Every hour, every cow either
Stops if the grass in her current cell was already eaten by another
cow.
Eats all the grass in her current cell and moves one cell forward
according to the direction she faces.
Over time, each cow therefore leaves a barren "rut" of empty cells behind her.
If two cows move onto the same grassy cell in the same move, they share the cell and continue moving in their respective directions in the next hour.
Please determine the amount of grass eaten by each cow. Some cows never stop, and therefore eat an infinite amount of grass.
INPUT FORMAT (input arrives from the terminal / stdin):
The first line of input contains N. Each of the next N lines describes the starting location of a cow, in terms of a character that is either N (for north-facing) or E (for east-facing) and two nonnegative integers x and y (0≤x≤1000000000, 0≤y≤1000000000) giving the coordinates of a cell. All x-coordinates are distinct from each-other, and similarly for the y-coordinates.
To be as clear as possible regarding directions and coordinates, if a cow is in cell (x,y) and moves north, she ends up in cell (x,y+1). If she instead had moved east, she would end up in cell (x+1,y).
OUTPUT FORMAT (print output to the terminal / stdout):
Print N lines of output. Line i in the output should describe the number of cells worth of grass that the ith cow in the input eats. If a cow eats an infinite amount of grass, output "Infinity" for that cow.
SAMPLE INPUT:
6
E 3 5
N 5 3
E 4 6
E 10 4
N 11 2
N 8 1
SAMPLE OUTPUT:
5
3
Infinity
Infinity
2
5
SCORING:
In test cases 2-5, all coordinates are at most 100.
In test cases 6-10, there are no additional constraints.
My logic is that since simulating the collisions would be too slow because the field is huge, we can sort the cows by their x values, iterate over all the collisions/intersections of cows and stop the ones that should be stopped, and after iterating, print out the distances of the stopped cows. And if a cow hasn't stopped, print "Infinity".
My code:
# Defining the cow class with the original order position, x, y, distance,
# and whether or not it stopped.
class Cow:
def __init__(self, i, x, y):
self.i = i
self.x = x
self.y = y
self.dist = 0
self.stopped = False
# Get input from console and split cows into east facing and north facing cows.
n = int(input().strip())
hor = []
ver = []
ans = [0] * n
for i in range(n):
line = input().strip().split()
if line[0] == 'E':
hor.append(Cow(i, int(line[1]), int(line[2])))
else:
ver.append(Cow(i, int(line[1]), int(line[2])))
hor.sort(key = lambda c: c.x)
ver.sort(key = lambda c: c.x)
# Iterate over every possible collision. Logic problem here:
for h in hor:
for v in ver:
vdist = abs(h.y - v.y)
hdist = abs(h.x - v.x)
if h.stopped and v.stopped:
continue
elif h.stopped:
if v.x >= h.x and v.x <= h.x + h.dist and v.y <= h.y:
if vdist > hdist:
v.dist = vdist
v.stopped = True
elif v.stopped:
if v.x >= h.x and h.y <= v.y + v.dist and v.y <= h.y:
if hdist > vdist:
h.dist = hdist
h.stopped = True
else:
if v.x >= h.x and v.y <= h.y:
if vdist > hdist:
v.dist = vdist
v.stopped = True
if hdist > vdist:
h.dist = hdist
h.stopped = True
# Combine the lists and put them back into the original order.
cows = hor + ver
cows.sort(key = lambda c: c.i)
# Print out all the cows' distances, and it a cow hasn't stopped, replace distance with Infinity.
for i in cows:
if not i.stopped:
i.dist = "Infinity"
print(i.dist)
I'm not sure if it's just my code that isn't correct, or if it's my basic logic. If anyone can provide a fix, it would be appreciated.
Try this revised approach, using set to add the movements and check intersection.
from collections import deque
import sys
class Cow:
def __init__(self, d, x, y, amt):
self.d = d
self.x = x
self.y = y
self.amt = amt
lines = sys.stdin.read().strip().split('\n')
n = int(lines[0])
EMPTY = set()
COW = []
for line in lines[1:]:
d, x, y = line.split()
x, y = int(x), int(y)
COW.append(Cow(d, x, y, 0))
S = set()
for i in range(n):
for j in range(n):
S.add(abs(COW[i].x - COW[j].x))
S.add(abs(COW[i].y - COW[j].y))
S2 = set()
for k in S:
S2.add(k -1)
S2.add(k)
S2.add(k + 1)
S2.add(max(S) + 1)
dq = deque(sorted(S2)) #
SCORE = [None for _ in range(n)]
t = 0
while dq:
#nt += 1
dt = dq.popleft() - t
dt = max(dt, 1)
t += dt
VOID = []
for i in range(n):
if SCORE[i] is None:
if (COW[i].x, COW[i].y) in EMPTY:
SCORE[i] = COW[i].amt
continue
VOID.append((COW[i].x, COW[i].y))
if COW[i].d == 'N': COW[i].y += dt
elif COW[i].d == 'E': COW[i].x += dt
COW[i].amt += dt
for spot in VOID: EMPTY.add(spot)
for i in range(n):
print(SCORE[i] if SCORE[i] else 'Infinity')
To keep track of your algorithm you could split 'intersection-finding' and 'cow-stopping' into separate parts.
import sys
from collections import namedtuple
Cow = namedtuple('Cow', ['distance','facing','x','y','number'])
lines = sys.stdin.read().strip().split('\n')
cows = [Cow(0,*[int(x) if x.isnumeric() else x for x in i.split()], e)
for e,i in enumerate(lines[1:])]
# finding intersections
# save if distances differ, sorted descending by distance
intersections = []
for cowA, cowB in [(cowA, cowB)
for cowB in cows if cowB.facing == 'N'
for cowA in cows if cowA.facing == 'E'
]:
if cowA.x < cowB.x and cowA.y > cowB.y:
d1, d2 = cowB.x - cowA.x, cowA.y - cowB.y
if d1 != d2:
intersections.append(
sorted([Cow(d1, *cowA[1:]),Cow(d2, *cowB[1:])], reverse=True))
# sorting intersections by larger distance
# checking if a cow reached the intersection or stopped earlier
distances = [int(10E9)] * len(cows)
for i in sorted(intersections):
if i[1].distance < distances[i[1].number] and i[0].distance < distances[i[0].number]:
distances[i[0].number] = i[0].distance
for i in distances:
print('Infinity' if i==int(10E9) else i)
Output
5
3
Infinity
Infinity
2
5
My mistake was hor.sort(key = lambda c: c.x) where I sorted the list by the 1st element instead of the 2nd.
It should be hor.sort(key = lambda c: c.y) since that's what matters at the intersections.
Write a function traverse() that takes in a list tb of n strings each containing n lower case characters
(a-z).
tb represents a square table with n rows and n columns. The function returns a string st generated by the procedure below that traverses the grid starting from the top left cell and ending at the bottom right cell.
At every step, the procedure moves either horizontally to the right or vertically down, depending on which of the two cells has a \smaller" letter (i.e., a letter that appears earlier in the alphabetical order).
The letter in the visited cell is then added to st. In case of ties, either direction can be taken.
When the right or bottom edges of the table are reached, there is obviously only a unique next cell to move to. As an example, traverse(["veus", "oxde", "oxlx", "hwuj"]) returns "veudexj"
so the table would look like this:
v o o h
e x x w
u d l u
s e x j
I am new in python and I wrote this code ... but it only prints "veuexj" I would say the problem is in this line if new_list[a - 1][b - 1] == new_list[a - 1][-2]: which force the parameter to skip the 'd' character. #And I don't know how to solve it.
def traverse(tb_list):
new_list = tb_list.copy()
st = new_list[0][0]
parameter = ''
handler = 1
for a in range(1, len(new_list)):
for b in range(handler, len(new_list[a])):
if new_list[a - 1][b - 1] == new_list[a - 1][-2]:
parameter = new_list[a][b]
elif new_list[a - 1][b - 1] > min(new_list[a - 1][b], new_list[a][b - 1]):
parameter = min(new_list[a - 1][b], new_list[a][b - 1])
elif new_list[a - 1][b - 1] < min(new_list[a - 1][b], new_list[a][b - 1]):
parameter = min(new_list[a - 1][b], new_list[a][b - 1])
st += parameter
handler = b
return st
print(traverse(["veus", "oxde", "oxlx", "hwuj"]))
You can try something like this (explanation added as comments):
def traverse(tb_list):
lst = tb_list.copy() #Takes a copy of tb_list
lst = list(zip(*[list(elem) for elem in lst])) #Transposes the list
final = lst[0][0] #Sets final as the first element of the list
index = [0,0] #Sets index to 0,0
while True:
x = index[0] #The x coordinate is the first element of the list
y = index[1] #The y coordinate is the second element of the list
if x == len(lst) - 1: #Checks if the program has reached the right most point of the table
if y == len(list(zip(*lst))) - 1: #Checks if program has reached the bottommost point of the table
return final #If both the conditions are True, it returns final (the final string)
else:
final += lst[x][y+1] #If the program has reached the right most corner, but not the bottommost, then the program moves one step down
index = [x, y+1] #Sets the index to the new coordinates
elif y == len(list(zip(*lst))) - 1: #Does the same thing in the previous if condition button in an opposite way (checks if program has reached bottommost corner first, rightmost corner next)
if x == len(lst) - 1:
return final
else:
final += lst[x + 1][y] #If the program has reached the bottommost corner, but not the rightmost, then the program moves one step right
index = [x + 1, y]
else: #If both conditions are false (rightmost and bottommost)
if lst[x+1][y] < lst[x][y+1]: #Checks if right value is lesser than the bottom value
final += lst[x+1][y] #If True, then it moves one step right and adds the alphabet at that index to final
index = [x+1,y] #Sets the index to the new coords
else: #If the previous if condition is False (bottom val > right val)
final += lst[x][y+1] #Moves one step down and adds the alphabet at that index to final
index = [x,y+1] #Sets the index to the new coords
lst = ["veus", "oxde", "oxlx", "hwuj"]
print(traverse(lst))
Output:
veudexj
I have added the explanation as comments, so take your time to go through it. If you are still not clear with any part of the code, feel free to ask me. Any suggestions to optimize/shorten my code are most welcome.
I am currently doing a python exercise which, when given a chess board of size n, is to return a solution with the maximum number of queens so that no two queens attack each other.
Thus, a solution requires that no two queens share the same row, column, or diagonal.
I am currently able to generate the board, but the problem comes in line 20 of my code, where if bd not in soln. For some reason that I am unable to identify, that line of code does not execute properly and correctly append the correct board to my solution set.
If anyone could help to identify the problem I would be very grateful.
import random
def queensol(n):
"""Find the number of solutions to placing Queens on a chessboard of size n"""
size = n
rng = random.Random()
tries = 0
bd = list(range(size))
soln = []
while True:
tries += 1
rng.shuffle(bd)
correct = 0
for queen_index in range(size):
if queen_noclash(bd, queen_index):
correct += 1
board_valid = correct == size
if board_valid:
if bd not in soln:
soln.append(bd)
print(soln)
def no_diagonal(x1, y1, x2, y2):
dx = abs(x1-x2)
dy = abs(y1-y2)
# print('dx',dx)
# print('dy',dy)
if dx == dy:
return False
else:
return True
def queen_noclash(bd, queen):
correct = 0
for left_queen in range(queen):
left_queen_x = left_queen
left_queen_y = bd[left_queen]
queen_x = queen
queen_y = bd[queen]
if no_diagonal(left_queen_x, left_queen_y, queen_x, queen_y):
correct += 1
if correct == queen:
return True
else:
return False
queensol(4)
The problem is that the bd is an object. You are appending the same object to the board. So, even though bd changes bd not in soln will always give False since you are comparing the same object. You should use list(bd) to create a new object every time you are appending to soln array. I have fixed this in the below code.
import random
def queensol(n):
"""Find the number of solutions to placing Queens on a chessboard of size n"""
size = n
rng = random.Random()
tries = 0
bd = list(range(size))
soln = []
while True:
tries += 1
rng.shuffle(bd)
correct = 0
for queen_index in range(size):
if queen_noclash(bd, queen_index):
correct += 1
board_valid = correct == size
if board_valid:
if bd not in soln:
soln.append(list(bd))
print(soln)
def no_diagonal(x1, y1, x2, y2):
dx = abs(x1-x2)
dy = abs(y1-y2)
# print('dx',dx)
# print('dy',dy)
if dx == dy:
return False
else:
return True
def queen_noclash(bd, queen):
correct = 0
for left_queen in range(queen):
left_queen_x = left_queen
left_queen_y = bd[left_queen]
queen_x = queen
queen_y = bd[queen]
if no_diagonal(left_queen_x, left_queen_y, queen_x, queen_y):
correct += 1
if correct == queen:
return True
else:
return False
queensol(4)
I wasn't quite sure how to word the title, sorry if it doesn't make sense/is misleading.
Note - a "boat" is 3 O's next to each other in an array, so
_|O|_ _|_|_
_|O|_ O|O|O
|O| | |
are boats.
So I have an a list of lists (n x n) (working with lists) in which I generate n boats at random spaces. I don't want the boats to be next to each other, touch by corners, or be on top of each other.
A tried checking if a boat would end up on top of another boat in the vein of this:
if board[y - 2][x] == 'O' or board[y + 2][x] == 'O' ...
and so on, which ended up being unsurprisingly long.
I also got index out of range errors, since I was sometimes checking for coordinates not in the field.
So, is there a way I can check for boats in every direction without going out of index range?
Better yet, any ideas on how to make the boats not generate next to each other?
The code for boat generation is here:
from random import *
side = int(input())
game_state = []
def generate_initial_state():
for i in range(side):
game_state.append([])
for j in range(side):
game_state[i].append('.')
for i in range(side):
# Generate boat origin on random coordinates within the game board,
# if there's a boat already, generate new ones
y_cor = randint(0, side-1)
x_cor = randint(0, side-1)
while game_state[y_cor][x_cor] == 'O':
y_cor = randint(0, side - 1)
x_cor = randint(0, side - 1)
# Direct chooses if the boat will be generated up, down, or sideways
direct = randint(1, 4)
cycle = 0
while cycle < 3:
# Generates a boat going from origin in one direction,
# if the boat would end outside the board, chooses a different direction
if direct == 1:
if y_cor + 2 >= side:
direct = randint(1, 4)
else:
game_state[y_cor + cycle][x_cor] = 'O'
cycle += 1
elif direct == 2:
if x_cor + 2 >= side:
direct = randint(1, 4)
else:
game_state[y_cor][x_cor + cycle] = 'O'
cycle += 1
elif direct == 3:
if y_cor - 2 < 0:
direct = randint(1, 4)
else:
game_state[y_cor - cycle][x_cor] = 'O'
cycle += 1
elif direct == 4:
if x_cor - 2 < 0:
direct = randint(1, 4)
else:
game_state[y_cor][x_cor - cycle] = 'O'
cycle += 1
for i in range(side):
print(*game_state[i])
First I would only use two directions (horizontal and vertical), which shouldn't change the probabilities (with your model, a boat can be generated in two ways).
This allows index overflow to only occur by exceeding the allowable indices, which provokes an IndexError that can be intercepted (using a negative index doesn't and that could mess up your generator).
Secondly, using a flag could help you do the trick.
I added a few other modifications :
EDIT : I just realized that my code was rejecting perfectly valid boats if they were on the border, so here is a version that doesn't.
Update : some explanations
We use a boolean flag boat_built to track the suitability of a randomly chosen position of the boat: once all tests are made, this variable decides if the choice was suitable (True) or if obstructions were encountered while tests were carried out (False).
Using
boat_built &= (game_state[x_cor + k*dx][y_cor + k*dy] != "0")
we update the flag for every test : if boat_built was False before the test, it will remain False regardless of the test's result (False & a = False): this is intended, because it means that an obstruction has already been encountered and the boat is invalid.
On the other hand, if boat_built was True before the test, it will contain the result of the test afterwards (True & a = a): this is also intended, since failing the new test means we now have found an obstruction.
Note that all 15 tests are carried out for every boat, even if an obstruction is encountered early on.
from random import *
side = int(input())
game_state = [['.' for i in range(side)] for j in range(side)]
l_dir = [(1, 0), (0, 1)]
def generate_initial_state():
for i in range(side):
boat_built = False
while not boat_built:
boat_built = True
y_cor = randrange(side)
x_cor = randrange(side)
dx, dy = l_dir[randrange(2)]
try:
# check that the three required cells are empty
for k in range(3):
boat_built &= (game_state[x_cor + k*dx][y_cor + k*dy] != "0")
except IndexError:
# if any is out of range, choice is invalid
boat_built = False
for k in range(5):
for l in [-1, 1]:
try:
# check if neighbours on the long sides are empty
boat_built &= (game_state[x_cor + (k-1)*dx + l*dy][y_cor + l*dx + (k-1)*dy] != "0")
except IndexError:
# if we're out of range, no obstruction
pass
for k in [-1, 3]:
try:
# check if neighbours on the short sides are empty
boat_built &= (game_state[x_cor + k*dx][y_cor + k*dy] != "0")
except IndexError:
# again, if we're out of range, no obstruction
pass
# if we reach this point, a valid position has been found
for k in range(3):
game_state[x_cor + k*dx][y_cor + k*dy] = "0"
generate_initial_state()
for i in range(side):
print(*game_state[i])
Your code has, as you complain, too much tedious repeated logic to inspect a point's neighbors. You could turn four tests into one with something like this:
offset = [
(0, -1),
(-1, 0), (1, 0),
(0, 1),
]
for xoff, yoff in offset:
if game_state[x + xoff * cycle][y + yoff * cycle] == 'O':
report_collision(x, y)
Additionally, you could mark the grid with both 'O' for "boat" and 'o' for "bordering a boat", to simplify detecting adjacent boats.
You can try the following class and see if it solves your problem:
#! /usr/bin/env python3
import collections
import enum
import random
def main():
board = Board(10, 10)
print(board)
board.place_boats([2, 3, 3, 4, 5])
print('\n' + '=' * 21 + '\n')
print(board)
Point = collections.namedtuple('Point', 'x, y')
# noinspection PyArgumentList
Orientation = enum.Enum('Orientation', 'HORIZONTAL, VERTICAL')
class Board:
def __init__(self, width, height):
self.__width = width
self.__height = height
self.__matrix = [[False] * height for _ in range(width)]
self.__available = {Point(x, y)
for x in range(width)
for y in range(height)}
def __str__(self):
width = self.__width * 2 + 1
height = self.__height * 2 + 1
grid = [[' '] * width for _ in range(height)]
for yo, xo, character in (0, 1, '|'), (1, 0, '-'), (1, 1, '+'):
for y in range(yo, height, 2):
for x in range(xo, width, 2):
grid[y][x] = character
for x, column in enumerate(self.__matrix):
for y, cell in enumerate(column):
if cell:
grid[y << 1][x << 1] = '#'
return '\n'.join(''.join(row) for row in grid)
# noinspection PyAssignmentToLoopOrWithParameter
def place_boats(self, sizes, patience=10):
matrix_backup = [column.copy() for column in self.__matrix]
available_backup = self.__available.copy()
for _ in range(patience):
# try to place all the boats
for size in sizes:
for _ in range(patience):
# try to place boat of current size
point = random.choice(tuple(self.__available))
method = random.choice(tuple(Orientation))
try:
# try to place a boat; does not mangle the matrix
self.make_boat(point, size, method)
except RuntimeError:
pass
else:
# break out of inner patience loop; go to next size
break # on success
else:
# break to outer patience loop; start from beginning
self.__matrix = [column.copy() for column in matrix_backup]
self.__available = available_backup.copy()
break # on failure
else:
# break out of outer patience loop; all sizes were placed
break # on success
else:
raise RuntimeError('could not place the requested boats')
def make_boat(self, point, size, method):
backup = [column.copy() for column in self.__matrix]
unusable = set()
for offset in range(size):
if method is Orientation.HORIZONTAL:
block = self.mark_cell(point, x_offset=offset)
elif method is Orientation.VERTICAL:
block = self.mark_cell(point, y_offset=offset)
else:
raise ValueError('method was not understood')
if block:
unusable.update(block)
else:
self.__matrix = backup
raise RuntimeError('cannot place boat')
self.__available -= unusable
def mark_cell(self, point, *, x_offset=0, y_offset=0):
target = Point(point.x + x_offset, point.y + y_offset)
if target in self.__available and \
0 <= target.x < self.__width and \
0 <= target.y < self.__height:
self.__matrix[target.x][target.y] = True
return {Point(target.x + xo, target.y + yo)
for xo in range(-1, 2)
for yo in range(-1, 2)}
if __name__ == '__main__':
main()
I have a problem where I have to find the largest square in an n * n grid.
e.g.
. . . . .
. # . # .
. # . . .
. # . # .
. # . . .
where the biggest square would be 3 by 3 in the bottom corner.
I am supposed to return the most steps someone could take before turning right so that they can repeat this infinitely without hitting a wall "#" or going outside the n * n square which is why the output is one less that the width/length of the square.
My code loops through the grid left to right, top to bottom looking for vertices that face down and to the right. Once it finds one it then looks for the biggest possible vertex facing up and to the right and when it finds that checks all four sides to see whether or not they are made up or .. This code works in under 1 second for me on anything around n = 100, however I need it to run at 1 second for n = 500. Any tips on how I can speed this up?
import sys
input = sys.stdin.readline
n = int(input())
maze = [list(input()) for _ in range(n)]
squares = 0
for r in range(n - 1):
for c in range(n - 1):
if maze[r][c] == '.' and maze[r][c + 1] == '.' and maze[r + 1] [c] == '.':
sides = []
for i in range(min(n - r - 1, n - c - 1), -1, -1):
if maze[r + i][c + i] == '.' and maze[r + i][c + i - 1] == '.' and maze[r + i - 1][c + i] == '.':
sides = i
if maze[r][c : c + sides] == ['.'] * sides and maze[r + sides][c : c + sides] == ['.'] * sides:
a = True
for j in range(sides):
if maze[r + j][c] != '.' or maze[r + j][c + sides] != '.':
a = False
if a and sides > squares:
squares = sides
break
if squares == n - 1:
break
print(squares)
I can think of a O(n^3) algorithm as follows:
Precompute 4 arrays: top[][], bottom[][], left[][], right[][], each stores the maximum length of a direction that you can go from (i,j)
For each (i,j) , use it as a square's bottom left corner, for each its diagonal points (i-1, j+1), (i-2, j+2)...etc., test if those points can be used as the square's top right corner. Store the maximum square side in the process
For step 1, all 4 arrays can be precomputed in O(n^2)
For step 2, as we loop through all (i,j), and for each (i,j) we have to see at most all diagonal points which is at most n of them, total we get O(n^3)
The test in step 2 can be done in O(1) using the 4 precomputed arrays, simply check if the 4 corners of the "possible squares" can be joined by checking the corresponding directions (top, bottom, left, right)
Of course, there are many minor things which can be done to speed up, for example:
In step 2, for each (i,j), only check for diagonal points which is in the range [current_maximum_diagonal_found ... max(right[i][j], top[i][j])]
Update current_maximum_diagonal_found along the whole algorithm, so that we hope for some (i,j), we do not need to check whole n diagonal points.
But strictly speaking, it is still O(n^3), but as far as I know it should be able to run in 1 second for n~500
that's an interesting problem. I tried out some things and ended up with this implementation which is O(n^3). I commented the code so that you can follow the idea hopefully. There's still room for speed improvements, but this version already does the job (e.g. with maze size 500x500):
Finished after 0.708 seconds.
Result: 112581 squares found, maximum square (x=13, y=270, size=18).
This is the source code (Python 3):
import random
import pprint
import time
# small sample maze
maze = ['.....',
'...#.',
'.#...',
'.#.#.',
'.#...']
# convert to boolean maze
maze_bin = [[True if cell == '.' else False for cell in line] for line in maze]
# uncomment to generate a random maze
# maze_size = 500
# threshold = 0.2
# maze_bin = [[1 if random.random() >= threshold else 0 for _ in range(maze_size)] for _ in range(maze_size)]
# take start time
t1 = time.time()
# rotate the maze (first column becomes first row, first row becomes first column)
maze_bin_rot = [[maze_bin[i][j] for i in range(len(maze_bin))] for j in range(len(maze_bin[0]))]
# horizontal_lengths is a two-dimensional list that contains the number of possible steps to the right for every cell.
horizontal_lengths = []
for line in maze_bin:
num = 0
line_lengths = []
for i in reversed(line):
line_lengths.append(i*num)
num = i * (num + i)
horizontal_lengths.append(tuple(reversed(line_lengths)))
# vertical_lengths is a two-dimensional list that contains the number of possible steps to the bottom for every cell.
vertical_lengths_rot = []
for line in maze_bin_rot:
num = 0
line_lengths = []
for i in reversed(line):
line_lengths.append(i*num)
num = i * (num + i)
vertical_lengths_rot.append(tuple(reversed(line_lengths)))
# do the rotation again to be back in normal coordinates
vertical_lengths = [[vertical_lengths_rot[i][j] for i in range(len(vertical_lengths_rot))] for j in range(len(vertical_lengths_rot[0]))]
# calculate the maximum size of a square that has it's upper left corner at (x, y).
# this is the minimum of the possible steps to the right and to the bottom.
max_possible_square = []
for y in range(len(maze_bin)):
line = []
for x in range(len(maze_bin[0])):
line.append(min(horizontal_lengths[y][x], vertical_lengths[y][x]))
max_possible_square.append(line)
# search for squares
results = []
max_size_square = (-1, -1, -1)
for y in range(len(max_possible_square)):
for x in range(len(max_possible_square[0])):
# start with maximum possible size and decrease size until a square is found.
for size in reversed(range(1, max_possible_square[y][x]+1)):
# look at the upper right (x+size,y) and bottom left corner (x,y+size).
# if it's possible to make at least size steps to the right from the bottom left corner
# and at least size steps to the bottom from the upper right corner, this is a valid square.
if horizontal_lengths[y+size][x] >= size and vertical_lengths[y][x+size] >= size:
results.append((x, y, size+1))
if size+1 > max_size_square[2]:
max_size_square = (x, y, size+1)
# break after the the largest square with upper left corner (x,y) has been found.
break
t2 = time.time()
# comment this print section if you use larger grids
print('Maze:')
pprint.pprint(maze_bin)
print('\n')
print('Horizontal possible steps:')
pprint.pprint(horizontal_lengths)
print('\n')
print('Vertical possible steps:')
pprint.pprint(vertical_lengths)
print('\n')
print('Maximum possible size of square:')
pprint.pprint(max_possible_square)
print('\n')
print('Results:')
for square in results:
print('Square: x={}, y={}, size={}'.format(*square))
print('\n')
# final results
print('Finished after {:.3f} seconds.'.format(t2-t1))
print('Result: {} squares found, maximum square (x={}, y={}, size={}).'.format(len(results), *max_size_square))
I hope this is what you were looking for. If you have any questions, just leave a comment below ;)
If we do not want to enumerate all results, one optimization that may be worth considering is the following. It's based on the strategy - "do not proceed further with this cell if it can not lead to the optimal solution"
for y in range(possible_y_value):
for x in range(possible_x_value):
# We are ready to process cell identified by (x,y).
# Check if max_possible_square_length at this cell is greater than size of best_result seen so far. If so, proceed further, otherwise skip this cell
if max_possible_square[y][x]+1 > best_result.size:
# proceed further with the inner most for loop
....
Even from within the inner most for loop, we can break out of the loop at the iteration when it falls below the best_result's size seen so far