Chapter “Backtracking algorithms”, exercise 1
Text
Propose some variation to the implementation of the peg solitaire exercise in order to make it more efficient – in particular, avoiding unsuccessful configurations if they have been already encountered previously while looking for a solution.
Answer
from anytree import Node
from collections import deque
# Test case for the algorithm
def test_solve(pegs, holes, last_move, no_win_list, expected):
result = solve(pegs, holes, last_move, no_win_list)
if expected == result.name["in"] and len(pegs) == 1:
return True
else:
return False
# Code of the algorithm
def solve(pegs, holes, last_move, no_win):
result = None
if pegs not in no_win:
no_win.append(set(pegs))
if len(pegs) == 1 and (5, 1) in pegs: # leaf-win base case
result = last_move
else:
last_move.children = valid_moves(pegs, holes)
if len(last_move.children) == 0: # leaf-lose base case
undo_move(last_move, pegs, holes) # backtracking
else: # recursive step
possible_moves = deque(last_move.children)
while result is None and len(possible_moves) > 0:
current_move = possible_moves.pop()
apply_move(current_move, pegs, holes)
result = solve(pegs, holes, current_move, no_win)
if result is None:
undo_move(last_move, pegs, holes) # backtracking
else:
undo_move(last_move, pegs, holes) # backtracking
return result
def create_board():
initial_hole = (5, 1)
holes = set()
holes.add(initial_hole)
pegs = set([
(1, 0), (4, 0),
(0, 1), (1, 1), (2, 1), (3, 1), (4, 1),
(1, 2), (4, 2),
(1, 3), (4, 3),
(0, 4), (1, 4), (2, 4), (3, 4), (4, 4), (5, 4),
(1, 5), (4, 5)
])
return pegs, holes
def valid_moves(pegs, holes):
result = list()
for x, y in holes:
if (x-1, y) in pegs and (x-2, y) in pegs:
result.append(Node({"move": (x-2, y), "in": (x, y), "remove": (x-1, y)}))
if (x+1, y) in pegs and (x+2, y) in pegs:
result.append(Node({"move": (x+2, y), "in": (x, y), "remove": (x+1, y)}))
if (x, y-1) in pegs and (x, y-2) in pegs:
result.append(Node({"move": (x, y-2), "in": (x, y), "remove": (x, y-1)}))
if (x, y+1) in pegs and (x, y+2) in pegs:
result.append(Node({"move": (x, y+2), "in": (x, y), "remove": (x, y+1)}))
return result
def apply_move(node, pegs, holes):
move = node.name
old_pos = move.get("move")
new_pos = move.get("in")
eat_pos = move.get("remove")
pegs.remove(old_pos)
holes.add(old_pos)
pegs.add(new_pos)
holes.remove(new_pos)
pegs.remove(eat_pos)
holes.add(eat_pos)
def undo_move(node, pegs, holes):
move = node.name
old_pos = move.get("move")
new_pos = move.get("in")
eat_pos = move.get("remove")
pegs.add(old_pos)
holes.remove(old_pos)
pegs.remove(new_pos)
holes.add(new_pos)
pegs.add(eat_pos)
holes.remove(eat_pos)
# Tests
pegs, holes = create_board()
print(test_solve(pegs, holes, Node("start"), list(), (5, 1)))
Additional material
The runnable Python file is available online.