A python package to search graphs.
Project description
pygraphsearch
A python package to search graphs.
1. Installation
To install the package, run the following command.
pip install pygraphsearch
2. Usage
As an example on how to use this package, we will make a program that will solve sliding puzzles. The complete source code is in the folder example. We will provide the program with some shuffled arrangement of tiles as the starting node, and we will tell it what the correct tile arrangement is as the target node.
We will also tell it how to move from one board to another.
2.1 Node
This package includes a Node class which your nodes should inherit from. In our example, we will make a class Board
that extends Node
. This class will represent the layout of the tiles on the board at any given time.
To override Node
we must override the function neighbours
. This function should return edges to all the nodes reachable from the current node.
In a sliding puzzle, there are always up to four legal moves. The empty tile can move up, down, left, or right, unless it's on an edge of the board in which case it can't move past the edge. We can make an enum called Move to store these four options.
To check if a move is legal, we can create a method can_move
. If we are able to move in a direction, then we call the board's move
method which will return a copy of the board but with the given move applied.
The important thing here is that we define the function neighbours
which returns some edges.
Note: Your node objects must also implement __hash__
and __eq__
.
from pygraphsearch import Node, Edge
from typing import Iterable # it's always good to specify types
from .Move import Move
class Board(Node):
# ...
def neighbours(self) -> Iterable[Edge]:
return [
Edge(self, self.move(move), move)
for move in Move
if self.can_move(move)
]
def __eq__(self, board: object) -> bool:
return isinstance(board, Board) and self.__tiles == board.__tiles
def __hash__(self) -> int:
return hash(tuple(self.__tiles))
2.2 Edges
As we saw in the previous section, the neighbours
method has to return an Edge
. This class is defined in this package and can be constructed by passing it two nodes and some optional data. The data, if passed, is used to convert an edge into a string for convenience, but otherwise not used by the search package.
In our example it will be useful to store the direction that the empty tile is moved in an edge, as it will allow us to reconstruct the solution later.
2.3 Basic Usage
Now that we have our nodes, we will write a basic program that will request a puzzle from the user and attempt to solve it using the search
function provided by this package.
To get the puzzle size and initial board layout, we'll make the following function.
def get_board_details() -> Tuple[int, List[int]]:
# get board size
size = int(
input("Enter the size of the sliding puzzle. (e.g. for a 3x3 puzzle enter 3): ")
)
print(
f"Enter {size * size} tile values row by row from top left to bottom right."
" (Use 0 for the empty tile)"
)
# get list of size * size integer inputs from user
tiles = [int(input()) for _ in range(size * size)]
return size, tiles
Then we'll use the following code to perform the search.
# Get board details from the user
size, tiles = get_board_details()
# Construct the initial node
start_board = Board(size, tiles)
# Construct the target node, eg a board with the tiles in order: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
target = Board(size, range(size * size))
# Create a frontier with custom parameters
frontier = IterativeDeepeningFrontier(start_board, initial_depth=2, depth_step=2)
# Call the search function with our frontier and with a lambda to tell it that a node is a target if it is equal to `target`
state1 = search(frontier, lambda node: node == target)
# Or simply call the function with the default options for one of the predefined algorithms
state2 = search(
start_board, lambda node: node == target, Algorithm.BREADTH_FIRST_SEARCH # (DFS would take forever for this problem)
)
As you can see, there are two ways to call the search function. The simplest way is to provide it with:
- a start node,
- a function which takes a node and returns true if the node is a target node or false otherwise, and
- which algorithm to use.
The other way to call the search function is to provide it with:
- a frontier (more on that in the next section) and
- a function which takes a node and returns true if the node is a target node or false otherwise.
2.4 Frontiers
A frontier is the heart of the search algorithm. It is a data structure which holds the furthest nodes we have explored and it determines the order in which we will explore them further.
For BFS, the frontier would be a FIFO queue. For DFS, it would be a LIFO stack.
If you want to implement your own search algorithms, all you need is to implement a frontier to pass to the search
function by extending the abstract Frontier
class provided by this package and implementing the abstract methods. These are:
insert(self, state: State)
to insert states into the stack,extract(self) -> Optional[State]
to extract states from the stack, and__len__(self) -> int
which is the number of states stored in the frontier. The only important thing to be aware of about this function is that it should return 0 only when there are no more states to extract. If for some reason you can't or don't want to conform to this, you'll have to overrideis_empty
to return true only when there are no more states to extract, and the search is done.
2.5 State
The search function returns a state object. This object has two properties.
node (Node)
: The target node. In our example this would be the state of the solved board.path (List[Edge])
: A list of the edges that lead from the start to the target node.
Using these, we can print the moves to solve the puzzle to the user, like so (continuing from the previous code):
if state1 is not None:
for edge in state1.path:
print(edge.data, end=" ")
Here we're using edge.data which we set before by passing the moves to the Edge
constructor in the Board
class. By accessing the edges' data, we can retrieve the moves that were taken by the search algorithm to reach the solved state and print it back to the user.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
File details
Details for the file pygraphsearch-3.0.0.tar.gz
.
File metadata
- Download URL: pygraphsearch-3.0.0.tar.gz
- Upload date:
- Size: 11.3 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.1.13 CPython/3.10.5 Linux/5.13.0-1031-azure
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 635362c1df94880a860611b114d9dae899990ba9fd3e601d2ddcde18aff512e5 |
|
MD5 | b3446ea23e20657e6cd6278e6660d00d |
|
BLAKE2b-256 | ec969eca29e5ebb6d8ba5ea6ad15b4fd94e36c1454807a038cc5e82f38339ef7 |
File details
Details for the file pygraphsearch-3.0.0-py3-none-any.whl
.
File metadata
- Download URL: pygraphsearch-3.0.0-py3-none-any.whl
- Upload date:
- Size: 13.5 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: poetry/1.1.13 CPython/3.10.5 Linux/5.13.0-1031-azure
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | 1f5e6c14f2c9649a54895b69422a68e9e02b3d016d989e0c03b8266437e1e387 |
|
MD5 | 71ee7d98b2f309c72ef67b43537e621a |
|
BLAKE2b-256 | d5e1aac55d2e071d720f5986234fcd8bfbd569537371b91c7b2ebb833911d116 |