I'm junior programmer, I am trying to solve 8-puzzle problem with breadth first search, but it took too long time to solve it, i want to optimize my code.
Configuration: [[5, 4, 3], [0, 7, 2], [6, 1, 8]] is going to solve in 22.623718615 seconds,
configuration [[8, 0, 6], [5, 4, 7], [2, 3, 1]] took among 235.721346421 seconds.
I want to decrease solve time.
There is my code:
from copy import deepcopy
from collections import deque
from time import perf_counter
most_hard = [[8, 0, 6], [5, 4, 7], [2, 3, 1]] # 30 moves
class CheckPuzzle:
def __init__(self, puzzle: list):
self.puzzle = puzzle
self.len = len(puzzle)
if self.len == 3:
self.goal = [[1, 2, 3],
[4, 5, 6],
[7, 8, 0]]
elif self.len == 4:
self.goal = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
if not self.is_valid():
raise TypeError("Puzzle is not valid")
elif not self.is_solvable():
raise Exception("Unsolvable puzzle")
# создай ф-ию check
def sum_of_numbers(self) -> int:
return sum(self.convert_to_1d(self.goal))
def sum_of_squares(self) -> int:
return sum([i ** 2 for i in self.convert_to_1d(self.goal)])
def is_valid(self) -> bool:
sum_of_numbers = 0
sum_of_squares = 0
for row in range(self.len):
for column in range(self.len):
sum_of_numbers += self.puzzle[row][column]
sum_of_squares += (self.puzzle[row][column]) ** 2
return sum_of_numbers == self.sum_of_numbers() and sum_of_squares == self.sum_of_squares()
def convert_to_1d(self, board) -> list:
one_dimension_matrix = []
for row in range(self.len):
for column in range(self.len):
one_dimension_matrix.append(board[row][column])
return one_dimension_matrix
def inversion(self, board) -> int:
inversion = 0
one_dimension_matrix = self.convert_to_1d(board)
for index in range(len(one_dimension_matrix)):
temp = one_dimension_matrix[index]
if temp == 0 or temp == 1:
continue
for elem in one_dimension_matrix[index:]:
if elem == 0:
continue
if temp > elem:
inversion += 1
return inversion
def is_solvable(self) -> bool:
inv_of_matrix = self.inversion(self.puzzle)
inv_of_goal_matrix = self.inversion(self.goal)
return (inv_of_matrix % 2 == 0 and inv_of_goal_matrix % 2 == 0) or \
(inv_of_matrix % 2 == 1 and inv_of_goal_matrix % 2 == 1)
class Puzzle:
def __init__(self, board: list):
self.board = board
self.len = len(board)
if self.len == 3:
self.goal = [[1, 2, 3],
[4, 5, 6],
[7, 8, 0]]
elif self.len == 4:
self.goal = [[1, 2, 3, 4],
[5, 6, 7, 8],
[9, 10, 11, 12],
[13, 14, 15, 0]]
def print_matrix(self) -> str:
output = ''
for row in self.board:
for elem in row:
output += str(elem) + " "
output += '\n'
return output
def get_index(self, matrix, value) -> tuple:
for i in range(self.len):
for j in range(self.len):
if matrix[i][j] == value:
return i, j
def manhattan(self):
distance = 0
for i in range(self.len):
for j in range(self.len):
if self.board[i][j] != 0:
x, y = divmod(self.board[i][j] - 1, self.len)
distance += abs(x - i) + abs(y - j)
return distance
def list_of_possible_moves(self) -> list:
x, y = self.get_index(self.board, 0)
possible_moves = []
if x > 0:
possible_moves.append((x - 1, y))
if x < self.len - 1:
possible_moves.append((x + 1, y))
if y > 0:
possible_moves.append((x, y - 1))
if y < self.len - 1:
possible_moves.append((x, y + 1))
return possible_moves
def move(self, to: tuple) -> list:
moving_board = deepcopy(self.board)
x, y = self.get_index(self.board, 0)
i, j = to
moving_board[x][y], moving_board[i][j] = moving_board[i][j], moving_board[x][y]
return moving_board
def solved(self) -> bool:
return self.board == self.goal
def __str__(self) -> str:
return ''.join(map(str, self))
def __iter__(self):
for row in self.board:
yield from row
class Node:
def __init__(self, puzzle, parent=None):
self.puzzle = puzzle
self.parent = parent
if self.parent:
self.g = parent.g + 1
else:
self.g = 0
def state(self) -> str:
return str(self)
def path(self):
node, p = self, []
while node:
p.append(node)
node = node.parent
yield from reversed(p)
def solved(self) -> bool:
return self.puzzle.solved()
def pretty_print(self) -> str:
return self.puzzle.print_matrix()
def h(self) -> int:
return self.puzzle.manhattan()
def f(self) -> int:
return self.h() + self.g
def all_moves(self) -> list:
return self.puzzle.list_of_possible_moves()
def __str__(self) -> str:
return str(self.puzzle)
def make_a_move(self, to: tuple) -> list:
return self.puzzle.move(to)
class GameTree:
def __init__(self, root):
self.root = root
def solve(self):
queue = deque([Node(self.root)])
seen = set()
seen.add(queue[0].state())
while queue:
queue = deque(sorted(list(queue), key=lambda node: node.f()))
node = queue.popleft()
if node.solved():
return node.path()
for move in node.all_moves():
moved = node.make_a_move(move)
child = Node(Puzzle(moved), node)
if child.state() not in seen:
queue.append(child)
seen.add(child.state())
def main():
a = [[5, 4, 3], [0, 7, 2], [6, 1, 8]]
c = Puzzle(a)
d = GameTree(c)
tic = perf_counter()
p = d.solve()
toc = perf_counter()
step = 0
for i in p:
print(i.pretty_print())
step += 1
print(step)
print(toc-tic)
if __name__ == "__main__":
main()
Description:
The 15-puzzle (also called Gem Puzzle, Boss Puzzle, Game of Fifteen,
Mystic Square and many others) is a sliding puzzle that consists of a
frame of numbered square tiles in random order with one tile missing.
The puzzle also exists in other sizes, particularly the smaller
8-puzzle. If the size is 3×3 tiles, the puzzle is called the 8-puzzle
or 9-puzzle, and if 4×4 tiles, the puzzle is called the 15-puzzle or
16-puzzle named, respectively, for the number of tiles and the number
of spaces. The object of the puzzle is to place the tiles in order by
making sliding moves that use the empty space.
https://en.wikipedia.org/wiki/15_puzzle
Related
I am trying to implement Circular Queue in python and trying implement str(self) that print all the elements from the queue from the beginning to the end.When I print out the list, it does not give the whole list of items in the queue.
I am splicing the items from the self.items from the front and going till the end of the list.
class CircularQueue:
def __init__(self, capacity):
self.items = [None] * capacity
self.max_queue = capacity
self.front = 0
self.back = self.max_queue - 1
self.count = 0
def enqueue(self, item):
if not self.is_full():
self.back = (self.back + 1) % self.max_queue
self.items[self.back] = item
self.count += 1
def dequeue(self):
if not self.is_empty():
item = self.items[self.front]
self.front = (self.front + 1) % self.max_queue
self.count -= 1
return item
def is_full(self):
return self.count == self.max_queue
def is_empty(self):
return self.count == 0
def peek(self):
return self.items[self.front]
def __str__(self):
my_list = []
for i in self.items[self.front:]:
my_list.append(i)
for i in self.items[:self.back+1]:
my_list.append(i)
return str(my_list)
q = CircularQueue(4)
print(q)
q.enqueue(1)
q.enqueue(2)
print(q)
q.enqueue(3)
q.enqueue(4)
print(q)
expected result
-> || ->
-> |1, 2| ->
-> |1, 2, 3, 4| ->
got
[None, None, None, None, None, None, None, None]
[1, 2, None, None, 1, 2]
[1, 2, 3, 4, 1, 2, 3, 4]
q = CircularQueue(4)
q.enqueue(1)
q.enqueue(2)
q.enqueue(3)
q.enqueue(4)
q.dequeue()
q.dequeue()
print(q)
q.dequeue()
q.dequeue()
print(q)
expected result
-> |3, 4| ->
-> || ->
got
[3, 4, 1, 2, 3, 4]
[1, 2, 3, 4, 1, 2, 3, 4]
You added two similar for loops, it's just that the first for loop prints everything on the right of the front, and the second for loop prints everything on the left of the back/rear. For example, if we have [1,2,None,None] where the front is at index 0 (value=1) and the rear is at index 1 (value=2) the 2 loops will print out [1,2,None,None,1,2], p.s the code adds 1 to the rear when printing, that's why you get [1,2,None,None,1,2] and not [1,2,None,None,1]
Solution:
replace the 2 for loops with 1 for loop that goes through the elements of the list once, so no splicing is needed
For the second output, I noticed that the dequeue function doesn't do anything, so I made it so that it replaces the item in the front with None
I added # changed on the lines I changed
class CircularQueue:
def __init__(self, capacity):
self.items = [None] * capacity
self.max_queue = capacity
self.front = 0
self.back = self.max_queue - 1
self.count = 0
def enqueue(self, item):
if not self.is_full():
self.back = (self.back + 1) % self.max_queue
self.items[self.back] = item
self.count += 1
def dequeue(self):
if not self.is_empty():
self.items[self.front] = None # changed
self.front = (self.front + 1) % self.max_queue
self.count -= 1
def is_full(self):
return self.count == self.max_queue
def is_empty(self):
return self.count == 0
def peek(self):
return self.items[self.front]
def __str__(self):
my_list = []
for i in self.items: # changed
my_list.append(i)
return str(my_list)
I'm supposed to create a program that is used to multiply or and/or add vectors. I'm supposed to do this using classes and overloading the built in functions like "+" and "str". I've done most of it but my problem is when I run the cases
v1 = Vector([2, 3, 4])
v2 = Vector([1, 2, 2])
v2[3] = 3
print(str(v2) == 'Vector: [1, 2, 3]')
print(str(v1 + v2) == 'Vector: [3, 5, 7]')
print(str(v2 * 2) == 'Vector: [2, 4, 6]')
print(str(2 * (v1 + v2)) == 'Vector: [6, 10, 14]')
For the last case I receive false, I did some poking around and found that my function is storing the data from the previous cases and is using them to compute the last case.
using print(str(v1 + v2) == 'Vector: [3, 5, 7]')
print(str(v2 * 2) == 'Vector: [2, 4, 6]')
as the 2 vectors instead and getting Vector: [10, 18, 26] as an output
my code is below:
class Vector:
def __init__(self,l):
if (isinstance(l,list) == False):
raise TypeError
elif(isinstance(l,list)):
for i in l:
if (isinstance(i, int) == False and isinstance(i,int) == False):
raise TypeError
else:
self.l = l
def dim(self):
return len(self.l)
def __getitem__ (self,i):
if i > len(self.l) or i < 1:
raise IndexError
return (self.l[i-1])
def __setitem__(self, i, x):
if i > len(self.l) or i < 1:
raise IndexError
self.l[i-1] = x
def __str__(self):
print ("Vector: "+ str(self.l))
return ("Vector: "+ str(self.l))
def __add__(self, other):
if (not isinstance(other, Vector)):
raise ValueError
elif(other.dim() != self.dim()):
raise ValueError
for i in range(0,len(self.l)):
self.l[i] = self.l[i] + other[i+1]
return Vector(self.l)
def __mul__(self, other):
if (isinstance(other,float)) or (isinstance(other,int)):
for j in range (0, len(self.l)):
self.l[j] = self.l[j] * other
return Vector(self.l)
elif (isinstance(other, Vector)):
for i in range(0,len(self.l)):
self.l[b] = self.l[b] * other[i+1]
return sum(self.l)
else:
raise AssertionError
def __rmul__(self,other):
if (isinstance(other,float)) or (isinstance(other,int)):
for k in range (0, len(self.l)):
self.l[k] = self.l[k] * other
return Vector(self.l)
elif (isinstance(other, Vector)):
for i in range(0,len(self.l)):
self.l[b] = self.l[b] * other[i+1]
return sum(self.l)
else:
raise AssertionError
How can I fix this unwanted overwriting?
I'm trying to adapt this answer in two ways:
I want to make the traverse function a class method, and
I want a call to traverse to yield the list of all root-to-leaf paths (list of lists) in the tree
First change was trivial, second one I'm struggling with. Here's my class definition:
class createnode:
""" thanks to https://stackoverflow.com/a/51911296/1870832"""
def __init__(self,nodeid):
self.nodeid=nodeid
self.child=[]
def __str__(self):
print(f"{self.nodeid}")
def traverse(self, path = []):
path.append(self.nodeid)
if len(self.child) == 0:
#print(path)
yield path
path.pop()
else:
for child in self.child:
child.traverse(path)
path.pop()
I construct a tree with:
ROOT_NODE = 0
root = createnode(ROOT_NODE)
lvl1 = [createnode(1), createnode(2), createnode(3)]
root.child += lvl1
root.child[0].child += [createnode(4), createnode(5)]
root.child[1].child += [createnode(6), createnode(7)]
root.child[2].child += [createnode(8), createnode(9)]
Desired output for printing all full root-leaf paths (e.g. w/ code below)
paths = root.traverse()
for p in paths:
print(p)
is:
[0, 1, 4]
[0, 1, 5]
[0, 2, 6]
[0, 2, 7]
[0, 3, 8]
[0, 3, 9]
You need to look into a recursive generator.
I have corrected your setup code:
class createnode:
""" thanks to https://stackoverflow.com/a/51911296/1870832"""
def __init__(self,nodeid):
self.nodeid=nodeid
self.child=[]
def __str__(self):
print(f"{self.nodeid}")
def traverse(self, path = None):
if path is None:
path = []
path.append(self.nodeid)
if len(self.child) == 0:
yield path
path.pop()
else:
for child in self.child:
yield from child.traverse(path)
path.pop()
ROOT_NODE = 0
root = createnode(ROOT_NODE)
children = [createnode(32), createnode(5)]
root.child += children
paths = root.traverse()
for p in paths:
print(p)
Output:
[0, 32]
[0, 5]
I don't have any experience with yield yet but I'd do it like this:
class createnode:
""" thanks to https://stackoverflow.com/a/51911296/1870832"""
def __init__(self,nodeid):
self.nodeid=nodeid
self.child=[]
def __str__(self):
print(f"{self.nodeid}")
def traverse(self, path = []):
path.append(self.nodeid)
if len(self.child) == 0:
print(path)
path.pop()
else:
for child in self.child:
child.traverse(path)
path.pop()
ROOT_NODE = 0
root = createnode(ROOT_NODE)
lvl1 = [createnode(1), createnode(2), createnode(3)]
root.child += lvl1
root.child[0].child += [createnode(4), createnode(5)]
root.child[1].child += [createnode(6), createnode(7)]
root.child[2].child += [createnode(8), createnode(9)]
root.traverse()
[0, 1, 4]
[0, 1, 5]
[0, 2, 6]
[0, 2, 7]
[0, 3, 8]
[0, 3, 9]
I'm trying to build a solution to the N-Puzzle problem using breadth first search in Python.
My solution is adept at finding an answer if all of the numbers bar the zero are in order. e.g.
initial_state = [1,2,3,4,0,5,6,7,8]
or
initial_state = [1,2,3,4,5,6,7,0,8]
but fails with
initial_state = [1,2,5,3,4,0,6,7,8]
Pleases find below my implementation. If someone could point out the flaw in my logic it'd much appreciated.
Thanks in advance!
def right(state):
items = list(state)
i = items.index(0)
n = int(math.sqrt(len(items)))
if (i+1) % n != 0:
del items[i]
items.insert(i+1, 0)
return tuple(items)
else:
pass
def left(state):
items = list(state)
i = items.index(0)
n = int(math.sqrt(len(items)))
if i % n != 0:
del items[i]
items.insert(i-1, 0)
return tuple(items)
else:
pass
def up(state):
items = list(state)
i = items.index(0)
n = int(math.sqrt(len(items)))
if n**2 < i <= (n**2 - n):
del items[i]
items.insert(i+n, 0)
return tuple(items)
else:
pass
def down(state):
items = list(state)
i = items.index(0)
n = int(math.sqrt(len(items)))
if i > n:
del items[i]
items.insert(i-n, 0)
return tuple(items)
else:
pass
class Problem(object):
def __init__(self, initial, goal=None):
self.initial = initial
self.goal = goal
self.n = len(initial)
self.size = int(math.sqrt(self.n))
self.blank = self.initial.index(0)
self.top_row = [i for i in range(self.n) if i < self.size]
self.bottom_row = [i for i in range(self.n) if self.n - (self.size) <= i < self.n]
self.left_column = [i for i in range(self.n) if i % self.size == 0]
self.right_column = [i for i in range(self.n) if (i + 1) % self.size == 0]
def actions(self):
result_list = ["UP","DOWN","LEFT","RIGHT"]
return result_list
def result(self, state, action):
if action == "RIGHT":
return right(state)
if action == "LEFT":
return left(state)
if action == "UP":
return up(state)
if action == "DOWN":
return down(state)
def goal_test(self, state):
return state == self.goal
def path_cost(self, c):
return c + 1
class Node:
def __init__(self, state, parent=None, action=None, path_cost=0):
self.state = state
self.parent = parent
self.action = action
self.path_cost = path_cost
self.depth = 0
if parent:
self.depth = parent.depth + 1
def __repr__(self):
return "<Node %s>" % (self.state,)
def __lt__(self, node):
return self.state < node.state
def expand(self, problem):
return [self.child_node(problem, action)
for action in problem.actions() if self.child_node(problem,action) is not None]
def child_node(self, problem, action):
next = problem.result(self.state, action)
if next:
return Node(next, self, action,
problem.path_cost(self.path_cost))
else:
pass
def solution(self):
return [node.action for node in self.path()[1:]]
def path(self):
node, path_back = self, []
while node:
path_back.append(node)
node = node.parent
return list(reversed(path_back))
def __eq__(self, other):
return isinstance(other, Node) and self.state == other.state
def __hash__(self):
return hash(self.state)
def bfs(problem):
node = Node(problem.initial)
frontier = deque([node])
explored = set()
while frontier:
node = frontier.pop()
explored.add(node.state)
if problem.goal_test(node.state):
return node
for child in node.expand(problem):
if child.state not in explored and child not in frontier:
frontier.append(child)
return [child for child in explored]
p = Problem((1,2,5,3,4,0,6,7,8), (0,1,2,3,4,5,6,7,8))
bfs(p)
#returns
"""[(1, 2, 5, 3, 4, 0, 6, 7, 8),
(1, 2, 0, 5, 3, 4, 6, 7, 8),
(0, 1, 2, 5, 3, 4, 6, 7, 8),
(1, 2, 5, 3, 0, 4, 6, 7, 8),
(1, 2, 5, 0, 3, 4, 6, 7, 8),
(1, 0, 2, 5, 3, 4, 6, 7, 8)]"""
If you process the neighbors (children) of a node (state) by moving the space in UP, DOWN, LEFT, RIGHT order, the solution of an 8-puzzle with bfs starting with the initial state 1,2,5,3,4,0,6,7,8 will be like the following (you can check out where it's differing with your solution):
path_to_goal: ['Up', 'Left', 'Left']
cost_of_path: 3
You may want to refer to this https://sandipanweb.wordpress.com/2017/03/16/using-uninformed-informed-search-algorithms-to-solve-8-puzzle-n-puzzle/?frame-nonce=9e97a821bc for more details.
This condition in up is never true: if n**2 < i <= (n**2 - n).
And this condition in down is off by one: if i > n.
Whether the rest of your code is correct or not is unclear, but you need to debug the fundamentals of your board representation and manipulation code first.
In your space-moving code, I personally would turn your index into an x and y coordinate:
x, y = i % n, i // n
Then you can test more naturally: x>0 for left, x<n-1 for right, y<n-1 for up and y>0 for down.
This question already has answers here:
python: how to have a property and with a setter function that detects all changes that happen to the value
(3 answers)
Closed 6 years ago.
globalList = []
class MyList:
def __init__(self):
self._myList = [1, 2, 3]
#property
def myList(self):
return self._myList + globalList
#myList.setter
def myList(self, val):
self._myList = val
mL1 = MyList()
print("myList: ", mL1.myList)
mL1.myList.append(4)
print("after appending a 4, myList: ", mL1.myList)
mL1.myList.extend([5,6,"eight","IX"])
print("after extend, myList: ", mL1.myList)
Result:
myList: [1, 2, 3]
after appending a 4, myList: [1, 2, 3]
after extend, myList: [1, 2, 3]
The problem I am facing is that mL1.myList.append(4) and mL1.myList.extend([5,6,"eight","IX"]) do not modify the _myList attribute in the mL1 object. How could I do to resolve the problem?
I define a method append() and a method extend() for the class object. It respectively appends to member myList and extends member myList.
global globalList
globalList = []
class MyList():
def __init__(self):
self._myList = [1, 2, 3]
#property
def myList(self):
return self._myList + globalList
#myList.setter
def myList(self, val):
self._myList = val
def append(self, val):
self.myList = self.myList + [val]
return self.myList
def extend(self, val):
return self.myList.extend(val)
mL1 = MyList()
print("myList: ", mL1.myList)
mL1.append(4)
print("after appending a 4, myList: ", mL1.myList)
mL1.myList.extend([5,6,"eight","IX"])
print("after extend, myList: ", mL1.myList)
result is
>>>
('myList: ', [1, 2, 3])
('after appending a 4, myList: ', [1, 2, 3, 4])
('after extend, myList: ', [1, 2, 3, 4, 5, 6, 'eight', 'IX'])
I would subclass list and override a few methods:
import itertools
class ExtendedList(list):
def __init__(self, other=None):
self.other = other or []
def __len__(self):
return list.__len__(self) + len(self.other)
def __iter__(self):
return itertools.chain(list.__iter__(self), iter(self.other))
def __getitem__(self, index):
l = list.__len__(self)
if index > l:
return self.other[index - l]
else:
return list.__getitem__(self, index)
It should work with just about everything:
In [9]: x = ExtendedList([1, 2, 3])
In [10]: x
Out[10]: [1, 2, 3]
In [11]: x.append(9)
In [12]: x
Out[12]: [9, 1, 2, 3]
In [13]: x.extend([19, 20])
In [14]: x
Out[14]: [9, 19, 20, 1, 2, 3]
In [15]: sum(x)
Out[15]: 54