DLX (Dancing Links) implementation in Cython
Project description
DLX Implementation
A DLX (Dancing Links) algorithm implementation in Cython for solving exact cover problems.
Features
- Fast: Implemented in Cython for speed (sub-millisecond for typical problems)
- Memory Efficient: Optimized data structures for minimal memory overhead
- Python Interface: Easy-to-use Python API
- Exact Cover Solver: Solves exact cover problems efficiently
Installation
From PyPI (when published)
pip install dlx-cython
Local Development
# Install in editable mode
pip install -e /path/to/dlx
From Source
git clone https://github.com/robmbrooks/dlx.git
cd dlx
pip install -e .
See INSTALLATION.md for detailed installation options and troubleshooting.
Quick Start
from dlxsolver import DLXSolver
# Define your constraint matrix
matrix = [
[1, 0, 1], # Row 0 covers columns 0 and 2
[0, 1, 0], # Row 1 covers column 1
[1, 1, 0], # Row 2 covers columns 0 and 1
]
# Create solver and find solution
solver = DLXSolver(matrix)
solution = solver.solve_one()
print(f"Solution: {solution}") # [0, 1]
Usage Examples
Basic Usage
from dlxsolver import DLXSolver
matrix = [[1, 0, 1], [0, 1, 0], [1, 1, 0]]
solver = DLXSolver(matrix)
# Find first solution
solution = solver.solve_one()
# Find all solutions
all_solutions = solver.solve(find_all=True)
Using Convenience Function
from dlxsolver import solve_exact_cover
solutions = solve_exact_cover(matrix, find_all=True)
With NumPy
import numpy as np
from dlxsolver import DLXSolver
matrix = np.array([[1, 0, 1], [0, 1, 0]], dtype=np.int32)
solver = DLXSolver(matrix)
solution = solver.solve_one()
Edge Cases
from dlxsolver import DLXSolver
import numpy as np
# Single row solution
matrix = [[1, 1, 1]] # One row covers all columns
solver = DLXSolver(matrix)
solution = solver.solve_one() # [0]
# Handling no solution
matrix = [[1, 0, 0], [0, 1, 0], [0, 0, 0]] # Column 2 can't be covered
solver = DLXSolver(matrix)
solution = solver.solve_one() # None
if solution is None:
print("No solution exists")
# Getting solution rows
matrix = [[1, 0, 1], [0, 1, 0]]
solver = DLXSolver(matrix)
solution = solver.solve_one() # [0, 1]
if solution:
solution_rows = solver.get_solution_rows(solution)
print(f"Solution rows:\n{solution_rows}")
# Output:
# [[1 0 1]
# [0 1 0]]
Error Handling
from dlxsolver import DLXSolver
# Invalid matrix (non-binary)
try:
matrix = [[1, 2, 3]] # Contains values other than 0/1
solver = DLXSolver(matrix)
except ValueError as e:
print(f"Error: {e}") # "Matrix must contain only 0s and 1s"
# Empty matrix
try:
matrix = []
solver = DLXSolver(matrix)
except ValueError as e:
print(f"Error: {e}") # "Matrix must have at least one row and one column"
# Non-2D matrix
try:
matrix = [1, 2, 3] # 1D array
solver = DLXSolver(matrix)
except ValueError as e:
print(f"Error: {e}") # "Matrix must be 2-dimensional"
Documentation
- INSTALLATION.md - Detailed installation instructions and troubleshooting
- PERFORMANCE_ANALYSIS.md - Performance benchmarks and analysis
- RELEASE.md - Release process for maintainers
Examples
See the examples/ directory for usage examples:
examples/simple_example.py- Basic usage examplesexamples/sudoku_example.py- Sudoku solver using DLXexample_usage.py- Comprehensive usage examples
Building from Source
# Install dependencies
pip install numpy cython setuptools
# Build Cython extension
python setup.py build_ext --inplace
# Install package
pip install -e .
Requirements
- Python 3.7+
- NumPy >= 1.19.0
- Cython >= 0.29.0 (for building)
API Reference
DLXSolver Class
Main solver class for exact cover problems.
DLXSolver(matrix)
Initialize the solver with a constraint matrix.
Parameters:
matrix(array-like): Binary matrix of shape(n_rows, n_cols)wherematrix[i][j] = 1means rowicovers columnj. Can be a list of lists or NumPy array.
Raises:
ValueError: If matrix is not 2-dimensional, empty, or contains non-binary values.
Example:
matrix = [[1, 0, 1], [0, 1, 0]]
solver = DLXSolver(matrix)
solve(find_all=True)
Solve the exact cover problem.
Parameters:
find_all(bool, optional): IfTrue(default), find all solutions. IfFalse, return only the first solution.
Returns:
list of lists: List of solutions, where each solution is a list of row indices forming an exact cover. Returns empty list if no solution exists.
Example:
solutions = solver.solve(find_all=True)
# [[0, 1], [2, 3]] # Multiple solutions
solve_one()
Find the first solution.
Returns:
list or None: First solution as a list of row indices, orNoneif no solution exists.
Example:
solution = solver.solve_one()
# [0, 1] or None
get_solution_rows(solution)
Get the actual matrix rows for a solution.
Parameters:
solution(list): List of row indices (as returned bysolve()orsolve_one()).
Returns:
numpy.ndarray: The rows of the matrix corresponding to the solution.
Example:
solution = [0, 1]
rows = solver.get_solution_rows(solution)
# Returns the actual matrix rows as a NumPy array
solve_exact_cover() Function
Convenience function to solve an exact cover problem without creating a solver instance.
Parameters:
matrix(array-like): Binary constraint matrix.find_all(bool, optional): Whether to find all solutions (default:True).
Returns:
list of lists: Solutions to the exact cover problem.
Example:
from dlxsolver import solve_exact_cover
matrix = [[1, 0, 1], [0, 1, 0]]
solutions = solve_exact_cover(matrix, find_all=True)
Performance
Characteristics
The DLX algorithm has the following performance characteristics:
-
Time Complexity:
- Best case: O(n) where n is the number of rows
- Average case: O(n × m) where m is average branching factor
- Worst case: Exponential (inherent to exact cover problems)
-
Space Complexity: O(n × m) where n is rows and m is columns
-
Typical Performance:
- Small problems (100x50): ~0.08ms
- Medium problems (500x200): ~10ms
- Large problems (1000x500): ~100ms
Factors Affecting Performance
- Matrix Density: Sparse matrices (low density) solve faster
- Solution Count: Finding all solutions takes longer than finding one
- Problem Structure: Problems with many overlapping constraints may be slower
- Early Termination: Using
find_all=Falsestops after first solution
Optimization Tips
- Use
solve_one()instead ofsolve(find_all=True)if you only need one solution - For very large problems, consider breaking into smaller sub-problems
- Sparse matrices (many zeros) perform better than dense matrices
See PERFORMANCE_ANALYSIS.md for detailed benchmarks and analysis.
License
This project is licensed under the MIT License - see the LICENSE file for details.
Citation
If you use this library in your research, please cite:
@software{dlx_cython,
title = {DLX-Cython: Dancing Links Implementation},
author = {Robert Brooks},
year = {2024},
url = {https://github.com/robmbrooks/dlx},
version = {0.1.0}
}
Contributing
Contributions are welcome! Please feel free to submit a Pull Request.
Troubleshooting
Common Issues
"No module named 'dlx'"
Problem: The Cython extension hasn't been built.
Solution:
cd /path/to/dlx
python setup.py build_ext --inplace
pip install -e .
"Matrix must contain only 0s and 1s"
Problem: Your matrix contains values other than 0 or 1.
Solution: Ensure your matrix is binary:
import numpy as np
matrix = np.array(your_matrix, dtype=np.int32)
# Verify it's binary
assert np.all((matrix == 0) | (matrix == 1))
"Matrix must be 2-dimensional"
Problem: You passed a 1D array instead of a 2D matrix.
Solution: Reshape your data:
# Wrong
matrix = [1, 0, 1, 0, 1, 0]
# Correct
matrix = [[1, 0, 1], [0, 1, 0]]
# or
matrix = np.array([1, 0, 1, 0, 1, 0]).reshape(2, 3)
Solver returns empty list / None
Problem: No exact cover solution exists for your matrix.
Solution: Verify your problem has a solution:
# Check if all columns can be covered
coverage = np.sum(matrix, axis=0)
if np.any(coverage == 0):
print("Some columns cannot be covered")
Slow performance on large problems
Problem: Very large or dense matrices may take longer.
Solutions:
- Use
solve_one()instead ofsolve(find_all=True)if possible - Consider if the problem can be decomposed into smaller sub-problems
- Check matrix density - sparse matrices perform better
Getting Help
- Check the examples/ directory for usage patterns
- Review PERFORMANCE_ANALYSIS.md for performance tips
- Open an issue on GitHub
Changelog
See CHANGELOG.md for version history.
Project details
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
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file dlx_cython-0.1.1.tar.gz.
File metadata
- Download URL: dlx_cython-0.1.1.tar.gz
- Upload date:
- Size: 140.7 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a29b9943254b7b63af06729c804a30af10fcc4547d7dc2deb2d52cf7ed133933
|
|
| MD5 |
4e7629917e7a7335df2f421c83c19785
|
|
| BLAKE2b-256 |
b0a0f833d66cadc60098e3267011b101559b80c989f2252acbe0b75c945fd51d
|
File details
Details for the file dlx_cython-0.1.1-cp314-cp314-macosx_15_0_arm64.whl.
File metadata
- Download URL: dlx_cython-0.1.1-cp314-cp314-macosx_15_0_arm64.whl
- Upload date:
- Size: 113.0 kB
- Tags: CPython 3.14, macOS 15.0+ ARM64
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.14.2
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
662122fc48d846817a437e8c12f902fb9461ea1d63e548387145c3062dd196fe
|
|
| MD5 |
e1f3c5e4b283a6606558453a1f18a3e1
|
|
| BLAKE2b-256 |
d1a3cfc1aad06105a397482fff968acabe0d11267750fb254d9a7de55298f72e
|