Spaces:
Sleeping
Sleeping
from itertools import combinations | |
from sympy.combinatorics.graycode import GrayCode | |
class Subset(): | |
""" | |
Represents a basic subset object. | |
Explanation | |
=========== | |
We generate subsets using essentially two techniques, | |
binary enumeration and lexicographic enumeration. | |
The Subset class takes two arguments, the first one | |
describes the initial subset to consider and the second | |
describes the superset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.next_binary().subset | |
['b'] | |
>>> a.prev_binary().subset | |
['c'] | |
""" | |
_rank_binary = None | |
_rank_lex = None | |
_rank_graycode = None | |
_subset = None | |
_superset = None | |
def __new__(cls, subset, superset): | |
""" | |
Default constructor. | |
It takes the ``subset`` and its ``superset`` as its parameters. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.subset | |
['c', 'd'] | |
>>> a.superset | |
['a', 'b', 'c', 'd'] | |
>>> a.size | |
2 | |
""" | |
if len(subset) > len(superset): | |
raise ValueError('Invalid arguments have been provided. The ' | |
'superset must be larger than the subset.') | |
for elem in subset: | |
if elem not in superset: | |
raise ValueError('The superset provided is invalid as it does ' | |
'not contain the element {}'.format(elem)) | |
obj = object.__new__(cls) | |
obj._subset = subset | |
obj._superset = superset | |
return obj | |
def __eq__(self, other): | |
"""Return a boolean indicating whether a == b on the basis of | |
whether both objects are of the class Subset and if the values | |
of the subset and superset attributes are the same. | |
""" | |
if not isinstance(other, Subset): | |
return NotImplemented | |
return self.subset == other.subset and self.superset == other.superset | |
def iterate_binary(self, k): | |
""" | |
This is a helper function. It iterates over the | |
binary subsets by ``k`` steps. This variable can be | |
both positive or negative. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.iterate_binary(-2).subset | |
['d'] | |
>>> a = Subset(['a', 'b', 'c'], ['a', 'b', 'c', 'd']) | |
>>> a.iterate_binary(2).subset | |
[] | |
See Also | |
======== | |
next_binary, prev_binary | |
""" | |
bin_list = Subset.bitlist_from_subset(self.subset, self.superset) | |
n = (int(''.join(bin_list), 2) + k) % 2**self.superset_size | |
bits = bin(n)[2:].rjust(self.superset_size, '0') | |
return Subset.subset_from_bitlist(self.superset, bits) | |
def next_binary(self): | |
""" | |
Generates the next binary ordered subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.next_binary().subset | |
['b'] | |
>>> a = Subset(['a', 'b', 'c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.next_binary().subset | |
[] | |
See Also | |
======== | |
prev_binary, iterate_binary | |
""" | |
return self.iterate_binary(1) | |
def prev_binary(self): | |
""" | |
Generates the previous binary ordered subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset([], ['a', 'b', 'c', 'd']) | |
>>> a.prev_binary().subset | |
['a', 'b', 'c', 'd'] | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.prev_binary().subset | |
['c'] | |
See Also | |
======== | |
next_binary, iterate_binary | |
""" | |
return self.iterate_binary(-1) | |
def next_lexicographic(self): | |
""" | |
Generates the next lexicographically ordered subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.next_lexicographic().subset | |
['d'] | |
>>> a = Subset(['d'], ['a', 'b', 'c', 'd']) | |
>>> a.next_lexicographic().subset | |
[] | |
See Also | |
======== | |
prev_lexicographic | |
""" | |
i = self.superset_size - 1 | |
indices = Subset.subset_indices(self.subset, self.superset) | |
if i in indices: | |
if i - 1 in indices: | |
indices.remove(i - 1) | |
else: | |
indices.remove(i) | |
i = i - 1 | |
while i >= 0 and i not in indices: | |
i = i - 1 | |
if i >= 0: | |
indices.remove(i) | |
indices.append(i+1) | |
else: | |
while i not in indices and i >= 0: | |
i = i - 1 | |
indices.append(i + 1) | |
ret_set = [] | |
super_set = self.superset | |
for i in indices: | |
ret_set.append(super_set[i]) | |
return Subset(ret_set, super_set) | |
def prev_lexicographic(self): | |
""" | |
Generates the previous lexicographically ordered subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset([], ['a', 'b', 'c', 'd']) | |
>>> a.prev_lexicographic().subset | |
['d'] | |
>>> a = Subset(['c','d'], ['a', 'b', 'c', 'd']) | |
>>> a.prev_lexicographic().subset | |
['c'] | |
See Also | |
======== | |
next_lexicographic | |
""" | |
i = self.superset_size - 1 | |
indices = Subset.subset_indices(self.subset, self.superset) | |
while i >= 0 and i not in indices: | |
i = i - 1 | |
if i == 0 or i - 1 in indices: | |
indices.remove(i) | |
else: | |
if i >= 0: | |
indices.remove(i) | |
indices.append(i - 1) | |
indices.append(self.superset_size - 1) | |
ret_set = [] | |
super_set = self.superset | |
for i in indices: | |
ret_set.append(super_set[i]) | |
return Subset(ret_set, super_set) | |
def iterate_graycode(self, k): | |
""" | |
Helper function used for prev_gray and next_gray. | |
It performs ``k`` step overs to get the respective Gray codes. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset([1, 2, 3], [1, 2, 3, 4]) | |
>>> a.iterate_graycode(3).subset | |
[1, 4] | |
>>> a.iterate_graycode(-2).subset | |
[1, 2, 4] | |
See Also | |
======== | |
next_gray, prev_gray | |
""" | |
unranked_code = GrayCode.unrank(self.superset_size, | |
(self.rank_gray + k) % self.cardinality) | |
return Subset.subset_from_bitlist(self.superset, | |
unranked_code) | |
def next_gray(self): | |
""" | |
Generates the next Gray code ordered subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset([1, 2, 3], [1, 2, 3, 4]) | |
>>> a.next_gray().subset | |
[1, 3] | |
See Also | |
======== | |
iterate_graycode, prev_gray | |
""" | |
return self.iterate_graycode(1) | |
def prev_gray(self): | |
""" | |
Generates the previous Gray code ordered subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset([2, 3, 4], [1, 2, 3, 4, 5]) | |
>>> a.prev_gray().subset | |
[2, 3, 4, 5] | |
See Also | |
======== | |
iterate_graycode, next_gray | |
""" | |
return self.iterate_graycode(-1) | |
def rank_binary(self): | |
""" | |
Computes the binary ordered rank. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset([], ['a','b','c','d']) | |
>>> a.rank_binary | |
0 | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.rank_binary | |
3 | |
See Also | |
======== | |
iterate_binary, unrank_binary | |
""" | |
if self._rank_binary is None: | |
self._rank_binary = int("".join( | |
Subset.bitlist_from_subset(self.subset, | |
self.superset)), 2) | |
return self._rank_binary | |
def rank_lexicographic(self): | |
""" | |
Computes the lexicographic ranking of the subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.rank_lexicographic | |
14 | |
>>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6]) | |
>>> a.rank_lexicographic | |
43 | |
""" | |
if self._rank_lex is None: | |
def _ranklex(self, subset_index, i, n): | |
if subset_index == [] or i > n: | |
return 0 | |
if i in subset_index: | |
subset_index.remove(i) | |
return 1 + _ranklex(self, subset_index, i + 1, n) | |
return 2**(n - i - 1) + _ranklex(self, subset_index, i + 1, n) | |
indices = Subset.subset_indices(self.subset, self.superset) | |
self._rank_lex = _ranklex(self, indices, 0, self.superset_size) | |
return self._rank_lex | |
def rank_gray(self): | |
""" | |
Computes the Gray code ranking of the subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c','d'], ['a','b','c','d']) | |
>>> a.rank_gray | |
2 | |
>>> a = Subset([2, 4, 5], [1, 2, 3, 4, 5, 6]) | |
>>> a.rank_gray | |
27 | |
See Also | |
======== | |
iterate_graycode, unrank_gray | |
""" | |
if self._rank_graycode is None: | |
bits = Subset.bitlist_from_subset(self.subset, self.superset) | |
self._rank_graycode = GrayCode(len(bits), start=bits).rank | |
return self._rank_graycode | |
def subset(self): | |
""" | |
Gets the subset represented by the current instance. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.subset | |
['c', 'd'] | |
See Also | |
======== | |
superset, size, superset_size, cardinality | |
""" | |
return self._subset | |
def size(self): | |
""" | |
Gets the size of the subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.size | |
2 | |
See Also | |
======== | |
subset, superset, superset_size, cardinality | |
""" | |
return len(self.subset) | |
def superset(self): | |
""" | |
Gets the superset of the subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.superset | |
['a', 'b', 'c', 'd'] | |
See Also | |
======== | |
subset, size, superset_size, cardinality | |
""" | |
return self._superset | |
def superset_size(self): | |
""" | |
Returns the size of the superset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.superset_size | |
4 | |
See Also | |
======== | |
subset, superset, size, cardinality | |
""" | |
return len(self.superset) | |
def cardinality(self): | |
""" | |
Returns the number of all possible subsets. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> a = Subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
>>> a.cardinality | |
16 | |
See Also | |
======== | |
subset, superset, size, superset_size | |
""" | |
return 2**(self.superset_size) | |
def subset_from_bitlist(self, super_set, bitlist): | |
""" | |
Gets the subset defined by the bitlist. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> Subset.subset_from_bitlist(['a', 'b', 'c', 'd'], '0011').subset | |
['c', 'd'] | |
See Also | |
======== | |
bitlist_from_subset | |
""" | |
if len(super_set) != len(bitlist): | |
raise ValueError("The sizes of the lists are not equal") | |
ret_set = [] | |
for i in range(len(bitlist)): | |
if bitlist[i] == '1': | |
ret_set.append(super_set[i]) | |
return Subset(ret_set, super_set) | |
def bitlist_from_subset(self, subset, superset): | |
""" | |
Gets the bitlist corresponding to a subset. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> Subset.bitlist_from_subset(['c', 'd'], ['a', 'b', 'c', 'd']) | |
'0011' | |
See Also | |
======== | |
subset_from_bitlist | |
""" | |
bitlist = ['0'] * len(superset) | |
if isinstance(subset, Subset): | |
subset = subset.subset | |
for i in Subset.subset_indices(subset, superset): | |
bitlist[i] = '1' | |
return ''.join(bitlist) | |
def unrank_binary(self, rank, superset): | |
""" | |
Gets the binary ordered subset of the specified rank. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> Subset.unrank_binary(4, ['a', 'b', 'c', 'd']).subset | |
['b'] | |
See Also | |
======== | |
iterate_binary, rank_binary | |
""" | |
bits = bin(rank)[2:].rjust(len(superset), '0') | |
return Subset.subset_from_bitlist(superset, bits) | |
def unrank_gray(self, rank, superset): | |
""" | |
Gets the Gray code ordered subset of the specified rank. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> Subset.unrank_gray(4, ['a', 'b', 'c']).subset | |
['a', 'b'] | |
>>> Subset.unrank_gray(0, ['a', 'b', 'c']).subset | |
[] | |
See Also | |
======== | |
iterate_graycode, rank_gray | |
""" | |
graycode_bitlist = GrayCode.unrank(len(superset), rank) | |
return Subset.subset_from_bitlist(superset, graycode_bitlist) | |
def subset_indices(self, subset, superset): | |
"""Return indices of subset in superset in a list; the list is empty | |
if all elements of ``subset`` are not in ``superset``. | |
Examples | |
======== | |
>>> from sympy.combinatorics import Subset | |
>>> superset = [1, 3, 2, 5, 4] | |
>>> Subset.subset_indices([3, 2, 1], superset) | |
[1, 2, 0] | |
>>> Subset.subset_indices([1, 6], superset) | |
[] | |
>>> Subset.subset_indices([], superset) | |
[] | |
""" | |
a, b = superset, subset | |
sb = set(b) | |
d = {} | |
for i, ai in enumerate(a): | |
if ai in sb: | |
d[ai] = i | |
sb.remove(ai) | |
if not sb: | |
break | |
else: | |
return [] | |
return [d[bi] for bi in b] | |
def ksubsets(superset, k): | |
""" | |
Finds the subsets of size ``k`` in lexicographic order. | |
This uses the itertools generator. | |
Examples | |
======== | |
>>> from sympy.combinatorics.subsets import ksubsets | |
>>> list(ksubsets([1, 2, 3], 2)) | |
[(1, 2), (1, 3), (2, 3)] | |
>>> list(ksubsets([1, 2, 3, 4, 5], 2)) | |
[(1, 2), (1, 3), (1, 4), (1, 5), (2, 3), (2, 4), \ | |
(2, 5), (3, 4), (3, 5), (4, 5)] | |
See Also | |
======== | |
Subset | |
""" | |
return combinations(superset, k) | |