diff --git a/tests/bruteforce.py b/tests/bruteforce.py new file mode 100644 index 0000000..741e5d1 --- /dev/null +++ b/tests/bruteforce.py @@ -0,0 +1,23 @@ +""" +bruteforce algorithm to compute the expected solutions +and help write tests +""" + +from itertools import chain, combinations + +import numpy as np + +# from the itertools module documentation +def powerset(iterable): + "powerset([1,2,3]) --> () (1,) (2,) (3,) (1,2) (1,3) (2,3) (1,2,3)" + s = list(iterable) + return chain.from_iterable(combinations(s, r) for r in range(len(s)+1)) + + +def bruteforce(data): + """ + Brute-force generator of all exact cover solutions + """ + for subset in powerset(range(data.shape[0])): + if np.all(data[list(subset)].sum(axis=0) == 1): + yield subset diff --git a/tests/problems.py b/tests/problems.py new file mode 100644 index 0000000..9c771d4 --- /dev/null +++ b/tests/problems.py @@ -0,0 +1,126 @@ +import numpy as np + +from exact_cover.io import DTYPE_FOR_ARRAY + +# one specific problem that I had trouble with +# originally based on solving the trivial problem +# of arranging 2 identical triminos on a 3x3 board + +# +--+ +# | | +# +--+--+ +# | | | +# +--+--+ + +# +--+--+--+ +# |xx| |xx| +# +--+--+--+ +# | | | | +# +--+--+--+ +# |xx| | | +# +--+--+--+ + +# this problem has 2 solutions +# (5, 13) and (6, 12) +def small_trimino_problem(): + to_cover = [ + [1, 0, 0, 1, 1, 0, 1, 0], + [1, 0, 0, 0, 1, 1, 0, 1], + [1, 0, 0, 0, 1, 1, 1, 0], + [1, 0, 1, 0, 1, 1, 0, 0], + [1, 0, 0, 0, 1, 0, 1, 1], + [1, 0, 1, 1, 1, 0, 0, 0], # <- 5 + [1, 0, 0, 0, 0, 1, 1, 1], # <- 6 + [0, 1, 0, 1, 1, 0, 1, 0], + [0, 1, 0, 0, 1, 1, 0, 1], + [0, 1, 0, 0, 1, 1, 1, 0], + [0, 1, 1, 0, 1, 1, 0, 0], + [0, 1, 0, 0, 1, 0, 1, 1], + [0, 1, 1, 1, 1, 0, 0, 0], # <- 12 + [0, 1, 0, 0, 0, 1, 1, 1], # <- 13 + ] + return dict( + data=np.array(to_cover, dtype=DTYPE_FOR_ARRAY), + solution1=[5, 13], + solution_count=2, + ) + +def small_trimino_problem_from_file(): + return dict( + data=np.load("tests/files/small_trimino_problem.npy"), + solution1=[5, 13], + solution_count=2, + ) + +# https://en.wikipedia.org/wiki/Exact_cover#Detailed_example +def detailed_wikipedia_problem(): + sets = [ + {1, 4, 7}, + {1, 4}, # <- 1 + {4, 5, 7}, + {3, 5, 6}, # <- 3 + {2, 3, 6, 7}, + {2, 7}, # <- 5 + ] + return dict( + data=np.array( + [[1 if i in s else 0 for i in range(1, 8)] for s in sets], + dtype=DTYPE_FOR_ARRAY), + solution1=[1, 3, 5], + solution_count=1, + ) + +def bruteforce_problem1(): + to_cover = [ + [1, 0, 0, 1, 0, 0, 1, 0], # <- sol1 + [0, 1, 0, 0, 1, 0, 0, 1], # <- sol1 + [0, 0, 1, 0, 0, 1, 0, 0], # <- sol1 + [0, 0, 0, 1, 0, 0, 0, 0], # <- sol2 + [1, 0, 1, 0, 1, 0, 0, 1], # <- sol2 + [0, 1, 0, 0, 0, 1, 1, 0], # <- sol2 + + ] + return dict( + data=np.array(to_cover, dtype=DTYPE_FOR_ARRAY), + solution1=[0, 1, 2], + solution_count=2, + ) + +def bruteforce_problem2(): + to_cover = [ + [1, 0, 0, 1, 0, 0, 1, 0], # <- sol1 + [0, 1, 0, 0, 1, 0, 0, 1], # <- sol1 + [0, 0, 1, 0, 0, 1, 0, 0], # <- sol1 + [0, 0, 0, 1, 0, 0, 0, 0], # <- sol2 + [1, 0, 1, 0, 1, 0, 0, 1], # <- sol2 + [0, 1, 0, 0, 0, 1, 1, 0], # <- sol2 + [1, 0, 0, 1, 0, 0, 1, 0], # <- sol1 + [0, 1, 0, 0, 1, 0, 0, 1], # <- sol1 + [0, 0, 1, 0, 0, 1, 0, 0], # <- sol1 + ] + return dict( + data=np.array(to_cover, dtype=DTYPE_FOR_ARRAY), + solution1=[0, 1, 2], + solution_count=9, + ) + +def bruteforce_problem3(): + to_cover = [ + [1, 0, 0, 1, 0, 0, 1, 0], # <- sol1 + [0, 1, 0, 0, 1, 0, 0, 1], # <- sol1 + [0, 0, 1, 0, 0, 1, 0, 0], # <- sol1 + [0, 0, 0, 1, 0, 0, 0, 0], # <- sol2 + [1, 0, 1, 0, 1, 0, 0, 1], # <- sol2 + [0, 1, 0, 0, 0, 1, 1, 0], # <- sol2 + [1, 0, 0, 1, 0, 0, 1, 0], # <- sol1 + [0, 1, 0, 0, 1, 0, 0, 1], # <- sol1 + [0, 0, 1, 0, 0, 1, 0, 0], # <- sol1 + [0, 0, 0, 1, 0, 0, 0, 0], # <- sol2 + [1, 0, 1, 0, 1, 0, 0, 1], # <- sol2 + [0, 1, 0, 0, 0, 1, 1, 0], # <- sol2 + ] + return dict( + data=np.array(to_cover, dtype=DTYPE_FOR_ARRAY), + solution1=[0, 1, 2], + solution_count=16, + ) diff --git a/tests/test_solution_count.py b/tests/test_solution_count.py index 62773ba..2822cdb 100644 --- a/tests/test_solution_count.py +++ b/tests/test_solution_count.py @@ -3,6 +3,9 @@ from exact_cover_impl import get_solution_count from exact_cover.io import DTYPE_FOR_ARRAY +from .problems import small_trimino_problem +from .problems import detailed_wikipedia_problem +from .problems import bruteforce_problem1 def test_solution_count(): data = np.array([[1, 0, 0], [0, 1, 0], [0, 1, 1], [0, 0, 1]], dtype=DTYPE_FOR_ARRAY) @@ -20,3 +23,33 @@ def test_solution_count_no_solutions(): data = np.array([[1, 0, 0, 0], [0, 1, 0, 0], [0, 1, 1, 0], [0, 0, 1, 0]], dtype=DTYPE_FOR_ARRAY) result = get_solution_count(data) assert result == 0 + +def test_small_trimino_problem(): + problem = small_trimino_problem() + data = problem['data'] + result = get_solution_count(data) + assert result == problem['solution_count'] + +def test_detailed_wikipedia_problem(): + problem = detailed_wikipedia_problem() + data = problem['data'] + result = get_solution_count(data) + assert result == problem['solution_count'] + +def test_bruteforce_problem1(): + problem = bruteforce_problem1() + data = problem['data'] + result = get_solution_count(data) + assert result == problem['solution_count'] + +def test_bruteforce_problem2(): + problem = bruteforce_problem2() + data = problem['data'] + result = get_solution_count(data) + assert result == problem['solution_count'] + +def test_bruteforce_problem3(): + problem = bruteforce_problem3() + data = problem['data'] + result = get_solution_count(data) + assert result == problem['solution_count'] diff --git a/tests/test_trimino_based.py b/tests/test_trimino_based.py index cb2bdc1..39b22d8 100644 --- a/tests/test_trimino_based.py +++ b/tests/test_trimino_based.py @@ -3,60 +3,19 @@ from exact_cover import get_exact_cover from exact_cover.error import NoSolution -from exact_cover.io import DTYPE_FOR_ARRAY -# from exact_cover.helpers import is_solution +from .problems import ( + # the exact cover matrix built manually + small_trimino_problem, + # the same store in a file + small_trimino_problem_from_file) -DTYPE = dict(dtype=DTYPE_FOR_ARRAY) -# from exact_cover.io import load_problem - -# from tests.config import GLOBAL_CONFIG - - -# one specific problem that I had trouble with -# originally based on solving the trivial problem -# of arranging 2 identical triminos on a 3x3 board - -# +--+ -# | | -# +--+--+ -# | | | -# +--+--+ - -# +--+--+--+ -# |xx| |xx| -# +--+--+--+ -# | | | | -# +--+--+--+ -# |xx| | | -# +--+--+--+ - - -# the exact cover matrix built manually def input1(): - to_cover = [ - [1, 0, 0, 1, 1, 0, 1, 0], - [1, 0, 0, 0, 1, 1, 0, 1], - [1, 0, 0, 0, 1, 1, 1, 0], - [1, 0, 1, 0, 1, 1, 0, 0], - [1, 0, 0, 0, 1, 0, 1, 1], - [1, 0, 1, 1, 1, 0, 0, 0], # <- 5 - [1, 0, 0, 0, 0, 1, 1, 1], - [0, 1, 0, 1, 1, 0, 1, 0], - [0, 1, 0, 0, 1, 1, 0, 1], - [0, 1, 0, 0, 1, 1, 1, 0], - [0, 1, 1, 0, 1, 1, 0, 0], - [0, 1, 0, 0, 1, 0, 1, 1], - [0, 1, 1, 1, 1, 0, 0, 0], - [0, 1, 0, 0, 0, 1, 1, 1], # <- 13 - ] - return np.array(to_cover, **DTYPE) - + return small_trimino_problem()['data'] def input2(): - return np.load("tests/files/small_trimino_problem.npy") - + return small_trimino_problem_from_file()['data'] def test_inputs_are_equal(): m1 = input1() @@ -77,8 +36,10 @@ def run_on_input(array, expected): def test_input1(): - run_on_input(input1(), [5, 13]) + expected = small_trimino_problem()['solution1'] + run_on_input(input1(), expected) def test_input2(): - run_on_input(input2(), [5, 13]) + expected = small_trimino_problem_from_file()['solution1'] + run_on_input(input2(), expected)