Module polysphere_app.DLX

Expand source code
from abc import ABC, abstractmethod


class DLX:
    """
    DLX class represents the Dancing Links algorithm implementation.
    It provides methods to solve the exact cover problem using DLX algorithm.
    """

    class RowSupplier(ABC):
        @abstractmethod
        def isColumnOccupied(self, col) -> bool:
            pass

    @staticmethod
    def solve(row_info, num_columns):
        """
        Solves the exact cover problem using DLX algorithm.

        Parameters:
        - row_info (list): List of row information.
        - num_columns (int): Number of columns.

        Returns:
        - list: List of solutions.
        """
        dlx = DLXImpl(row_info, num_columns)
        return dlx.search(None)

    @staticmethod
    def solveAll(row_info, num_columns):
        """
        Solves all possible solutions for the exact cover problem using DLX algorithm.

        Parameters:
        - row_info (list): List of row information.
        - num_columns (int): Number of columns.

        Returns:
        - list: List of all solutions.
        """
        solutions = []
        dlx = DLXImpl(row_info, num_columns)
        dlx.search(solutions)
        return solutions


class DLXImpl:
    """
    Implementation of the Dancing Links (DLX) algorithm for solving exact cover problems.
    """

    class Cell:
        def __init__(self):
            self.up = None
            self.down = None
            self.column = None
            self.row = None
            self.detached = False

    class Header:
        def __init__(self):
            self.left = None
            self.right = None
            self.root = self.Cell()
            self.root.up = self.root
            self.root.down = self.root
            self.count = 0

        class Cell:
            def __init__(self):
                self.up = None
                self.down = None
                self.column = None
                self.row = None
                self.detached = False

    class Row:
        def __init__(self, info):
            self.cells = []
            self.info = info

    def __init__(self, row_info, num_columns):
        """
        Initializes the DLXImpl object.

        Args:
            row_info (list): List of RowInfo objects representing the rows of the exact cover problem.
            num_columns (int): Number of columns in the exact cover problem.
        """
        self.createHeaders(num_columns)
        self.createRows(row_info, num_columns)

    def search(self, solutions):
        """
        Searches for solutions to the exact cover problem.

        Args:
            solutions (list): List to store the solutions found.

        Returns:
            list: The first solution found, or None if no solution is found.
        """
        partial_solution = []
        return self._search(self.column_list_, partial_solution, solutions)

    def createRows(self, row_info, num_columns):
        """
        Creates the rows of the exact cover problem.

        Args:
            row_info (list): List of RowInfo objects representing the rows of the exact cover problem.
            num_columns (int): Number of columns in the exact cover problem.
        """
        self.rows = []
        for info in row_info:
            row = DLXImpl.Row(info)
            for c in range(num_columns):
                if info.isColumnOccupied(c):
                    cell = DLXImpl.Cell()
                    cell.row = row
                    cell.column = self.headers[c]
                    cell.up = cell.column.root.up
                    cell.down = cell.column.root
                    cell.column.root.up.down = cell
                    cell.column.root.up = cell
                    cell.column.count += 1
                    cell.detached = False
                    row.cells.append(cell)
            self.rows.append(row)

    def createHeaders(self, num_columns):
        """
        Creates the headers of the exact cover problem.

        Args:
            num_columns (int): Number of columns in the exact cover problem.
        """
        self.column_list_ = DLXImpl.Header()
        self.column_list_.right = self.column_list_
        self.column_list_.left = self.column_list_
        self.headers = []
        for _ in range(num_columns):
            h = DLXImpl.Header()
            h.right = self.column_list_
            h.left = self.column_list_.left
            self.column_list_.left.right = h
            self.column_list_.left = h
            self.headers.append(h)

    def _search(self, columns, partial_solution, all_solutions):
        """
        Recursive helper function for searching solutions to the exact cover problem.

        Args:
            columns (Header): The current column list.
            partial_solution (list): The current partial solution.
            all_solutions (list): List to store all solutions found.

        Returns:
            list: The first solution found, or None if no solution is found.
        """
        if self.isColumnListEmpty(columns):
            return partial_solution

        column = self.selectColumn(columns)

        x = column.root.down
        while x != column.root:
            partial_solution.append(x.row.info)

            for r in x.row.cells:
                self.eliminateColumn(r.column)

            solution = self._search(columns, partial_solution, all_solutions)
            if solution is not None:
                if all_solutions is not None:
                    all_solutions.append(list(solution))
                else:
                    return solution

            for r in reversed(x.row.cells):
                self.reinstateColumn(r.column)

            partial_solution.pop()
            x = x.down

        return None

    def isColumnListEmpty(self, column_list):
        """
        Checks if the column list is empty.

        Args:
            column_list (Header): The column list.

        Returns:
            bool: True if the column list is empty, False otherwise.
        """
        return column_list.right == column_list

    def selectColumn(self, column_list):
        """
        Selects the column with the fewest number of ones.

        Args:
            column_list (Header): The column list.

        Returns:
            Header: The selected column.
        """
        if self.isColumnListEmpty(column_list):
            return None

        min_col = column_list.right
        col = min_col.right
        while col != column_list:
            if col.count < min_col.count:
                min_col = col
            col = col.right
        return min_col

    def eliminateColumn(self, column):
        """
        Eliminates a column from the column list.

        Args:
            column (Header): The column to eliminate.
        """
        r = column.root.down
        while r != column.root:
            self.eliminateRow(r.row, column)
            r = r.down
        column.left.right = column.right
        column.right.left = column.left

    def reinstateColumn(self, column):
        """
        Reinstates a column into the column list.

        Args:
            column (Header): The column to reinstate.
        """
        r = column.root.up
        while r != column.root:
            self.reinstateRow(r.row)
            r = r.up
        column.left.right = column
        column.right.left = column

    def eliminateRow(self, row, skip_column):
        """
        Eliminates a row from the column list.

        Args:
            row (Row): The row to eliminate.
            skip_column (Header): The column to skip during elimination.
        """
        for cell in row.cells:
            if cell.column != skip_column and not cell.detached:
                cell.up.down = cell.down
                cell.down.up = cell.up
                assert cell.column.count > 0
                cell.column.count -= 1
                cell.detached = True

    def reinstateRow(self, row):
        """
        Reinstates a row into the column list.

        Args:
            row (Row): The row to reinstate.
        """
        for cell in reversed(row.cells):
            if cell.detached:
                cell.up.down = cell
                cell.down.up = cell
                cell.column.count += 1
                cell.detached = False

