Change Breadth First Search to Depth First Search, N- Queen Solver - python

I would appreciate if someone helps me understand how to change the Breadth First Search to Depth First Search, or which steps I need to follow.
The algorithm is basically in the next functions:
canPlace
getpositions
loopBoard
CODE:
import sys
import copy
from os import system
#Set the console title
system("title Michael Fiford - Breadth First N-Queen Solver")
class QueenSolver:
#Store for the amount of queens we're placing, or table size
tableSize = 0
#The alphabet, for nice cell referencing on the output
alphabet = ['A', 'B', 'C', 'D', 'E', 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', 'U', 'V', 'W', 'X', 'Y', 'Z']
#The queue of possible moves that we will create and loop through
queue = []
#Whether or not the solver can be ran
canRun = False
def setup(self, queenNumber):
#Set the number of queens/table size
self.tableSize = queenNumber
#Can run, so long as there are no errors
self.canRun = True
#Show error if there is no solution, or would take too long
if queenNumber < 4:
print "ERROR: A solution is not available for this few number of queens"
self.canRun = False
elif queenNumber > 13:
print "ERROR: This solution would take too long to calculate, and your computer would probably run out of memory first!"
self.canRun = False
#Create an empty table
def blankTable(self):
table = []
for row in xrange(self.tableSize):
new = []
for col in xrange(self.tableSize):
new.append(0);
table.append(new)
return table
#Place a queen in a table
def placeQueen(self, table, row, col):
#Copy the table, as python is annoying and will change both
t2 = copy.deepcopy(table)
t2[row][col] = 1
return t2
#The main program loop
def loopBoard(self):
#The column we are currently looking at
col = 1
#Loop while the queue isn't empty, and there are still move possibilities to explore
while len(self.queue):
#Create a new empty queue
queue2 = []
#Update status
print col, "Queens Placed"
#Loop the queue, looking for positions from each status
#s is an array containing the current queen positions for this status
for s in self.queue:
#Get what moves are available from this status
availableMoves = self.getPositions(s, col)
#If we are placing the last queen, and there are solutions available, finish
if col == self.tableSize -1 and len(availableMoves):
#Clear queue
self.queue = []
#Get the solution (or one of them, we only care about the first one)
s = availableMoves[0]
break;
#Add the possible moves to the new queue
#This a table containing all queens now placed
if len(availableMoves):
queue2 += availableMoves
#Replace the old queue with the new one
self.queue = queue2
#Increase Queen/col counter
col += 1
self.finish(s, col)
#Get an array of moves that are available, given info about the current table, and the current column
def getPositions(self, table, col):
#Create a row counter, and array to contain our position info
row = 0
possiblePositions = []
#Loop all rows on the board
while row < self.tableSize:
#If we can place in this space
if self.canPlace(table, row, col):
#Add the table with the newly added queen to the list of possible moves
possiblePositions.append(self.placeQueen(table, row, col))
row += 1
return possiblePositions
#Check whether or not we can place a queen in a position, given the table and the row and col of the desired position
#Return True if space is available
def canPlace(self, table, row, col):
# - Direction
# Check left/right
x = 0
#Loop across the table
while x < self.tableSize:
if table[x][col]:
return False
x += 1
# | Direction
#Check up/down
y = 0
#Loop down the table
while y < self.tableSize:
if table[row][y]:
return False
y += 1
# / Direction
#Check up right Diagonal
#We can start in the cell 1 up and 1 right of the cell in question, as we have already checked the actual cell in the above 2 checks
x = row + 1
y = col + 1
#Loop up/right through the table
while x < self.tableSize and y < self.tableSize:
if table[x][y]:
return False
x += 1
y += 1
#Check down left Diagonal
#Again, not starting in the cell specified
x = row - 1
y = col - 1
#Loop down/left through the table
while x >= 0 and y >= 0:
if table[x][y]:
return False
x -= 1
y -= 1
# \ Direction
#Check up left diagonal
#Again, not starting in the cell specified
x = row - 1
y = col + 1
#Loop up left through the table
while x >= 0 and y < self.tableSize:
if table[x][y]:
return False
x -= 1
y += 1
#Check down right diagonal
#Again, not starting in the cell specified
x = row + 1
y = col - 1
#Loop down right through the table
while x < self.tableSize and y >= 0:
if table[x][y]:
return False
x += 1
y -= 1
return True
#Output a table to a user, looking all pretty
def display(self, table):
#Max Number Length, so we can indent our table nicely later
mnl = len(str(len(table)))
#New Line
print ""
#Top of the table, E.g " A B C D"
print " "*mnl, " ",
for x in range(self.tableSize):
print self.alphabet[x],
#New Line
print ""
#Row spacer, E.g " * - - - - *
print " " * mnl, " *",
for x in range(self.tableSize):
print "-",
print "*"
#Row Counter
#Print the actual table, with the Queens as 1's, empty space as 0
#Also prefixed by the row number, E.g " 3 | 0 1 0 0 |
x = 1
for row in table:
#If numbers are shorter than the largest number, give them extra spaces so the rows still line up
extraPadding = mnl - len(str(x))
#Show the number prefix, spaces, and | symbol, E.g " 6 | "
print "", x, " "*int(extraPadding) + "|",
#Show the value of the cell (1 or 0)
for col in row:
print col,
#End of the row
print "|"
#Next Row
x += 1
#Show the same row spacer as at the top of the table, E.g " * - - - - *
print " " * mnl, " *",
for x in range(self.tableSize):
print "-",
print "*"
#We're done! Show output to the user
def finish(self, table, col):
#If we found the right number of queens
if col == self.tableSize:
print ""
print "Total of", self.tableSize, "Queens placed!"
print "Solution:"
self.display(table)
else:
print ""
print "ERROR: Could not place all queens for some unknown reason =["
#Run the script
def run(self):
if not self.canRun:
print "ERROR: Can not run"
else:
print ""
print "Working..."
print ""
self.queue = self.getPositions(self.blankTable(), 0)
self.loopBoard()
#Ask the user how many Queens they want to use
def ask():
while True:
print ""
print "How many Queens would you like use? [8]"
input = raw_input()
#Check if the input given is an integer
if input.isdigit():
return int(input)
#If no input is given, use the standard 8
elif input == "":
return 8;
print "ERROR: Invalid Input"
#Run the program
def run():
#Instantiate the solver
qs = QueenSolver()
#While ask hasn't given a valid input
while(not qs.canRun):
qs.setup(ask())
print ""
#GO!
qs.run()
#Prompt the user if they want to run the program again
def prompt():
#Has valid input been received?
while True:
print ""
print "Would you like to run the script again? Please enter Y/N [N]"
input = raw_input()
#Check if the input given is Y or N
if input == "Y" or input == "y":
return True
#Also accept an empty string in place of N
elif input == "N" or input == "n" or input == "":
return False
print "ERROR: Invalid Input"
if __name__ == "__main__":
print ""
print ""
print " #######################################"
print " ## Breadth First Search Queen Solver ##"
print " ## By: Michael Fiford - COMF3 ##"
print " ## Date: 03/12/2013 ##"
print " #######################################"
#Run the script, and prompt them after if they want to run it again
shouldRun = True
while(shouldRun):
run()
shouldRun = prompt()

The difference between DFS and BFS is in the way you are exploring the nodes of the graph.
In the DFS algorithm, you are first exploring the last node added into the stack (last in first out) whereas in the BFS algorithm, you are exploring it on the basis of first in first out (queue).
The resulting change is the code shoud be quite small: in the BFS algorithm, you could use a list to implement the stack, appending new node at the top of the stack and then exploring them:
l=[]
while len(l)>0:
new_node=l.pop()
l.extend(new_node.get_neighbors())
The change is quite small to switch to the BFS algorithm: you switch from a stack to queue. This is implemented in Python in the collections module as a deque (with an efficient implementation of popleft (getting the first item of the list and remove it) and append (appending an item at the end of the queue):
import collections
l=collections.deque()
while len(l)>0:
new_node=l.popleft()
l.extend(new_node.get_neighbors())
Your code can be rewritten to fit with the previous description:
while len(self.queue):
#Create a new empty queue
#queue2 = []
#Update status
print col, "Queens Placed"
#Loop the queue, looking for positions from each status
#s is an array containing the current queen positions for this status
s=queue.pop()
#Get what moves are available from this status
availableMoves = self.getPositions(s, col)
#If we are placing the last queen, and there are solutions available, finish
if col == self.tableSize -1 and len(availableMoves):
#Clear queue
self.queue = []
#Get the solution (or one of them, we only care about the first one)
s = availableMoves[0]
break;
#Add the possible moves to the new queue
#This a table containing all queens now placed
#if len(availableMoves):
# queue2 += availableMoves
queue.extend(availableMoves)
#Replace the old queue with the new one
#self.queue = queue2
#Increase Queen/col counter
col += 1
Let me know if you need more explanations. Hope this helps.

Related

Can't seem to find the source of my KeyError

I'm currently doing a leetcode question, and I feel as though my logic is fairly solid, but I am getting a KeyError: 'B' error on the line that says if window_frequency[lefty] == 1:
and cannot seem to figure it out. Does anyone have any ideas?
Also the purpose of the code is to find the minimum substring of string s that contains all the letters of the second inputted string (aka string t)
code:
def minWindow(s, t):
"""
:type s: str
:type t: str
:rtype: str
"""
# dictionary of character frequencies
# everytime we move the right pointer, we check if the letter frequency is a subset of the
# window frequency dictionary, if it is, we set window_valid to true.
# when the window is no longer valid, we increase move it, editing the frequencies of the #window
letter_frequency = {}
window_frequency = {}
left = 0
current_result = ""
result = ""
# fill dictionary for letter frequency for t
for each in t:
if each not in letter_frequency:
letter_frequency[each] = 1
else:
letter_frequency[each] += 1
for right in range(len(s)):
letter = s[right]
# adding to frequency
if letter not in window_frequency:
window_frequency[letter] = 1
else:
window_frequency[letter] += 1
# setting the result value if the window is valid
window_valid = all(letter_frequency.get(key, None) == val for key, val in window_frequency.items())
while window_valid == True:
current_result = s[left:right]
if len(result) == 0:
result = current_result
elif (len(current_result) < len(result)):
result = current_result
# now we decrease the size of the window till it is no longer valid
lefty = s[left]
if window_frequency[lefty] == 1:
del window_frequency[lefty]
else:
window_frequency[lefty] -= 1
left += 1
return result
print(minWindow("ABAABANCHQR", "ABC"))

How to solve multiple sudokus in one file with python?

I am making a sudoku solver for school and one part of it, is that it needs to be able to solve multiple sudokus in a file. Currently it can solve one at a time, but how would I code it to solve multiple?
board = []
#open the file with puzzles in it
with open('project.txt','r') as file:
for line in file:
board.append(list(map(int,line.strip())))
#check puzzle for empty spaces
def solve(puzzle):
find = find_empty(puzzle)
if not find:
return True
else:
row, col = find
#go through 1 to 9 for each empty space
for vertical in range(1,10):
if valid(puzzle, vertical, (row, col)):
puzzle[row][col] = vertical
#continue to next empty space if number works
if solve(puzzle):
return True
#backtrack until correct number is found
puzzle[row][col] = 0
return False
def valid(puzzle, num, pos):
# Check each vertical row for correct numbers
for vertical in range(len(puzzle[0])):
if puzzle[pos[0]][vertical] == num and pos[1] != vertical:
return False
# Check each horizontal column for correct numbers
for vertical in range(len(puzzle)):
if puzzle[vertical][pos[1]] == num and pos[0] != vertical:
return False
# Check each 3x3 box for correct numbers
box_x = pos[1] // 3
box_y = pos[0] // 3
for vertical in range(box_y*3, box_y*3 + 3):
for horizontal in range(box_x * 3, box_x*3 + 3):
#if any wrong numbers appear try again
if puzzle[vertical][horizontal] == num and (vertical,horizontal) != pos:
return False
#if all numbers are correct solve puzzle
return True
#formatting for how the final board should look
def print_board(puzzle):
#open a new file for the answers to go into
with open('files.txt', 'w') as file:
#each row should have this formatting
for vertical in range(len(puzzle)):
if vertical % 3 == 0 and vertical != 0:
print("- - - - - - - - - - - - - ", file=file)
#each column should have this formatting
for horizontal in range(len(puzzle[0])):
if horizontal % 3 == 0 and horizontal != 0:
print(" | ", end="", file=file)
if horizontal == 8:
print(puzzle[vertical][horizontal], file=file)
else:
print(str(puzzle[vertical][horizontal]) + " ", end="", file=file)
def find_empty(puzzle):
for vertical in range(len(puzzle)):
for horizontal in range(len(puzzle[0])):
if puzzle[vertical][horizontal] == 0:
return (vertical, horizontal) # row, col
return None
#print the final solved puzzles
solve(board)
print_board(board)
the file would be set up like this:
003020600
900305001
001806400
008102900
700000008
006708200
002609500
800203009
005010300
000900800
128006400
070800060
800430007
500000009
600079008
090004010
003600284
001007000
and we don't know how many puzzles there will be in the end. Thanks for any help!
The problem was that you were putting all the puzzles into a single board.
Simply use the empty line (\n) between the puzzles as a delimiter to separate the board.
Besides that, you should use if __name__ == "__main__": to
protects users from accidentally invoking the script when they didn't intend to.
if __name__ == "__main__":
board_list = []
board = []
# open the file with puzzles in it
with open('project.txt', 'r') as file:
for line in file:
# print(line)
if line == "\n":
print("\\n detected")
board_list.append(board)
print(board_list)
board = []
else:
#print("constructing board")
board.append(list(map(int, line.strip())))
board_list.append(board)
for i in range(len(board_list)):
# print the final solved puzzles
solve(board_list[i])
print_board(board_list[i], i)
I have also modified print_board() so that it write the puzzles into different files.
def print_board(puzzle, index):
# take the index of the board to write it to different files
with open(f'files{index}.txt', 'w') as file:
# your code

CS50 DNA works for small.csv but not for large

I am having problems with CS50 pset6 DNA. It is getting all the right values and gives correct answers when I use the small.csv file but not when I use the large one. I have been going through it with debug50 for over a week and can't figure out the problem. I assume the problem is somewhere in the loop through the samples to find the STRS but I just don't see what it is doing wrong when walking through it.
If you are unfamiliar with CS50 DNA problemset, the code is supposed to look through a dna sequence (argv[1]) and compare it with a CSV file containing people DNA STRs to figure out which person (if any) it belongs to.
Note; My code fails within the case; (Python dna.py databases/large.csv sequences/5.txt) if this helps.
from sys import argv
from csv import reader
#ensures correct number of arguments
if (len(argv) != 3):
print("usage: python dna.py data sample")
#dict for storage
peps = {}
#storage for strands we look for.
types = []
#opens csv table
with open(argv[1],'r') as file:
data = reader(file)
line = 0
number = 0
for l in data:
if line == 0:
for col in l:
if col[2].islower() and col != 'name':
break
if col == 'name':
continue
else:
types.append(col)
line += 1
else:
row_mark = 0
for col in l:
if row_mark == 0:
peps[col] = []
row_mark += 1
else:
peps[l[0]].append(col)
#convert sample to string
samples = ""
with open(argv[2], 'r') as sample:
for c in sample:
samples = samples + c
#DNA STR GROUPS
dna = { "AGATC" : 0,
"AATG" : 0,
"TATC" : 0,
"TTTTTTCT" : 0,
"TCTAG" : 0,
"GATA" : 0,
"GAAA" : 0,
"TCTG" : 0 }
#go through all the strs in dna
for keys in dna:
#the longest run of sequnace
longest = 0
#the current run of sequances
run = 0
size = len(keys)
#look through sample for longest
i = 0
while i < len(samples):
hold = samples[i:(i + size)]
if hold == keys:
run += 1
#ensure the code does not go outside len of samples
if ((i + size) < len(samples)):
i = i + size
continue
if run > longest:
longest = run
run = 0
i += 1
dna[keys] = longest
#see who it is
positive = True
person = ''
for key in peps:
positive = True
for entry in types:
x = types.index(entry)
test = dna.get(entry)
can = int(peps.get(key)[x])
if (test != can):
positive = False
if positive == True:
person = key
break
if person != '':
print(person)
else:
print("No match")
Problem is in this while loop. Look at this code carefully.
while i < len(samples):
hold = samples[i:(i + size)]
if hold == keys:
run += 1
#ensure the code does not go outside len of samples
if ((i + size) < len(samples)):
i = i + size
continue
if run > longest:
longest = run
run = 0
i += 1
You have a missing logic here. You are supposed to check the longest consecutive DNA sequence. So when you have a repetition of dna sequence back to back, you need to find how many times it is repeated. When it is no longer repeated, only then, you need to check if this is the longest sequence.
Solution
You need to add else statement after if hold==keys: statement. This would be the right fix;
while i < len(samples):
hold = samples[i:(i + size)]
if hold == keys:
run += 1
#ensure the code does not go outside len of samples
if ((i + size) < len(samples)):
i = i + size
continue
else: #only if there is no longer sequence match, check this.
if run > longest:
longest = run
run = 0
else: #if the number of sequence match is still smaller then longest, then make run zero.
run = 0
i += 1
earik87 is absolutely right! Just I like to add, the code is missing an = to work for all the cases especially when you have redundant sequences.
while i < len(samples):
hold = samples[i:(i + size)]
if hold == keys:
run += 1
#ensure the code does not go outside len of samples **( I added =)**
if ((i + size) <= len(samples)):
i = i + size
continue
else: #only if there is no longer sequence match, check this.
if run > longest:
longest = run
run = 0
else: #if the number of sequence match is still smaller then longest, then make run zero.
run = 0
i += 1

Attempting to alter a copy but instead altering the original :(

I'm trying to make a 'smart' opponent in my Tic Tac Toe program. To do this I've created a 'possible win' function which will decide if there is a possible win in the next turn. My problem when running this code is that on every iteration of the for loop the variable board seems to be changed.
I want to reset potential board to the original board at the start of every iteration which is why I included potential_board = board[:] at the start of the loop. I then edit potential_board but every time the loop repeats this variable is not reset, and board is in fact changed as well. Why is this?
Many thanks!
import random,copy
board = [['o','o',' '],[' ',' ',' '],[' ',' ',' ']]
cols = [['o',' ',' '],['o','',''],['o','','']]
def possible_win(board,player):
""" This function should predict whether a winning move is possible in
the next turn. This could be done by simulating every possible next move
and running check_win() on those positions.
:param board,player: checks a win for the specified player
:return:
"""
spaces = empty_spaces(board)
print('Spaces',spaces)
winning_moves = []
for space in spaces:
potential_board = board[:]
print('PBoard',potential_board)
print(space[0],space[1])
potential_board[space[0]][space[1]] = 'o'
if check_win(potential_board,'o'):
winning_moves.append(space)
return winning_moves
def choose_space(board):
a = True
while a:
col = int(input('Choose your column of 1,2,3: ')) - 1
row = int(input('Choose your row of 1,2,3: ')) - 1
if board[row][col] == ' ':
board[row][col] = 'o'
a = False
else: print('Sorry, try again')
return board
def empty_spaces(board):
empty_spaces = []
ind = 0
for row in board:
ind1 = 0
for space in row:
if space == ' ':
empty_spaces.append((ind, ind1))
ind1 += 1
ind += 1
return empty_spaces
def comp_choose_space(board):
choice = random.choice(empty_spaces(board))
board[choice[0]][choice[1]] = 'x'
return board
def check_win(board,player):
rows = board
columns = construct_cols(board)
for row in board:
# if player fills row win = True
a = ind = 0
for space in row:
if rows[board.index(row)][ind] != player: break
else: a += 1
ind += 1
if a == 3:
return True
for col in columns:
a = ind = 0
for space in col:
if rows[columns.index(col)][ind] != player:
break
else:
a += 1
ind += 1
if a == 3:
return True
if rows[0][0] == player and rows[1][1] == player and rows[2][2] == player \
or rows[0][2] == player and rows[1][1] == player and rows[2][0] == player:
return True
return False
def construct_cols(board):
cols = [['','',''],['','',''],['','','']]
for row in range(len(board)):
for col in range(row):
cols[col][row] = board[row][col] # sounds like this should work
return cols
def print_board(board):
for row in board:
print('| {} {} {} |'.format(row[0],row[1],row[2]))
def main():
turns = 0
board = [[' ',' ',' '],[' ',' ',' '],[' ',' ',' ']]
print_board(board)
win = False
while win == False and turns < 9:
turns += 1
board = choose_space(board)
if check_win(board,'o'): win,winner = True,'won'
board = comp_choose_space(board)
if check_win(board,'x'): win,winner = True,'lost'
print_board(board)
if turns == 9: print('You drew!')
else:
print('{}, you {}'.format('Congratulations' if winner == 'won' else 'Sorry',winner))
print(possible_win(board,'o'))
# print(empty_spaces(board))
# print(check_win(board,'o'))
# print_board(board)
# print(comp_choose_space(board))
# main()
# Future project - make the computer smarter than just randomly choosing a space
# ie seeing how close i am to winning
EDIT:
By using copy.deepcopy() I managed to fix this, but I dont understand why this works and copy.copy() and board[:] did not work? Could somebody explain this?
This is what copy.deepcopy is for. It will traverse the structure, creating copies of each mutable object within. Using a slice [:] or shallow copy duplicated only the top level, leaving the list for each row shared.
Basically, if we start out with a list:
l = [a, b, c]
shallow = l[:]
shallow2 = copy(l)
deep = deepcopy(l)
The two shallow copies have operated only on l, not on a, b or c. They both have the value [a, b, c] but are distinct lists. All of them refer to the same a, b and c objects (the only change from their perspective is that there are more references).
The deep copy has gone deeper and copied each element; it is a new list with the shape [deepcopy(a), deepcopy(b), deepcopy(c)], whatever those values turned into.

How to count specific substrings using slice notation

I want to count the number of occurrences of the substring "bob" within the string s. I do this exercise for an edX Course.
s = 'azcbobobegghakl'
counter = 0
numofiterations = len(s)
position = 0
#loop that goes through the string char by char
for iteration in range(numofiterations):
if s[position] == "b": # search pos. for starting point
if s[position+1:position+2] == "ob": # check if complete
counter += 1
position +=1
print("Number of times bob occurs is: " + str(counter))
However it seems that the s[position+1:position+2] statement is not working properly. How do i adress the two chars behind a "b"?
The second slice index isn't included. It means that s[position+1:position+2] is a single character at position position + 1, and this substring cannot be equal to ob. See a related answer. You need [:position + 3]:
s = 'azcbobobegghakl'
counter = 0
numofiterations = len(s)
position = 0
#loop that goes through the string char by char
for iteration in range(numofiterations - 2):
if s[position] == "b": # search pos. for starting point
if s[position+1:position+3] == "ob": # check if complete
counter += 1
position +=1
print("Number of times bob occurs is: " + str(counter))
# 2
You could use .find with an index:
s = 'azcbobobegghakl'
needle = 'bob'
idx = -1; cnt = 0
while True:
idx = s.find(needle, idx+1)
if idx >= 0:
cnt += 1
else:
break
print("{} was found {} times.".format(needle, cnt))
# bob was found 2 times.
Eric's answer explains perfectly why your approach didn't work (slicing in Python is end-exclusive), but let me propose another option:
s = 'azcbobobegghakl'
substrings = [s[i:] for i in range(0, len(s))]
filtered_s = filter(substrings, lambda s: s.startswith("bob"))
result = len(filtered_s)
or simply
s = 'azcbobobegghakl'
result = sum(1 for ss in [s[i:] for i in range(0, len(s))] if ss.startswith("bob"))

Categories