diff --git a/.travis.yml b/.travis.yml index 3469d31..3bc0644 100644 --- a/.travis.yml +++ b/.travis.yml @@ -17,7 +17,7 @@ install: - pip install -r tests/requirements.txt script: - - pytest --cov=. --cov-report=term-missing + - pytest --cov=combcov --cov-report=term-missing after_success: - coveralls diff --git a/README.md b/README.md index 4d71922..d6141cc 100644 --- a/README.md +++ b/README.md @@ -7,32 +7,24 @@ A generalization of the permutation-specific algorithm [Struct](https://github.c extended for other types of combinatorial objects. -## Example usage +## Demo -Below is an example where CombCov finds a _String Set_ cover for the set of string over the alphabet `{a,b}` that -avoids the substring `aa` (meaning no string in the set contains `aa` as a substring). +Take a look at `demo/string_set.py` as an example on how to use `CombCov` with your own combinatorial object. It finds +a _String Set_ cover for the set of string over the alphabet `{a,b}` that avoids the substring `aa` (meaning no string +in the set contains `aa` as a substring). -```python -from string_set import StringSet -from comb_cov import CombCov - -alphabet = ['a', 'b'] -avoid = ['aa'] -string_set = StringSet(alphabet, avoid) - -max_elmnt_size = 7 -comb_cov = CombCov(string_set, max_elmnt_size) +```bash +python -m demo.string_set ``` It prints out the following: ```text -Trying to find a cover for 'Av(aa) over ∑={a,b}' up to size 7 using 27 self-generated rules. +Trying to find a cover for ''*Av(aa) over ∑={a,b} using elements up to size 7. (Enumeration: [1, 2, 3, 5, 8, 13, 21, 34]) -SUCCESS! Found 1 solution(s). Solution nr. 1: - - ''*Av(b,a) over ∑={a,b} - - 'a'*Av(b,a) over ∑={a,b} + - ''*Av(a,b) over ∑={a,b} + - 'a'*Av(a,b) over ∑={a,b} - 'b'*Av(aa) over ∑={a,b} - 'ab'*Av(aa) over ∑={a,b} ``` @@ -44,5 +36,5 @@ Run unittests: ```bash pip install -r tests/requirements.txt -pytest +pytest --cov=combcov --cov=demo --cov-report=term-missing ``` diff --git a/comb_cov.py b/comb_cov.py deleted file mode 100644 index 48c2334..0000000 --- a/comb_cov.py +++ /dev/null @@ -1,62 +0,0 @@ -from exact_cover import exact_cover - - -class CombCov(): - - def __init__(self, root_object, max_elmnt_size=9): - self.root_object = root_object - self.max_elmnt_size = max_elmnt_size - self._enumerate_all_elmnts_up_to_max_size() - self._create_binary_strings_from_rules() - - print("Trying to find a cover for '{}' up to size {} using {} self-generated rules.".format(self.root_object, - self.max_elmnt_size, - len(self.rules))) - print("(Enumeration: {})".format(self.enumeration)) - self._find_cover() - - solutions = self.get_solutions() - if solutions.__len__() > 0: - print("SUCCESS! Found {} solution(s).".format(solutions.__len__())) - for nr, solution in enumerate(solutions, start=1): - print("Solution nr. {}:".format(nr)) - for rule in solution: - print(" - {}".format(rule)) - else: - print("FAILURE. No solutions found.") - - def _enumerate_all_elmnts_up_to_max_size(self): - elmnts = [] - self.enumeration = [None] * (self.max_elmnt_size + 1) - for n in range(self.max_elmnt_size + 1): - elmnts_of_length_n = self.root_object.of_length(n) - self.enumeration[n] = len(elmnts_of_length_n) - elmnts.extend(elmnts_of_length_n) - - self.elmnts_dict = { - string: nr for nr, string in enumerate(elmnts, start=0) - } - - def _create_binary_strings_from_rules(self): - self.rules_dict = {} - self.rules = self.root_object.rule_generator(max_string_length=self.max_elmnt_size) - for rule in self.rules: - binary_string = 0 - for elmnt in rule.get_elmnts(): - binary_string += 2 ** (self.elmnts_dict[elmnt]) - - self.rules_dict[rule] = binary_string - - def _find_cover(self): - self.solutions_indices = exact_cover(list(self.rules_dict.values()), len(self.elmnts_dict)) - - def get_solutions_indices(self): - return self.solutions_indices - - def get_solutions(self): - solutions = [] - for solution_indices in self.get_solutions_indices(): - solution = [self.rules[binary_string] for binary_string in solution_indices] - solutions.append(solution) - - return solutions diff --git a/combcov/__init__.py b/combcov/__init__.py new file mode 100644 index 0000000..b2db31d --- /dev/null +++ b/combcov/__init__.py @@ -0,0 +1,2 @@ +from .combcov import CombCov, Rule +from .exact_cover import ExactCover diff --git a/combcov/combcov.py b/combcov/combcov.py new file mode 100644 index 0000000..7128d99 --- /dev/null +++ b/combcov/combcov.py @@ -0,0 +1,77 @@ +import abc + +from combcov.exact_cover import ExactCover + + +class CombCov(): + + def __init__(self, root_object, max_elmnt_size): + self.root_object = root_object + self.max_elmnt_size = max_elmnt_size + self._enumerate_all_elmnts_up_to_max_size() + self._create_binary_strings_from_rules() + + def _enumerate_all_elmnts_up_to_max_size(self): + elmnts = [] + self.enumeration = [None] * (self.max_elmnt_size + 1) + for n in range(self.max_elmnt_size + 1): + elmnts_of_length_n = self.root_object.get_elmnts(of_size=n) + self.enumeration[n] = len(elmnts_of_length_n) + elmnts.extend(elmnts_of_length_n) + + self.elmnts_dict = { + string: nr for nr, string in enumerate(elmnts, start=0) + } + + def _create_binary_strings_from_rules(self): + self.rules_dict = {} + self.rules = [] + for rule in self.root_object.get_subrules(): + rule_is_good = True + binary_string = 0 + for elmnt_size in range(self.max_elmnt_size + 1): + seen_elmnts = set() + for elmnt in rule.get_elmnts(of_size=elmnt_size): + if elmnt not in self.elmnts_dict or elmnt in seen_elmnts: + rule_is_good = False + break + else: + seen_elmnts.add(elmnt) + binary_string += 2 ** (self.elmnts_dict[elmnt]) + + if not rule_is_good: + break + + if rule_is_good: + self.rules.append(rule) + self.rules_dict[rule] = binary_string + + def solve(self): + self.ec = ExactCover(list(self.rules_dict.values()), len(self.elmnts_dict)) + self.solutions_indices = self.ec.exact_cover() + + def get_solutions(self): + solutions = [] + for solution_indices in self.solutions_indices: + solution = [self.rules[binary_string] for binary_string in solution_indices] + solutions.append(solution) + + return solutions + + +class Rule(abc.ABC): + @abc.abstractmethod + def get_elmnts(self, of_size): + pass + + @abc.abstractmethod + def get_subrules(self): + pass + + @abc.abstractmethod + def __hash__(self): + pass + + @abc.abstractmethod + def __str__(self): + pass diff --git a/combcov/exact_cover.py b/combcov/exact_cover.py new file mode 100644 index 0000000..55afb42 --- /dev/null +++ b/combcov/exact_cover.py @@ -0,0 +1,68 @@ +import os +import shutil +import tempfile +from subprocess import Popen, DEVNULL + + +class ExactCover: + + def __init__(self, bitstrings, cover_string_length): + self.bitstrings = bitstrings + self.cover_string_length = cover_string_length + + def exact_cover(self): + try: + for res in self.exact_cover_gurobi(): + yield res + except Exception as exc: + raise RuntimeError( + "Gurobi may not be installed and there are no alternative solution method at the moment.") from exc + + def _call_Popen(self, inp, outp): + return Popen('gurobi_cl ResultFile=%s %s' % (outp, inp), shell=True, stdout=DEVNULL) + + def exact_cover_gurobi(self): + tdir = None + used = set() + anything = False + try: + tdir = str(tempfile.mkdtemp(prefix='combcov_tmp')) + inp = os.path.join(tdir, 'inp.lp') + outp = os.path.join(tdir, 'out.sol') + + with open(inp, 'w') as lp: + lp.write('Minimize %s\n' % ' + '.join('x%d' % i for i in range(len(self.bitstrings)))) + lp.write('Subject To\n') + + for i in range(self.cover_string_length): + here = [] + for j in range(len(self.bitstrings)): + if (self.bitstrings[j] & (1 << i)) != 0: + here.append(j) + lp.write(' %s = 1\n' % ' + '.join('x%d' % x for x in here)) + + lp.write('Binary\n') + lp.write(' %s\n' % ' '.join('x%d' % i for i in range(len(self.bitstrings)))) + lp.write('End\n') + + p = self._call_Popen(inp, outp) + assert p.wait() == 0 + + with open(outp, 'r') as sol: + while True: + line = sol.readline() + if not line: + break + if line.startswith('#') or not line.strip(): + continue + anything = True + + k, v = line.strip().split() + if int(v) == 1: + used.add(int(k[1:])) + finally: + if tdir is not None: + shutil.rmtree(tdir) + + if anything: + yield sorted(used) diff --git a/demo/__init__.py b/demo/__init__.py new file mode 100644 index 0000000..ffc51b0 --- /dev/null +++ b/demo/__init__.py @@ -0,0 +1 @@ +from .string_set import StringSet diff --git a/demo/string_set.py b/demo/string_set.py new file mode 100644 index 0000000..c4b719a --- /dev/null +++ b/demo/string_set.py @@ -0,0 +1,112 @@ +import itertools + +from combcov import CombCov, Rule + + +class StringSet(Rule): + + def __init__(self, alphabet=tuple(), avoid=frozenset(), prefix=""): + self.alphabet = tuple(alphabet) + self.avoid = frozenset(avoid) + self.prefix = prefix + self.max_prefix_size = max(0, max(len(av) for av in self.avoid)) + + def contains(self, string): + return all(av not in string for av in self.avoid) + + def next_lexicographical_string(self, from_string): + if from_string is None: + return "" + + else: + string = list(from_string) + + # Increasing last character by one and carry over if needed + for i in range(len(string)): + pos = -(i + 1) + char = string[pos] + index = self.alphabet.index(char) + next_index = index + 1 + if next_index == len(self.alphabet): + string[pos] = self.alphabet[0] + # ...and carry one over + else: + string[pos] = self.alphabet[next_index] + return "".join(string) + + # If we get this far we need to increase the length of the string + return self.alphabet[0] + "".join(string) + + @staticmethod + def _get_all_substrings_of(s): + # list of set because we don't want duplicates + return sorted(list(set(s[i:j + 1] for i in range(len(s)) for j in range(i, len(s))))) + + def get_all_avoiding_subsets(self): + avoiding_substrings = [self._get_all_substrings_of(avoid) for avoid in self.avoid] + return {frozenset(product) for product in itertools.product(*avoiding_substrings)} + + def get_elmnts(self, of_size): + strings_of_length = [] + + padding = of_size - len(self.prefix) + rest = self.alphabet[0] * padding + + while len(rest) == padding: + if self.contains(rest): + strings_of_length.append(self.prefix + rest) + rest = self.next_lexicographical_string(rest) + + return strings_of_length + + def get_subrules(self): + rules = [] + prefixes = [] + for n in range(self.max_prefix_size): + prefixes.extend(self.get_elmnts(n + 1)) + + # Singleton rules, on the form prefix + empty StringSet + for prefix in [''] + prefixes: + empty_string_set = StringSet(alphabet=self.alphabet, avoid=frozenset(self.alphabet), prefix=prefix) + rules.append(empty_string_set) + + # Regular rules of the from prefix + non-empty StringSet + for prefix in prefixes: + for avoiding_subset in self.get_all_avoiding_subsets(): + substring_set = StringSet(self.alphabet, avoiding_subset, prefix) + rules.append(substring_set) + + return rules + + def __eq__(self, other): + if isinstance(other, StringSet): + return (self.alphabet == other.alphabet and self.avoid == other.avoid and self.prefix == other.prefix) + return False + + def __hash__(self): + return hash(self.alphabet) ^ hash(self.avoid) ^ hash(self.prefix) + + def __str__(self): + return "'{}'*Av({}) over ∑={{{}}}".format(self.prefix, ",".join(self.avoid), ",".join(self.alphabet)) + + +def main(): + alphabet = ('a', 'b') + avoid = frozenset(['aa']) + string_set = StringSet(alphabet, avoid) + max_elmnt_size = 7 + + print("Trying to find a cover for {} using elements up to size {}.".format(string_set, max_elmnt_size)) + comb_cov = CombCov(string_set, max_elmnt_size) + comb_cov.solve() + + print("(Enumeration: {})".format(comb_cov.enumeration)) + + for nr, solution in enumerate(comb_cov.get_solutions(), start=1): + print("Solution nr. {}:".format(nr)) + for rule in solution: + print(" - {}".format(rule)) + + +if __name__ == "__main__": + main() diff --git a/exact_cover.py b/exact_cover.py deleted file mode 100644 index 4db2939..0000000 --- a/exact_cover.py +++ /dev/null @@ -1,65 +0,0 @@ -import os -import shutil -import tempfile -from subprocess import Popen, DEVNULL - - -def exact_cover(bitstrings, cover_string_length): - try: - for res in exact_cover_gurobi(bitstrings, cover_string_length): - yield res - except Exception as exc: - raise RuntimeError( - "Gurobi may not be installed and there are no alternative solution method at the moment.") from exc - - -def _call_Popen(inp, outp): - return Popen('gurobi_cl ResultFile=%s %s' % (outp, inp), shell=True, stdout=DEVNULL) - -def exact_cover_gurobi(bitstrings, cover_string_length): - tdir = None - used = set() - anything = False - try: - tdir = str(tempfile.mkdtemp(prefix='combcov_tmp')) - inp = os.path.join(tdir, 'inp.lp') - outp = os.path.join(tdir, 'out.sol') - - with open(inp, 'w') as lp: - lp.write('Minimize %s\n' % ' + '.join('x%d' % i for i in range(len(bitstrings)))) - lp.write('Subject To\n') - - for i in range(cover_string_length): - here = [] - for j in range(len(bitstrings)): - if (bitstrings[j] & (1 << i)) != 0: - here.append(j) - lp.write(' %s = 1\n' % ' + '.join('x%d' % x for x in here)) - - lp.write('Binary\n') - lp.write(' %s\n' % ' '.join('x%d' % i for i in range(len(bitstrings)))) - lp.write('End\n') - - p = _call_Popen(inp, outp) - assert p.wait() == 0 - - with open(outp, 'r') as sol: - while True: - line = sol.readline() - if not line: - break - if line.startswith('#') or not line.strip(): - continue - anything = True - - # x2 0 - # x3 1 - k, v = line.strip().split() - if int(v) == 1: - used.add(int(k[1:])) - finally: - if tdir is not None: - shutil.rmtree(tdir) - - if anything: - yield sorted(used) diff --git a/string_set.py b/string_set.py deleted file mode 100644 index 516f14e..0000000 --- a/string_set.py +++ /dev/null @@ -1,129 +0,0 @@ -import itertools -from collections import Generator - - -class Rule: - def __init__(self, prefix, string_set, max_string_length): - self.prefix = prefix - self.string_set = string_set - self.max_string_length = max_string_length - - self.strings = [] - for n in range(max_string_length - len(prefix) + 1): - for elmnt in string_set.of_length(n): - self.strings.append(prefix + elmnt) - - def get_elmnts(self): - return frozenset(self.strings) - - def __str__(self): - return "'{}'*{}".format(self.prefix, self.string_set) - - -class StringSet(Generator): - """The set of strings over alphabet ∑ avoiding a set of strings A.""" - - def __init__(self, alphabet=list(), avoid=frozenset()): - self.alphabet = list(alphabet) - self.avoid = frozenset(avoid) - if self.avoid is not frozenset(): - self.max_prefix_size = max(len(av) for av in self.avoid) - else: - self.max_prefix_size = 0 - - # Relating to the generator function - self._nr = 0 - self._last_string = None - - def __str__(self): - return "Av({}) over ∑={{{}}}".format(",".join(self.avoid), ",".join(self.alphabet)) - - def next_lexicographical_string(self, from_string): - if from_string is None: - return "" - - else: - string = list(from_string) - - # Increasing last character by one and carry over if needed - for i in range(len(string)): - pos = -(i + 1) - char = string[pos] - index = self.alphabet.index(char) - next_index = index + 1 - if next_index == len(self.alphabet): - string[pos] = self.alphabet[0] - # ...and carry one over - else: - string[pos] = self.alphabet[next_index] - return "".join(string) - - # If we get this far we need to increase the length of the string - return self.alphabet[0] + "".join(string) - - def contains(self, string): - return all(av not in string for av in self.avoid) - - def send(self, ignored_arg): - while True: - next_string = self.next_lexicographical_string(self._last_string) - self._last_string = next_string - if self.contains(next_string): - # Relies on this being an infinite set - self._nr += 1 - return next_string - - def throw(self, type=None, value=None, traceback=None): - raise StopIteration - - def of_length(self, n=0): - strings_of_length = [] - string_to_consider = self.alphabet[0] * n - - while len(string_to_consider) == n: - if self.contains(string_to_consider): - strings_of_length.append(string_to_consider) - string_to_consider = self.next_lexicographical_string(string_to_consider) - - return strings_of_length - - @staticmethod - def _get_all_substrings_of(s): - # list of set because we don't want duplicates - return sorted(list(set(s[i:j + 1] for i in range(len(s)) for j in range(i, len(s))))) - - def get_all_avoiding_subsets(self): - avoiding_substrings = [self._get_all_substrings_of(avoid) for avoid in self.avoid] - return {frozenset(product) for product in itertools.product(*avoiding_substrings)} - - def accept_rule(self, rule): - return rule.get_elmnts() is not frozenset() and all(self.contains(string) for string in rule.get_elmnts()) - - def rule_generator(self, max_string_length=0): - rules = [] - prefixes = [] - for n in range(self.max_prefix_size): - prefixes.extend(self.of_length(n + 1)) - - # Singleton rules, on the form prefix + empty StringSet - for prefix in [''] + prefixes: - empty_string_set = StringSet(alphabet=self.alphabet, avoid=frozenset(self.alphabet)) - rule = Rule(prefix=prefix, string_set=empty_string_set, max_string_length=len(prefix)) - if self.accept_rule(rule): - rules.append(rule) - - # Regular rules of the from prefix + non-empty StringSet - for prefix in prefixes: - for avoiding_subset in self.get_all_avoiding_subsets(): - substring_set = StringSet(self.alphabet, avoiding_subset) - rule = Rule(prefix, substring_set, max_string_length) - if self.accept_rule(rule): - rules.append(rule) - - return rules - - def __eq__(self, other): - """Overrides the default implementation""" - if isinstance(other, StringSet): - return (self.alphabet == other.alphabet and self.avoid == other.avoid) - return False diff --git a/tests/test_comb_cov.py b/tests/test_comb_cov.py index 92dda1b..bca1a52 100644 --- a/tests/test_comb_cov.py +++ b/tests/test_comb_cov.py @@ -1,25 +1,54 @@ import unittest from unittest.mock import patch -from comb_cov import CombCov -from string_set import StringSet +from combcov import CombCov, ExactCover +from demo import StringSet class CombCovTest(unittest.TestCase): def test_with_string_sets(self): - alphabet = ['a', 'b'] - avoid = ['aa'] + alphabet = ('a', 'b') + avoid = frozenset(['aa']) string_set = StringSet(alphabet, avoid) max_elmnt_size = 7 - with patch.object(CombCov, '_find_cover', return_value=None): - with patch.object(CombCov, 'get_solutions_indices', return_value=[[0, 1, 3], ]): - comb_cov = CombCov(string_set, max_elmnt_size) - solutions_indices = comb_cov.get_solutions_indices() + solution_indices = [[0, 1, 3], ] + with patch.object(ExactCover, 'exact_cover', return_value=solution_indices): + comb_cov = CombCov(string_set, max_elmnt_size) + comb_cov.solve() - actual_solution_indices = [0, 1, 3] - self.assertEqual(len(solutions_indices), 1) - self.assertEqual(solutions_indices[0], actual_solution_indices) + solutions = comb_cov.get_solutions() + self.assertEqual(len(solution_indices), len(solutions)) + + for i, solution in enumerate(solutions): + self.assertEqual(len(solution_indices[i]), len(solution)) + + +class RuleTest(unittest.TestCase): + alphabet = ('a', 'b') + avoid = frozenset(['aa', 'bb']) + avoid_subset = frozenset(['bb']) + prefix = "ab" + + def setUp(self): + self.string_set = StringSet(self.alphabet, self.avoid, self.prefix) + self.sub_string_set = StringSet(self.alphabet, self.avoid_subset, self.prefix) + + def test_elements(self): + max_string_length = 4 + for elmnt in self.string_set.get_elmnts(of_size=max_string_length): + self.assertEqual(max_string_length, len(elmnt)) + + elmnts = [] + for length in range(max_string_length + 1): + elmnts.extend(self.string_set.get_elmnts(of_size=length)) + + expected_rule_elmnts = frozenset(['ab', 'aba', 'abb', 'abab', 'abba']) + self.assertEqual(expected_rule_elmnts, frozenset(elmnts)) + + def test_rule_generation(self): + rules = self.string_set.get_subrules() + self.assertNotIn(None, rules) if __name__ == '__main__': diff --git a/tests/test_exact_cover.py b/tests/test_exact_cover.py index b73eccc..2289c2e 100644 --- a/tests/test_exact_cover.py +++ b/tests/test_exact_cover.py @@ -4,10 +4,10 @@ from subprocess import Popen, DEVNULL from unittest.mock import patch -import exact_cover +from combcov import ExactCover -def _mocked_call_Popen(inp, outp): +def _mocked_call_Popen(self, inp, outp): dir = str(Path(__file__).parents[0]) av_21_inp_lp = os.path.join(dir, 'av_21', 'inp.lp') av_21_out_sol = os.path.join(dir, 'av_21', 'out.sol') @@ -29,8 +29,9 @@ class GurobiExactCover(unittest.TestCase): cover_string_length = 5 def test_perm_av_21_with_gurobi_mocked_out(self): - with patch.object(exact_cover, '_call_Popen', _mocked_call_Popen): - solutions = exact_cover.exact_cover_gurobi(self.av_21_bitstrings, self.cover_string_length) + with patch.object(ExactCover, '_call_Popen', _mocked_call_Popen): + ec = ExactCover(self.av_21_bitstrings, self.cover_string_length) + solutions = ec.exact_cover_gurobi() first_solution = solutions.__next__() self.assertEqual(first_solution, self.av_21_solution) self.assertRaises(StopIteration, solutions.__next__) diff --git a/tests/test_string_set.py b/tests/test_string_set.py index 39f4607..2bef4a6 100644 --- a/tests/test_string_set.py +++ b/tests/test_string_set.py @@ -1,107 +1,63 @@ import unittest -from collections import Generator -from string_set import Rule, StringSet - - -class RuleTest(unittest.TestCase): - alphabet = ['a', 'b'] - avoid = ['aa', 'bb'] - avoid_subset = ['bb'] - - def test_rule_creation(self): - string_set = StringSet(self.alphabet, self.avoid) - - prefix = "ab" - max_string_length = 4 - avoiding_subset = ['bb'] - sub_string_set = StringSet(self.alphabet, avoiding_subset) - - rule = Rule(prefix, sub_string_set, max_string_length) - self.assertFalse(string_set.accept_rule(rule)) - self.assertGreaterEqual(max_string_length, max(len(elmnt) for elmnt in rule.get_elmnts())) - - expected_rule_elmnts = frozenset(['ab', 'aba', 'abb', 'abaa', 'abab', 'abba']) - self.assertEqual(rule.get_elmnts(), expected_rule_elmnts) - - def test_rule_generation(self): - string_set = StringSet(self.alphabet, self.avoid) - max_string_length = 9 - rules = string_set.rule_generator(max_string_length) - - self.assertNotIn(None, rules) - self.assertTrue(all(string_set.accept_rule(rule) for rule in rules)) +from demo import StringSet class StringSetTest(unittest.TestCase): - alphabet = ['a', 'b'] - avoid = ['aa', 'bb'] - avoid_subset = ['bb'] - - def test_is_generator(self): - string_set = StringSet(self.alphabet, self.avoid) - self.assertIsInstance(string_set, Generator) + alphabet = ('a', 'b') + avoid = frozenset(['aa', 'bb']) + avoid_subset = frozenset(['bb']) - def test_first_8_elements(self): - string_set = StringSet(self.alphabet, self.avoid) - actual_first_8_elmts = ['', 'a', 'b', 'ab', 'ba', 'aba', 'bab', 'abab'] - generator_first_8_elmts = [next(string_set) for _ in range(8)] - self.assertListEqual(actual_first_8_elmts, generator_first_8_elmts) + def setUp(self): + self.string_set = StringSet(self.alphabet, self.avoid) + self.sub_string_set = StringSet(self.alphabet, self.avoid_subset) def test_next_string(self): - string_set = StringSet(self.alphabet, self.avoid) + self.assertEqual(self.string_set.next_lexicographical_string(None), '') - self.assertEqual(string_set.next_lexicographical_string(''), 'a') - self.assertEqual(string_set.next_lexicographical_string('a'), 'b') - self.assertEqual(string_set.next_lexicographical_string('b'), 'aa') + self.assertEqual(self.string_set.next_lexicographical_string(''), 'a') + self.assertEqual(self.string_set.next_lexicographical_string('a'), 'b') + self.assertEqual(self.string_set.next_lexicographical_string('b'), 'aa') - self.assertEqual(string_set.next_lexicographical_string('aa'), 'ab') - self.assertEqual(string_set.next_lexicographical_string('ab'), 'ba') - self.assertEqual(string_set.next_lexicographical_string('ba'), 'bb') + self.assertEqual(self.string_set.next_lexicographical_string('aa'), 'ab') + self.assertEqual(self.string_set.next_lexicographical_string('ab'), 'ba') + self.assertEqual(self.string_set.next_lexicographical_string('ba'), 'bb') - self.assertEqual(string_set.next_lexicographical_string('bb'), 'aaa') - self.assertEqual(string_set.next_lexicographical_string('aaa'), 'aab') + self.assertEqual(self.string_set.next_lexicographical_string('bb'), 'aaa') + self.assertEqual(self.string_set.next_lexicographical_string('aaa'), 'aab') def test_contains_string(self): - string_set = StringSet(self.alphabet, self.avoid) - - self.assertFalse(string_set.contains('aa')) - self.assertFalse(string_set.contains('bb')) - self.assertFalse(string_set.contains('abba')) - self.assertFalse(string_set.contains('bababaa')) - - self.assertTrue(string_set.contains('')) - self.assertTrue(string_set.contains('a')) - self.assertTrue(string_set.contains('b')) - self.assertTrue(string_set.contains('ab')) - self.assertTrue(string_set.contains('bababa')) - - def test_sunny_strings_of_length(self): - string_set = StringSet(self.alphabet, self.avoid) - length_five_strings = string_set.of_length(5) - actual_valid_strings = ['ababa', 'babab'] - self.assertListEqual(length_five_strings, actual_valid_strings) - - def test_rainy_strings_of_length(self): - string_set = StringSet(self.alphabet, self.avoid) - length_zero_strings = string_set.of_length(0) + self.assertFalse(self.string_set.contains('aa')) + self.assertFalse(self.string_set.contains('bb')) + self.assertFalse(self.string_set.contains('abba')) + self.assertFalse(self.string_set.contains('bababaa')) + + self.assertTrue(self.string_set.contains('')) + self.assertTrue(self.string_set.contains('a')) + self.assertTrue(self.string_set.contains('b')) + self.assertTrue(self.string_set.contains('ab')) + self.assertTrue(self.string_set.contains('bababa')) + + def test_strings_of_length(self): + length_five_strings = self.string_set.get_elmnts(of_size=5) + self.assertListEqual(length_five_strings, ['ababa', 'babab']) + + length_zero_strings = self.string_set.get_elmnts(of_size=0) self.assertListEqual(length_zero_strings, ['']) - negative_length_strings = string_set.of_length(-1) + negative_length_strings = self.string_set.get_elmnts(of_size=-1) self.assertListEqual(negative_length_strings, []) def test_all_substrings_of_string(self): - string = 'aba' - substrings = StringSet._get_all_substrings_of(string) + substrings = StringSet._get_all_substrings_of('aba') expected_substrings = ['a', 'ab', 'aba', 'b', 'ba'] - self.assertEqual(expected_substrings, substrings) + self.assertEqual(substrings, expected_substrings) def test_avoiding_subsets(self): - string_set = StringSet(self.alphabet, self.avoid) expected_subsets = {frozenset({"aa", "bb"}), frozenset({"aa", "b"}), frozenset({"a", "bb"}), frozenset({'a', 'b'})} - subsets = string_set.get_all_avoiding_subsets() - self.assertEqual(expected_subsets, subsets) + subsets = self.string_set.get_all_avoiding_subsets() + self.assertEqual(subsets, expected_subsets) def test_equality(self): string_set = StringSet(self.alphabet, self.avoid) @@ -113,11 +69,6 @@ def test_equality_reversed_alphabet(self): string_set_rev = StringSet(list(reversed(self.alphabet)), self.avoid) self.assertNotEqual(string_set, string_set_rev) - def test_equality_reversed_avoidance(self): - string_set = StringSet(self.alphabet, self.avoid) - string_set_rev = StringSet(self.alphabet, list(reversed(self.avoid))) - self.assertEqual(string_set, string_set_rev) - def test_equality_nonsense(self): string_set = StringSet(self.alphabet, self.avoid) self.assertNotEqual(string_set, "nonsense")