Classes

class DLX

DLX class represents the Dancing Links algorithm implementation. It provides methods to solve the exact cover problem using DLX algorithm.

Expand source code
class DLX:
    """
    DLX class represents the Dancing Links algorithm implementation.
    It provides methods to solve the exact cover problem using DLX algorithm.
    """

    class RowSupplier(ABC):
        @abstractmethod
        def isColumnOccupied(self, col) -> bool:
            pass

    @staticmethod
    def solve(row_info, num_columns):
        """
        Solves the exact cover problem using DLX algorithm.

        Parameters:
        - row_info (list): List of row information.
        - num_columns (int): Number of columns.

        Returns:
        - list: List of solutions.
        """
        dlx = DLXImpl(row_info, num_columns)
        return dlx.search(None)

    @staticmethod
    def solveAll(row_info, num_columns):
        """
        Solves all possible solutions for the exact cover problem using DLX algorithm.

        Parameters:
        - row_info (list): List of row information.
        - num_columns (int): Number of columns.

        Returns:
        - list: List of all solutions.
        """
        solutions = []
        dlx = DLXImpl(row_info, num_columns)
        dlx.search(solutions)
        return solutions

Class variables

var RowSupplier

Helper class that provides a standard way to create an ABC using inheritance.

Static methods

def solve(row_info, num_columns)

Solves the exact cover problem using DLX algorithm.

