Source code for gameanalysis.collect

"""Module with useful collections for game analysis"""
import bisect

import numpy as np

from gameanalysis import utils


[docs]def mcces(thresh, iterable=()): """Create a new minimum connected component set""" mset = _MinimumConnectedComponentElementSet(thresh) for vector, weight in iterable: mset.add(vector, weight) return mset
class _MinimumConnectedComponentElementSet(object): """A class for returning vectors with the minimum weight Vectors are only returned if they have the minimum weight in their connected component, where two vectors are connected if they're closer than `thresh` distance apart. Inserts can take up to `O(n)` where `n` is the number of elements inserted. If this is problematic, a better data structure will probably be necessary.""" def __init__(self, thresh): self._thresh = thresh ** 2 self._set = [] def _similar(self, ait, bit): """Test if elements are similar""" return sum((ai - bi) ** 2 for ai, bi in zip(ait, bit)) <= self._thresh def add(self, vector, weight): """Add a vector with a weight Returns true if the element is distinct from every element in the container""" vector = tuple(vector) mins = (weight, vector) vecs = [vector] new_set = [] for set_tup in self._set: smin, svecs = set_tup if any(self._similar(vector, v) for v in svecs): mins = min(smin, mins) vecs.extend(svecs) else: new_set.append(set_tup) bisect.insort(new_set, (mins, vecs)) self._set = new_set return len(vecs) == 1 def get(self, vector): """Get the representative vector if contained else None""" vector = tuple(vector) for set_tup in self._set: (_, rep), svecs = set_tup if any(self._similar(vector, v) for v in svecs): return rep return None def clear(self): """Remove all vectors added to the set""" self._set.clear() def __len__(self): return len(self._set) def __contains__(self, vector): return self.get(vector) is not None def __iter__(self): return iter((v, w) for (w, v), _ in self._set) def __repr__(self): return "{}({}, {})".format( self.__class__.__name__[1:], self._thresh, list(self) )
[docs]def bitset(dim, iterable=()): """Create a new bitset""" bits = _BitSet(dim) for bit in iterable: bits.add(bit) return bits
class _BitSet(object): """Set of bitmasks A bitmask is in the set if all of the true bits have been added together. When iterating, all maximal bitsets are returned. An empty bitset still contains 0.""" # This compresses all bitmasks down to the number they are # implicitly, and uses bitwise math to replicate the same functions. def __init__(self, dim): self._masks = [0] self._mask = 2 ** np.arange(dim) def add(self, bitmask): """Add a mask to the bit set""" bitmask = np.asarray(bitmask, bool) if bitmask not in self: # pylint: disable=no-else-return num = bitmask.dot(self._mask) self._masks[:] = [m for m in self._masks if m & ~num] self._masks.append(num) return True else: return False def clear(self): """Clear all bitmasks that were added""" self._masks.clear() self._masks.append(0) def __contains__(self, bitmask): utils.check( bitmask.size == self._mask.size, "can't add bitmasks of different sizes" ) num = bitmask.dot(self._mask) return not all(num & ~m for m in self._masks) def __iter__(self): return ((m // self._mask % 2).astype(bool) for m in self._masks) def __eq__(self, othr): # pylint: disable-msg=protected-access return ( type(self) is type(othr) and self._mask.size == othr._mask.size and frozenset(self._masks) == frozenset(othr._masks) ) def __bool__(self): return len(self._masks) > 1 or bool(self._masks[0] != 0) def __repr__(self): return "{}({!r})".format(self.__class__.__name__[1:], self._masks)