class ConstrainedNodes(Serializeable): def __init__(self, nodes: t.Iterable[ConstrainedNode]): self._nodes = FrozenMultiset(nodes) def serialize(self) -> serialization_model: return { 'nodes': self._nodes } @classmethod def deserialize(cls, value: serialization_model, inflator: Inflator) -> 'ConstrainedNodes': return cls( nodes = ( ConstrainedNode.deserialize(node, inflator) for node in value['nodes'] ) ) def __iter__(self) -> t.Iterable[ConstrainedNode]: return self._nodes.__iter__() def __hash__(self) -> int: return hash(self._nodes) def __eq__(self, other: object) -> bool: return ( isinstance(other, self.__class__) and self._nodes == other._nodes ) def __repr__(self) -> str: return '{}({})'.format( self.__class__.__name__, self._nodes, )
class BaseCube(BaseCubeableCollection, PersistentHashable, t.Generic[C, M, T, I, P, L]): def __init__( self, cubeables: t.Union[t.Iterable[C], t.Iterable[t.Tuple[C, int]], t.Mapping[C, int], None] = None, ): self._cubeables = FrozenMultiset() if cubeables is None else FrozenMultiset(cubeables) self._models: t.Optional[FrozenMultiset[M]] = None self._traps: t.Optional[FrozenMultiset[T]] = None self._garbage_traps: t.Optional[FrozenMultiset[T]] = None self._tickets: t.Optional[FrozenMultiset[I]] = None self._purples: t.Optional[FrozenMultiset[P]] = None self._laps: t.Optional[FrozenMultiset[L]] = None @property def items(self) -> t.Iterable[C]: return self._cubeables @property def models(self) -> FrozenMultiset[M]: if self._models is None: self._models = FrozenMultiset( cubeable for cubeable in self._cubeables if isinstance(cubeable, OrpBase) ) return self._models @property def cubeables(self) -> FrozenMultiset[C]: return self._cubeables @property def traps(self) -> FrozenMultiset[T]: if self._traps is None: self._traps = FrozenMultiset( cubeable for cubeable in self._cubeables if isinstance(cubeable, BaseTrap) ) return self._traps @property def garbage_traps(self) -> FrozenMultiset[T]: if self._garbage_traps is None: self._garbage_traps = FrozenMultiset( cubeable for cubeable in self._cubeables if ( isinstance(cubeable, BaseTrap) and cubeable.intention_type == IntentionType.GARBAGE ) ) return self._garbage_traps @property def tickets(self) -> FrozenMultiset[I]: if self._tickets is None: self._tickets = FrozenMultiset( cubeable for cubeable in self._cubeables if isinstance(cubeable, BaseTicket) ) return self._tickets @property def purples(self) -> FrozenMultiset[P]: if self._purples is None: self._purples = FrozenMultiset( cubeable for cubeable in self._cubeables if isinstance(cubeable, BasePurple) ) return self._purples @property def laps(self) -> FrozenMultiset[L]: if self._laps is None: self._laps = FrozenMultiset( cubeable for cubeable in self._cubeables if isinstance(cubeable, BaseLap) ) return self._laps @property def all_models(self) -> t.Iterator[M]: for model in self.models: yield model yield from self.garbage_models @property def garbage_models(self) -> t.Iterator[M]: for trap in self.traps: yield from trap for ticket in self.tickets: yield from ticket def filter(self: B, pattern: Pattern[M]) -> B: return self.__class__( cubeables = ( cubeable for cubeable in self._cubeables if ( isinstance(cubeable, OrpBase) and pattern.match(cubeable) ) or ( isinstance(cubeable, t.Iterable) and any(pattern.matches(cubeable)) ) ), ) def scale(self, amount: int) -> BaseCube: current_size = len(self) if not current_size: raise ValueError('cannot scale empty cube') remaining = amount - current_size if remaining <= 0: return self factor = (amount / current_size) - 1 additionals: Multiset[Cubeable] = Multiset() factored = OrderedDict() for cubeable, multiplicity in self.cubeables.items(): amount = multiplicity * factor whole = int(amount) if whole: additionals.add(cubeable, whole) remainder = amount - whole if remainder: factored[cubeable] = remainder s = sum(factored.values()) return self + self.__class__(additionals) + ( self.__class__( choice( list(factored.keys()), remaining - len(additionals), replace = False, p = [v / s for v in factored.values()], ) ) if s else self.__class__() ) def __iter__(self) -> t.Iterator[C]: return self._cubeables.__iter__() def __len__(self) -> int: return len(self._cubeables) @abstractmethod def serialize(self) -> serialization_model: pass @classmethod @abstractmethod def deserialize(cls, value: serialization_model, inflator: Inflator) -> BaseCube: pass def _calc_persistent_hash(self) -> t.Iterable[t.ByteString]: for model in sorted(self.models, key = lambda _printing: _printing.id): yield str(model.id).encode('ASCII') for persistent_hash in sorted( lap.persistent_hash() for lap in self.laps ): yield persistent_hash.encode('ASCII') def __hash__(self) -> int: return hash(self._cubeables) def __eq__(self, other: object) -> bool: return ( isinstance(other, self.__class__) and self._cubeables == other._cubeables ) def __add__(self, other: t.Union[BaseCubeableCollection, t.Iterable[Cubeable]]) -> BaseCube: if isinstance(other, BaseCubeableCollection): return self.__class__( self._cubeables + other.items ) return self.__class__( self._cubeables + other ) def __sub__(self, other: t.Union[BaseCubeableCollection, t.Iterable[Cubeable]]) -> BaseCube: if isinstance(other, BaseCubeableCollection): return self.__class__( self._cubeables - other.items ) return self.__class__( self._cubeables - other ) def __repr__(self) -> str: return f'{self.__class__.__name__}({self.__hash__()})'