Parameters: - row_info (list): List of row information. - num_columns (int): Number of columns.

Returns: - list: List of solutions.

Expand source code
@staticmethod
def solve(row_info, num_columns):
    """
    Solves the exact cover problem using DLX algorithm.

    Parameters:
    - row_info (list): List of row information.
    - num_columns (int): Number of columns.

    Returns:
    - list: List of solutions.
    """
    dlx = DLXImpl(row_info, num_columns)
    return dlx.search(None)
def solveAll(row_info, num_columns)

Solves all possible solutions for the exact cover problem using DLX algorithm.

Parameters: - row_info (list): List of row information. - num_columns (int): Number of columns.

Returns: - list: List of all solutions.

Expand source code
@staticmethod
def solveAll(row_info, num_columns):
    """
    Solves all possible solutions for the exact cover problem using DLX algorithm.

    Parameters:
    - row_info (list): List of row information.
    - num_columns (int): Number of columns.

    Returns:
    - list: List of all solutions.
    """
    solutions = []
    dlx = DLXImpl(row_info, num_columns)
    dlx.search(solutions)
    return solutions
class DLXImpl (row_info, num_columns)

Implementation of the Dancing Links (DLX) algorithm for solving exact cover problems.

Initializes the DLXImpl object.

Args

row_info : list
List of RowInfo objects representing the rows of the exact cover problem.
num_columns : int
Number of columns in the exact cover problem.
Expand source code
class DLXImpl:
    """
    Implementation of the Dancing Links (DLX) algorithm for solving exact cover problems.
    """

    class Cell:
        def __init__(self):
            self.up = None
            self.down = None
            self.column = None
            self.row = None
            self.detached = False

    class Header:
        def __init__(self):
            self.left = None
            self.right = None
            self.root = self.Cell()
            self.root.up = self.root
            self.root.down = self.root
            self.count = 0

        class Cell:
            def __init__(self):
                self.up = None
                self.down = None
                self.column = None
                self.row = None
                self.detached = False

    class Row:
        def __init__(self, info):
            self.cells = []
            self.info = info

    def __init__(self, row_info, num_columns):
        """
        Initializes the DLXImpl object.

        Args:
            row_info (list): List of RowInfo objects representing the rows of the exact cover problem.
            num_columns (int): Number of columns in the exact cover problem.
        """
        self.createHeaders(num_columns)
        self.createRows(row_info, num_columns)

    def search(self, solutions):
        """
        Searches for solutions to the exact cover problem.

        Args:
            solutions (list): List to store the solutions found.

        Returns:
            list: The first solution found, or None if no solution is found.
        """
        partial_solution = []
        return self._search(self.column_list_, partial_solution, solutions)

    def createRows(self, row_info, num_columns):
        """
        Creates the rows of the exact cover problem.

        Args:
            row_info (list): List of RowInfo objects representing the rows of the exact cover problem.
            num_columns (int): Number of columns in the exact cover problem.
        """
        self.rows = []
        for info in row_info:
            row = DLXImpl.Row(info)
            for c in range(num_columns):
                if info.isColumnOccupied(c):
                    cell = DLXImpl.Cell()
                    cell.row = row
                    cell.column = self.headers[c]
                    cell.up = cell.column.root.up
                    cell.down = cell.column.root
                    cell.column.root.up.down = cell
                    cell.column.root.up = cell
                    cell.column.count += 1
                    cell.detached = False
                    row.cells.append(cell)
            self.rows.append(row)

    def createHeaders(self, num_columns):
        """
        Creates the headers of the exact cover problem.

        Args:
            num_columns (int): Number of columns in the exact cover problem.
        """
        self.column_list_ = DLXImpl.Header()
        self.column_list_.right = self.column_list_
        self.column_list_.left = self.column_list_
        self.headers = []
        for _ in range(num_columns):
            h = DLXImpl.Header()
            h.right = self.column_list_
            h.left = self.column_list_.left
            self.column_list_.left.right = h
            self.column_list_.left = h
            self.headers.append(h)

    def _search(self, columns, partial_solution, all_solutions):
        """
        Recursive helper function for searching solutions to the exact cover problem.

        Args:
            columns (Header): The current column list.
            partial_solution (list): The current partial solution.
            all_solutions (list): List to store all solutions found.

        Returns:
            list: The first solution found, or None if no solution is found.
        """
        if self.isColumnListEmpty(columns):
            return partial_solution

        column = self.selectColumn(columns)

        x = column.root.down
        while x != column.root:
            partial_solution.append(x.row.info)

            for r in x.row.cells:
                self.eliminateColumn(r.column)

            solution = self._search(columns, partial_solution, all_solutions)
            if solution is not None:
                if all_solutions is not None:
                    all_solutions.append(list(solution))
                else:
                    return solution

            for r in reversed(x.row.cells):
                self.reinstateColumn(r.column)

            partial_solution.pop()
            x = x.down

        return None

    def isColumnListEmpty(self, column_list):
        """
        Checks if the column list is empty.

        Args:
            column_list (Header): The column list.

        Returns:
            bool: True if the column list is empty, False otherwise.
        """
        return column_list.right == column_list

    def selectColumn(self, column_list):
        """
        Selects the column with the fewest number of ones.

        Args:
            column_list (Header): The column list.

        Returns:
            Header: The selected column.
        """
        if self.isColumnListEmpty(column_list):
            return None

        min_col = column_list.right
        col = min_col.right
        while col != column_list:
            if col.count < min_col.count:
                min_col = col
            col = col.right
        return min_col

    def eliminateColumn(self, column):
        """
        Eliminates a column from the column list.

        Args:
            column (Header): The column to eliminate.
        """
        r = column.root.down
        while r != column.root:
            self.eliminateRow(r.row, column)
            r = r.down
        column.left.right = column.right
        column.right.left = column.left

    def reinstateColumn(self, column):
        """
        Reinstates a column into the column list.

        Args:
            column (Header): The column to reinstate.
        """
        r = column.root.up
        while r != column.root:
            self.reinstateRow(r.row)
            r = r.up
        column.left.right = column
        column.right.left = column

    def eliminateRow(self, row, skip_column):
        """
        Eliminates a row from the column list.

        Args:
            row (Row): The row to eliminate.
            skip_column (Header): The column to skip during elimination.
        """
        for cell in row.cells:
            if cell.column != skip_column and not cell.detached:
                cell.up.down = cell.down
                cell.down.up = cell.up
                assert cell.column.count > 0
                cell.column.count -= 1
                cell.detached = True

    def reinstateRow(self, row):
        """
        Reinstates a row into the column list.

        Args:
            row (Row): The row to reinstate.
        """
        for cell in reversed(row.cells):
            if cell.detached:
                cell.up.down = cell
                cell.down.up = cell
                cell.column.count += 1
                cell.detached = False

