Source code for gameanalysis.collect
from collections import abc
import numpy as np
[docs]class WeightedSimilaritySet(object):
"""A set of non-similar elements prioritized by weight
Allows adding a bunch of weighted elements, and when iterated, only
iterates over dissimilar elements with the lowest weights. Adding new
elements that are similar to the existing set, but with higher weights
won't change the set returned."""
def __init__(self, is_similar):
self._is_similar = is_similar
self._i = 0 # Tie breaking
self._items = []
self._set = []
self._computed = True
[docs] def add(self, item, weight):
self._computed = False
self._set.clear()
self._items.append((weight, self._i, item))
self._i += 1
return self
[docs] def clear(self):
self._i = 0
self._items.clear()
self._set.clear()
self._computed = True
def _satisfy(self):
if not self._computed:
self._items.sort()
for w, _, i in self._items:
if all(not self._is_similar(i, j) for j, _ in self._set):
self._set.append((i, w))
self._computed = True
def __len__(self):
self._satisfy()
return len(self._set)
def __iter__(self):
self._satisfy()
return iter(self._set)
def __repr__(self):
self._satisfy()
suffix = ('.add(' + ').add('.join('{}, {}'.format(i, w)
for w, _, i in self._items) + ')'
if self._items else '')
return '{}({}){}'.format(self.__class__.__name__,
self._is_similar.__name__, suffix)
[docs]class DynamicArray(object):
"""A object with a backed array that also allows adding data"""
def __init__(self, item_shape, dtype=None, initial_room=8,
grow_fraction=2):
assert grow_fraction > 1
if not isinstance(item_shape, abc.Sized):
item_shape = (item_shape,)
self._data = np.empty((initial_room,) + tuple(item_shape), dtype)
self._length = 0
self._grow_fraction = 2
[docs] def append(self, array):
"""Append an array"""
array = np.asarray(array, self._data.dtype)
if array.shape[1:] == self._data.shape[1:]:
# Adding multiple
num = array.shape[0]
self.ensure_capacity(self._length + num)
self._data[self._length:self._length + num] = array
self._length += num
elif array.shape == self._data.shape[1:]:
# Adding one
self.ensure_capacity(self._length + 1)
self._data[self._length] = array
self._length += 1
else:
raise ValueError("Invalid shape for append")
[docs] def pop(self, num=None):
"""Pop one or several arrays"""
if num is None:
assert self._length > 0, "can't pop from an empty array"
self._length -= 1
return self._data[self._length].copy()
else:
assert num >= 0 and self._length >= num
self._length -= num
return self._data[self._length:self._length + num].copy()
@property
def data(self):
"""A view of all of the data"""
return self._data[:self._length]
[docs] def ensure_capacity(self, new_capacity):
"""Make sure the array has a least new_capacity"""
if new_capacity > self._data.shape[0]:
growth = round(self._data.shape[0] * self._grow_fraction) + 1
new_size = max(growth, new_capacity)
new_data = np.empty((new_size,) + self._data.shape[1:],
self._data.dtype)
new_data[:self._length] = self._data[:self._length]
self._data = new_data
[docs] def compact(self):
"""Trim underlying storage to hold only valid data"""
self._data = self.data.copy()
self._length = self._data.shape[0]
def __iter__(self):
return iter(self.data)
def __len__(self):
return self._length
def __str__(self):
return str(self.data)
def __repr__(self):
return repr(self.data)
[docs]class BitSet(object):
"""Set of bitmasks
A bitmask is in the set if all of the true bits have been added"""
# This compresses all bitmasks down to the number they are implicitly, and
# uses bitwise math to replicate the same functions.
def __init__(self):
self._masks = []
[docs] def add(self, bitmask):
if not self._masks:
self._mask = 2 ** np.arange(bitmask.size)
if bitmask not in self:
num = bitmask.dot(self._mask)
self._masks[:] = [m for m in self._masks if not m & ~num]
self._masks.append(num)
return True
else:
return False
[docs] def clear(self):
self._masks.clear()
def __contains__(self, bitmask):
assert 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 __bool__(self):
return bool(self._masks)
def __repr__(self):
return '{}({!r})'.format(self.__class__.__name__, self._masks)
[docs]class MixtureSet(object):
"""A set of mixtures
Elements are only kept if the norm of their difference is greater than the
tolerance."""
def __init__(self, tolerance):
self._tol = tolerance ** 2
self._mixtures = []
[docs] def add(self, mixture):
if mixture not in self:
self._mixtures.append(mixture)
return True
else:
return False
[docs] def clear(self):
self._mixtures.clear()
def __contains__(self, mix):
return any(np.dot(m - mix, m - mix) <= self._tol for m in self)
def __bool__(self):
return bool(self._mixtures)
def __len__(self):
return len(self._mixtures)
def __iter__(self):
return iter(self._mixtures)
def __repr__(self):
return '{}({!r})'.format(self.__class__.__name__, self._mixtures)