You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
431 lines
11 KiB
431 lines
11 KiB
from sympy.core import Basic, Integer
|
|
|
|
import random
|
|
|
|
|
|
class GrayCode(Basic):
|
|
"""
|
|
A Gray code is essentially a Hamiltonian walk on
|
|
a n-dimensional cube with edge length of one.
|
|
The vertices of the cube are represented by vectors
|
|
whose values are binary. The Hamilton walk visits
|
|
each vertex exactly once. The Gray code for a 3d
|
|
cube is ['000','100','110','010','011','111','101',
|
|
'001'].
|
|
|
|
A Gray code solves the problem of sequentially
|
|
generating all possible subsets of n objects in such
|
|
a way that each subset is obtained from the previous
|
|
one by either deleting or adding a single object.
|
|
In the above example, 1 indicates that the object is
|
|
present, and 0 indicates that its absent.
|
|
|
|
Gray codes have applications in statistics as well when
|
|
we want to compute various statistics related to subsets
|
|
in an efficient manner.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics import GrayCode
|
|
>>> a = GrayCode(3)
|
|
>>> list(a.generate_gray())
|
|
['000', '001', '011', '010', '110', '111', '101', '100']
|
|
>>> a = GrayCode(4)
|
|
>>> list(a.generate_gray())
|
|
['0000', '0001', '0011', '0010', '0110', '0111', '0101', '0100', \
|
|
'1100', '1101', '1111', '1110', '1010', '1011', '1001', '1000']
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] Nijenhuis,A. and Wilf,H.S.(1978).
|
|
Combinatorial Algorithms. Academic Press.
|
|
.. [2] Knuth, D. (2011). The Art of Computer Programming, Vol 4
|
|
Addison Wesley
|
|
|
|
|
|
"""
|
|
|
|
_skip = False
|
|
_current = 0
|
|
_rank = None
|
|
|
|
def __new__(cls, n, *args, **kw_args):
|
|
"""
|
|
Default constructor.
|
|
|
|
It takes a single argument ``n`` which gives the dimension of the Gray
|
|
code. The starting Gray code string (``start``) or the starting ``rank``
|
|
may also be given; the default is to start at rank = 0 ('0...0').
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics import GrayCode
|
|
>>> a = GrayCode(3)
|
|
>>> a
|
|
GrayCode(3)
|
|
>>> a.n
|
|
3
|
|
|
|
>>> a = GrayCode(3, start='100')
|
|
>>> a.current
|
|
'100'
|
|
|
|
>>> a = GrayCode(4, rank=4)
|
|
>>> a.current
|
|
'0110'
|
|
>>> a.rank
|
|
4
|
|
|
|
"""
|
|
if n < 1 or int(n) != n:
|
|
raise ValueError(
|
|
'Gray code dimension must be a positive integer, not %i' % n)
|
|
n = Integer(n)
|
|
args = (n,) + args
|
|
obj = Basic.__new__(cls, *args)
|
|
if 'start' in kw_args:
|
|
obj._current = kw_args["start"]
|
|
if len(obj._current) > n:
|
|
raise ValueError('Gray code start has length %i but '
|
|
'should not be greater than %i' % (len(obj._current), n))
|
|
elif 'rank' in kw_args:
|
|
if int(kw_args["rank"]) != kw_args["rank"]:
|
|
raise ValueError('Gray code rank must be a positive integer, '
|
|
'not %i' % kw_args["rank"])
|
|
obj._rank = int(kw_args["rank"]) % obj.selections
|
|
obj._current = obj.unrank(n, obj._rank)
|
|
return obj
|
|
|
|
def next(self, delta=1):
|
|
"""
|
|
Returns the Gray code a distance ``delta`` (default = 1) from the
|
|
current value in canonical order.
|
|
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics import GrayCode
|
|
>>> a = GrayCode(3, start='110')
|
|
>>> a.next().current
|
|
'111'
|
|
>>> a.next(-1).current
|
|
'010'
|
|
"""
|
|
return GrayCode(self.n, rank=(self.rank + delta) % self.selections)
|
|
|
|
@property
|
|
def selections(self):
|
|
"""
|
|
Returns the number of bit vectors in the Gray code.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics import GrayCode
|
|
>>> a = GrayCode(3)
|
|
>>> a.selections
|
|
8
|
|
"""
|
|
return 2**self.n
|
|
|
|
@property
|
|
def n(self):
|
|
"""
|
|
Returns the dimension of the Gray code.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics import GrayCode
|
|
>>> a = GrayCode(5)
|
|
>>> a.n
|
|
5
|
|
"""
|
|
return self.args[0]
|
|
|
|
def generate_gray(self, **hints):
|
|
"""
|
|
Generates the sequence of bit vectors of a Gray Code.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics import GrayCode
|
|
>>> a = GrayCode(3)
|
|
>>> list(a.generate_gray())
|
|
['000', '001', '011', '010', '110', '111', '101', '100']
|
|
>>> list(a.generate_gray(start='011'))
|
|
['011', '010', '110', '111', '101', '100']
|
|
>>> list(a.generate_gray(rank=4))
|
|
['110', '111', '101', '100']
|
|
|
|
See Also
|
|
========
|
|
|
|
skip
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] Knuth, D. (2011). The Art of Computer Programming,
|
|
Vol 4, Addison Wesley
|
|
|
|
"""
|
|
bits = self.n
|
|
start = None
|
|
if "start" in hints:
|
|
start = hints["start"]
|
|
elif "rank" in hints:
|
|
start = GrayCode.unrank(self.n, hints["rank"])
|
|
if start is not None:
|
|
self._current = start
|
|
current = self.current
|
|
graycode_bin = gray_to_bin(current)
|
|
if len(graycode_bin) > self.n:
|
|
raise ValueError('Gray code start has length %i but should '
|
|
'not be greater than %i' % (len(graycode_bin), bits))
|
|
self._current = int(current, 2)
|
|
graycode_int = int(''.join(graycode_bin), 2)
|
|
for i in range(graycode_int, 1 << bits):
|
|
if self._skip:
|
|
self._skip = False
|
|
else:
|
|
yield self.current
|
|
bbtc = (i ^ (i + 1))
|
|
gbtc = (bbtc ^ (bbtc >> 1))
|
|
self._current = (self._current ^ gbtc)
|
|
self._current = 0
|
|
|
|
def skip(self):
|
|
"""
|
|
Skips the bit generation.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics import GrayCode
|
|
>>> a = GrayCode(3)
|
|
>>> for i in a.generate_gray():
|
|
... if i == '010':
|
|
... a.skip()
|
|
... print(i)
|
|
...
|
|
000
|
|
001
|
|
011
|
|
010
|
|
111
|
|
101
|
|
100
|
|
|
|
See Also
|
|
========
|
|
|
|
generate_gray
|
|
"""
|
|
self._skip = True
|
|
|
|
@property
|
|
def rank(self):
|
|
"""
|
|
Ranks the Gray code.
|
|
|
|
A ranking algorithm determines the position (or rank)
|
|
of a combinatorial object among all the objects w.r.t.
|
|
a given order. For example, the 4 bit binary reflected
|
|
Gray code (BRGC) '0101' has a rank of 6 as it appears in
|
|
the 6th position in the canonical ordering of the family
|
|
of 4 bit Gray codes.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics import GrayCode
|
|
>>> a = GrayCode(3)
|
|
>>> list(a.generate_gray())
|
|
['000', '001', '011', '010', '110', '111', '101', '100']
|
|
>>> GrayCode(3, start='100').rank
|
|
7
|
|
>>> GrayCode(3, rank=7).current
|
|
'100'
|
|
|
|
See Also
|
|
========
|
|
|
|
unrank
|
|
|
|
References
|
|
==========
|
|
|
|
.. [1] https://web.archive.org/web/20200224064753/http://statweb.stanford.edu/~susan/courses/s208/node12.html
|
|
|
|
"""
|
|
if self._rank is None:
|
|
self._rank = int(gray_to_bin(self.current), 2)
|
|
return self._rank
|
|
|
|
@property
|
|
def current(self):
|
|
"""
|
|
Returns the currently referenced Gray code as a bit string.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics import GrayCode
|
|
>>> GrayCode(3, start='100').current
|
|
'100'
|
|
"""
|
|
rv = self._current or '0'
|
|
if not isinstance(rv, str):
|
|
rv = bin(rv)[2:]
|
|
return rv.rjust(self.n, '0')
|
|
|
|
@classmethod
|
|
def unrank(self, n, rank):
|
|
"""
|
|
Unranks an n-bit sized Gray code of rank k. This method exists
|
|
so that a derivative GrayCode class can define its own code of
|
|
a given rank.
|
|
|
|
The string here is generated in reverse order to allow for tail-call
|
|
optimization.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics import GrayCode
|
|
>>> GrayCode(5, rank=3).current
|
|
'00010'
|
|
>>> GrayCode.unrank(5, 3)
|
|
'00010'
|
|
|
|
See Also
|
|
========
|
|
|
|
rank
|
|
"""
|
|
def _unrank(k, n):
|
|
if n == 1:
|
|
return str(k % 2)
|
|
m = 2**(n - 1)
|
|
if k < m:
|
|
return '0' + _unrank(k, n - 1)
|
|
return '1' + _unrank(m - (k % m) - 1, n - 1)
|
|
return _unrank(rank, n)
|
|
|
|
|
|
def random_bitstring(n):
|
|
"""
|
|
Generates a random bitlist of length n.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics.graycode import random_bitstring
|
|
>>> random_bitstring(3) # doctest: +SKIP
|
|
100
|
|
"""
|
|
return ''.join([random.choice('01') for i in range(n)])
|
|
|
|
|
|
def gray_to_bin(bin_list):
|
|
"""
|
|
Convert from Gray coding to binary coding.
|
|
|
|
We assume big endian encoding.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics.graycode import gray_to_bin
|
|
>>> gray_to_bin('100')
|
|
'111'
|
|
|
|
See Also
|
|
========
|
|
|
|
bin_to_gray
|
|
"""
|
|
b = [bin_list[0]]
|
|
for i in range(1, len(bin_list)):
|
|
b += str(int(b[i - 1] != bin_list[i]))
|
|
return ''.join(b)
|
|
|
|
|
|
def bin_to_gray(bin_list):
|
|
"""
|
|
Convert from binary coding to gray coding.
|
|
|
|
We assume big endian encoding.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics.graycode import bin_to_gray
|
|
>>> bin_to_gray('111')
|
|
'100'
|
|
|
|
See Also
|
|
========
|
|
|
|
gray_to_bin
|
|
"""
|
|
b = [bin_list[0]]
|
|
for i in range(1, len(bin_list)):
|
|
b += str(int(bin_list[i]) ^ int(bin_list[i - 1]))
|
|
return ''.join(b)
|
|
|
|
|
|
def get_subset_from_bitstring(super_set, bitstring):
|
|
"""
|
|
Gets the subset defined by the bitstring.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics.graycode import get_subset_from_bitstring
|
|
>>> get_subset_from_bitstring(['a', 'b', 'c', 'd'], '0011')
|
|
['c', 'd']
|
|
>>> get_subset_from_bitstring(['c', 'a', 'c', 'c'], '1100')
|
|
['c', 'a']
|
|
|
|
See Also
|
|
========
|
|
|
|
graycode_subsets
|
|
"""
|
|
if len(super_set) != len(bitstring):
|
|
raise ValueError("The sizes of the lists are not equal")
|
|
return [super_set[i] for i, j in enumerate(bitstring)
|
|
if bitstring[i] == '1']
|
|
|
|
|
|
def graycode_subsets(gray_code_set):
|
|
"""
|
|
Generates the subsets as enumerated by a Gray code.
|
|
|
|
Examples
|
|
========
|
|
|
|
>>> from sympy.combinatorics.graycode import graycode_subsets
|
|
>>> list(graycode_subsets(['a', 'b', 'c']))
|
|
[[], ['c'], ['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], \
|
|
['a', 'c'], ['a']]
|
|
>>> list(graycode_subsets(['a', 'b', 'c', 'c']))
|
|
[[], ['c'], ['c', 'c'], ['c'], ['b', 'c'], ['b', 'c', 'c'], \
|
|
['b', 'c'], ['b'], ['a', 'b'], ['a', 'b', 'c'], ['a', 'b', 'c', 'c'], \
|
|
['a', 'b', 'c'], ['a', 'c'], ['a', 'c', 'c'], ['a', 'c'], ['a']]
|
|
|
|
See Also
|
|
========
|
|
|
|
get_subset_from_bitstring
|
|
"""
|
|
for bitstring in list(GrayCode(len(gray_code_set)).generate_gray()):
|
|
yield get_subset_from_bitstring(gray_code_set, bitstring)
|