Class variables

var Cell
var Header
var Row

Methods

def createHeaders(self, num_columns)

Creates the headers of the exact cover problem.

Args

num_columns : int
Number of columns in the exact cover problem.
Expand source code
def createHeaders(self, num_columns):
    """
    Creates the headers of the exact cover problem.

    Args:
        num_columns (int): Number of columns in the exact cover problem.
    """
    self.column_list_ = DLXImpl.Header()
    self.column_list_.right = self.column_list_
    self.column_list_.left = self.column_list_
    self.headers = []
    for _ in range(num_columns):
        h = DLXImpl.Header()
        h.right = self.column_list_
        h.left = self.column_list_.left
        self.column_list_.left.right = h
        self.column_list_.left = h
        self.headers.append(h)
def createRows(self, row_info, num_columns)

Creates the rows of the exact cover problem.

Args

row_info : list
List of RowInfo objects representing the rows of the exact cover problem.
num_columns : int
Number of columns in the exact cover problem.
Expand source code
def createRows(self, row_info, num_columns):
    """
    Creates the rows of the exact cover problem.

    Args:
        row_info (list): List of RowInfo objects representing the rows of the exact cover problem.
        num_columns (int): Number of columns in the exact cover problem.
    """
    self.rows = []
    for info in row_info:
        row = DLXImpl.Row(info)
        for c in range(num_columns):
            if info.isColumnOccupied(c):
                cell = DLXImpl.Cell()
                cell.row = row
                cell.column = self.headers[c]
                cell.up = cell.column.root.up
                cell.down = cell.column.root
                cell.column.root.up.down = cell
                cell.column.root.up = cell
                cell.column.count += 1
                cell.detached = False
                row.cells.append(cell)
        self.rows.append(row)
def eliminateColumn(self, column)

Eliminates a column from the column list.

Args

column : Header
The column to eliminate.
Expand source code
def eliminateColumn(self, column):
    """
    Eliminates a column from the column list.

    Args:
        column (Header): The column to eliminate.
    """
    r = column.root.down
    while r != column.root:
        self.eliminateRow(r.row, column)
        r = r.down
    column.left.right = column.right
    column.right.left = column.left
def eliminateRow(self, row, skip_column)

Eliminates a row from the column list.

Args

row : Row
The row to eliminate.
skip_column : Header
The column to skip during elimination.
Expand source code
def eliminateRow(self, row, skip_column):
    """
    Eliminates a row from the column list.

    Args:
        row (Row): The row to eliminate.
        skip_column (Header): The column to skip during elimination.
    """
    for cell in row.cells:
        if cell.column != skip_column and not cell.detached:
            cell.up.down = cell.down
            cell.down.up = cell.up
            assert cell.column.count > 0
            cell.column.count -= 1
            cell.detached = True
def isColumnListEmpty(self, column_list)

Checks if the column list is empty.

Args

column_list : Header
The column list.

Returns

bool
True if the column list is empty, False otherwise.
Expand source code
def isColumnListEmpty(self, column_list):
    """
    Checks if the column list is empty.

    Args:
        column_list (Header): The column list.

    Returns:
        bool: True if the column list is empty, False otherwise.
    """
    return column_list.right == column_list
def reinstateColumn(self, column)

Reinstates a column into the column list.

Args

column : Header
The column to reinstate.
Expand source code
def reinstateColumn(self, column):
    """
    Reinstates a column into the column list.

    Args:
        column (Header): The column to reinstate.
    """
    r = column.root.up
    while r != column.root:
        self.reinstateRow(r.row)
        r = r.up
    column.left.right = column
    column.right.left = column
def reinstateRow(self, row)

Reinstates a row into the column list.

Args

row : Row
The row to reinstate.
Expand source code
def reinstateRow(self, row):
    """
    Reinstates a row into the column list.

    Args:
        row (Row): The row to reinstate.
    """
    for cell in reversed(row.cells):
        if cell.detached:
            cell.up.down = cell
            cell.down.up = cell
            cell.column.count += 1
            cell.detached = False
def search(self, solutions)

Searches for solutions to the exact cover problem.

Args

solutions : list
List to store the solutions found.

Returns

list
The first solution found, or None if no solution is found.
Expand source code
def search(self, solutions):
    """
    Searches for solutions to the exact cover problem.

    Args:
        solutions (list): List to store the solutions found.

    Returns:
        list: The first solution found, or None if no solution is found.
    """
    partial_solution = []
    return self._search(self.column_list_, partial_solution, solutions)
def selectColumn(self, column_list)

Selects the column with the fewest number of ones.

Args

column_list : Header
The column list.

Returns

Header
The selected column.
Expand source code
def selectColumn(self, column_list):
    """
    Selects the column with the fewest number of ones.

    Args:
        column_list (Header): The column list.

    Returns:
        Header: The selected column.
    """
    if self.isColumnListEmpty(column_list):
        return None

    min_col = column_list.right
    col = min_col.right
    while col != column_list:
        if col.count < min_col.count:
            min_col = col
        col = col.right
    return min_col