def _create_with_cut_prefix(cls, sequence, domain=None, *, n_elements=None, fixed_map=None, degrees=None, is_combination=False, slice_=None, perm_type=None, shit_set=frozenset()): ''' Create a `PermSpace`, cutting a prefix off the start if possible. This is used internally in `PermSpace.__getitem__` and `PermSpace.index`. It's important to cut off the prefix, especially for `CombSpace` because in such cases it obviates the need for a `fixed_map`, and `CombSpace` doesn't work with `fixed_map`. ''' if degrees is not None: raise NotImplementedError prefix = [] fixed_map = dict(fixed_map) for i in sequence_tools.CuteRange(infinity): try: prefix.append(fixed_map[i]) except KeyError: break else: del fixed_map[i] n_elements -= 1 sequence = list(sequence) for item in prefix: if is_combination: sequence = sequence[sequence.index(item) + 1:] else: sequence[sequence.index(item)] = misc.MISSING_ELEMENT # More efficient than removing the element, we filter these out # later. shit_set = {misc.MISSING_ELEMENT} | shit_set sequence = [item for item in sequence if item not in shit_set] fixed_map = { key - len(prefix): value for key, value in fixed_map.items() } perm_space = cls(sequence, n_elements=n_elements, fixed_map=fixed_map, is_combination=is_combination, slice_=slice_, perm_type=perm_type) perm_space.prefix = tuple(prefix) return perm_space
def __init__(self, iterable_or_length, domain=None, n_elements=None, fixed_map={}, degrees=None, is_combination=False, slice_=None, perm_type=None): self.sequence = tuple(iterable_or_length) if \ isinstance(iterable_or_length, collections.Iterable) else \ sequence_tools.CuteRange(iterable_or_length) self.sequence_length = len(self.sequence) self._sequence_frozen_bag = \ nifty_collections.FrozenBag(self.sequence) self.is_recurrent = len(set(self.sequence)) < len(self.sequence) self.n_elements = n_elements if n_elements is not None else \ len(self.sequence) self.domain = (domain or sequence_tools.CuteRange( self.sequence_length))[:self.n_elements] self.fixed_map = { key: value for key, value in fixed_map.items() if key in self.domain } self.degrees = \ degrees or sequence_tools.CuteRange(self.sequence_length) self.is_combination = is_combination self.is_degreed = (self.degrees != sequence_tools.CuteRange( self.sequence_length)) self.slice_ = slice_ if perm_type is None: self.perm_type = tuple self.is_typed = False else: self.perm_type = FruityTuple self.is_typed = True
def test(): comb_space = CombSpace('dumber', 2) assert isinstance(comb_space, CombSpace) assert isinstance(comb_space, PermSpace) assert comb_space.length == 1 + 2 + 3 + 4 + 5 things_in_comb_space = ( 'du', 'db', 'br', ('d', 'u'), Comb('du', comb_space) ) things_not_in_comb_space = ( 'dx', 'dub', ('d', 'x'), {'d', 'u', 'b'}, Comb('dux', comb_space), Comb('du', CombSpace('other', 2)), {'d', 'u'}, 'ud', 'rb', Comb('bu', comb_space) ) for thing in things_in_comb_space: assert thing in comb_space for thing in things_not_in_comb_space: assert thing not in comb_space assert comb_space.n_unused_elements == 4 assert comb_space.index('du') == 0 assert comb_space.index('er') == comb_space.length - 1 assert comb_space.undapplied == comb_space assert comb_space.unrapplied == CombSpace(6, 2) assert comb_space.unpartialled == CombSpace('dumber', 6) assert comb_space.unpartialled.get_partialled(5) == CombSpace('dumber', 5) assert comb_space.uncombinationed == PermSpace('dumber', n_elements=2) assert comb_space.undegreed == comb_space assert comb_space.unrapplied.get_rapplied(range(10, 70, 10)) == \ CombSpace(range(10, 70, 10), 2) with cute_testing.RaiseAssertor(): comb_space.undapplied.get_dapplied(range(10, 70, 10)) with cute_testing.RaiseAssertor(): comb_space.get_degreed(3) assert comb_space.unfixed == comb_space assert not comb_space.fixed_indices assert comb_space.free_indices == comb_space.free_keys == \ sequence_tools.CuteRange(2) assert comb_space.free_values == 'dumber' comb = comb_space[7] assert type(comb.uncombinationed) is Perm assert tuple(comb) == tuple(comb.uncombinationed) assert comb.is_combination assert not comb.uncombinationed.is_combination assert repr(comb_space) == '''<CombSpace: 'dumber', n_elements=2>''' assert repr(CombSpace(tuple(range(50, 0, -1)), 3)) == \ '''<CombSpace: (50, 49, 48, 47, 46, 45, 44, 43, 42 ... ), n_elements=3>'''
def _iter(self): yielded_candidates = set() for candidate in itertools.permutations(self.sequence, self.n_elements): if candidate in yielded_candidates: continue if any(candidate[self.domain.index(key)] != value for key, value in self.fixed_map.items()): continue if self.is_combination: i = -1 rule_out_because_of_bad_comb_order = False # Until challeneged. for item in candidate: try: i = self.sequence.index(item, i + 1) except ValueError: rule_out_because_of_bad_comb_order = True if rule_out_because_of_bad_comb_order: continue if self.is_degreed: unvisited_items = \ set(sequence_tools.CuteRange(self.sequence_length)) n_cycles = 0 while unvisited_items: starting_item = current_item = next(iter(unvisited_items)) while current_item in unvisited_items: unvisited_items.remove(current_item) current_item = self.sequence.index( candidate[current_item]) if current_item == starting_item: n_cycles += 1 degree = self.sequence_length - n_cycles if degree not in self.degrees: continue yielded_candidates.add(candidate) yield candidate
def test(): huge_perm_space = PermSpace(range(100)) big_perm_space = PermSpace(range(150), fixed_map={ 1: 5, 70: 3, }, degrees=(3, 5)) product_space = ProductSpace((huge_perm_space, big_perm_space)) assert product_space.length == \ huge_perm_space.length * big_perm_space.length (perm_0, perm_1) = product_space[10**10] assert perm_0 in huge_perm_space assert perm_1 in big_perm_space assert (perm_0, perm_1) in product_space assert product_space.index((perm_0, perm_1)) == 10**10 repr(~perm_0) repr(~perm_1) assert (~perm_0, ~perm_1) in product_space assert repr(product_space) == ( '<ProductSpace: 933262154439441526816992388562667004907159682643816214' '685929638952175999932299156089414639761565182862536979208272237582511' '85210916864000000000000000000000000 * 208755412068>') assert product_space assert not ProductSpace(((), )) assert not ProductSpace(((), {})) with cute_testing.RaiseAssertor(IndexError): product_space[product_space.length] with cute_testing.RaiseAssertor(IndexError): product_space[product_space.length + 7] with cute_testing.RaiseAssertor(IndexError): product_space[-product_space.length - 1] with cute_testing.RaiseAssertor(IndexError): product_space[-product_space.length - 100] # In the following asserts, using `CuteRange` rather than `xrange` because # the latter doesn't have a functional `__hash__`. assert set( (ProductSpace( (sequence_tools.CuteRange(4), sequence_tools.CuteRange(3))), ProductSpace( (sequence_tools.CuteRange(4), sequence_tools.CuteRange(3))), ProductSpace((sequence_tools.CuteRange(3), sequence_tools.CuteRange(4))))) == set( (ProductSpace((sequence_tools.CuteRange(4), sequence_tools.CuteRange(3))), ProductSpace((sequence_tools.CuteRange(3), sequence_tools.CuteRange(4))))) assert ProductSpace( (sequence_tools.CuteRange(4), sequence_tools.CuteRange(3))) == ProductSpace( (sequence_tools.CuteRange(4), sequence_tools.CuteRange(3))) assert ProductSpace( (sequence_tools.CuteRange(4), sequence_tools.CuteRange(3))) != \ ProductSpace( (sequence_tools.CuteRange(3), sequence_tools.CuteRange(4)) )
def _iterate_tests(): for variation_selection in \ combi.perming.variations.variation_selection_space: kwargs = {} if variation_selection.is_recurrent and \ not variation_selection.is_rapplied: assert not variation_selection.is_allowed # Can't even test this illogical clash. continue if variation_selection.is_recurrent: iterable_or_length_and_sequence_options = (('abracab', 'abracab'), ((1, 2, 3, 4, 5, 5, 4, 3), (1, 2, 3, 4, 5, 5, 4, 3))) elif variation_selection.is_rapplied: iterable_or_length_and_sequence_options = (([1, 4, 2, 5, 3, 7], (1, 4, 2, 5, 3, 7)), ) else: iterable_or_length_and_sequence_options = (( 7, sequence_tools.CuteRange(7)), (sequence_tools.CuteRange(9), sequence_tools.CuteRange(9))) if variation_selection.is_dapplied: domain_to_cut_options = ('QPONMLKJIHGFEDCBAZYXWVUTSR', [7 + i**2 for i in range(20)]) else: domain_to_cut_options = (NO_ARGUMENT, ) if variation_selection.is_partial: n_elements_options = (1, 2, 5) else: n_elements_options = (NO_ARGUMENT, ) perm_space_type_options = (PermSpace, ) if variation_selection.is_combination: is_combination_options = (True, ) else: is_combination_options = (NO_ARGUMENT, ) if variation_selection.is_fixed: # All fixed maps have key `0` so even if `n_elements=1` the space # will still be fixed. purified_fixed_map_options = ( { 0: 1, 4: 3, }, { 0: 0, 1: -2, -2: -3, }, ) else: purified_fixed_map_options = (NO_ARGUMENT, ) if variation_selection.is_degreed: degrees_options = ( (0, 2, 4, 5), 1, ) else: degrees_options = (NO_ARGUMENT, ) if variation_selection.is_sliced: slice_options = (slice(2, -2), slice(3, 4)) else: slice_options = (NO_ARGUMENT, ) if variation_selection.is_typed: if variation_selection.is_combination: perm_type_options = (FruityComb, ) else: perm_type_options = (FruityPerm, ) else: perm_type_options = (NO_ARGUMENT, ) product_space_ = combi.ProductSpace( ((variation_selection, ), perm_space_type_options, iterable_or_length_and_sequence_options, domain_to_cut_options, n_elements_options, is_combination_options, purified_fixed_map_options, degrees_options, slice_options, perm_type_options)) for i in range(len(product_space_)): fucking_globals = dict(globals()) fucking_globals.update(locals()) yield eval( 'lambda: _check_variation_selection(*product_space_[%s])' % i, fucking_globals, locals())
def _check_variation_selection(variation_selection, perm_space_type, iterable_or_length_and_sequence, domain_to_cut, n_elements, is_combination, purified_fixed_map, degrees, slice_, perm_type): assert isinstance(variation_selection, combi.perming.variations.VariationSelection) kwargs = {} iterable_or_length, sequence = iterable_or_length_and_sequence kwargs['iterable_or_length'] = iterable_or_length sequence_set = set(sequence) if domain_to_cut != NO_ARGUMENT: kwargs['domain'] = actual_domain = domain_to_cut[:len(sequence)] else: actual_domain = sequence_tools.CuteRange(len(sequence)) if n_elements != NO_ARGUMENT: kwargs['n_elements'] = n_elements actual_n_elements = n_elements if (n_elements != NO_ARGUMENT) else 0 if is_combination != NO_ARGUMENT: kwargs['is_combination'] = is_combination if purified_fixed_map != NO_ARGUMENT: kwargs['fixed_map'] = actual_fixed_map = dict( (actual_domain[key], sequence[value]) for key, value in purified_fixed_map.items() if key < len(sequence)) else: actual_fixed_map = {} if variation_selection.is_degreed: kwargs['degrees'] = degrees = (0, 2, 4, 5) if perm_type != NO_ARGUMENT: kwargs['perm_type'] = perm_type try: perm_space = perm_space_type(**kwargs) except (combi.UnallowedVariationSelectionException, TypeError): if not variation_selection.is_allowed: return else: raise if slice_ != NO_ARGUMENT: perm_space = perm_space[slice_] else: if not variation_selection.is_allowed: raise TypeError( "Shouldn't have allowed this `VariationSelection.`") brute_perm_space = BrutePermSpace( slice_=(perm_space.canonical_slice if variation_selection.is_sliced else None), **kwargs) assert perm_space.variation_selection == variation_selection assert perm_space.sequence_length == len(sequence) assert (perm_space.domain == perm_space.sequence) == ( not variation_selection.is_dapplied and not variation_selection.is_rapplied and not variation_selection.is_partial) if perm_space.length: assert perm_space.index(perm_space[-1]) == perm_space.length - 1 assert perm_space.index(perm_space[0]) == 0 if variation_selection.is_partial: assert 0 < perm_space.n_unused_elements == \ len(sequence) - actual_n_elements else: assert perm_space.n_unused_elements == 0 assert perm_space == PermSpace(**kwargs)[perm_space.canonical_slice] assert (not perm_space != PermSpace(**kwargs)[perm_space.canonical_slice]) assert hash(perm_space) == \ hash(PermSpace(**kwargs)[perm_space.canonical_slice]) typed_perm_space = perm_space.get_typed( FruityComb if variation_selection.is_combination else FruityPerm) assert typed_perm_space.is_typed assert variation_selection.is_typed is perm_space.is_typed is \ (perm_space != perm_space.untyped) is (perm_space == typed_perm_space) if perm_space.is_sliced and perm_space.length >= 2: assert perm_space[0] == perm_space.unsliced[2] assert perm_space[1] == perm_space.unsliced[3] assert perm_space[-1] == perm_space.unsliced[-3] assert perm_space[-2] == perm_space.unsliced[-4] assert perm_space.unsliced[0] not in perm_space assert perm_space.unsliced[1] not in perm_space assert perm_space.unsliced[2] in perm_space assert perm_space.unsliced[-1] not in perm_space assert perm_space.unsliced[-2] not in perm_space assert perm_space.unsliced[-3] in perm_space if perm_space: # Making sure that `brute_perm_space` isn't empty: next(iter(brute_perm_space)) # This is crucial otherwise the zip-based loop below won't run and # we'll get the illusion that the tests are running while they're # really not. for i, (perm, brute_perm_tuple) in enumerate( itertools.islice(itertools.izip(perm_space, brute_perm_space), 10)): assert tuple(perm) == brute_perm_tuple assert perm in perm_space assert tuple(perm) in perm_space assert iter(list(perm)) in perm_space assert set(perm) not in perm_space assert isinstance(perm, combi.Perm) assert perm.is_rapplied == variation_selection.is_rapplied assert perm.is_dapplied == variation_selection.is_dapplied assert perm.is_partial == variation_selection.is_partial assert perm.is_combination == variation_selection.is_combination assert perm.is_pure == (not (variation_selection.is_rapplied or variation_selection.is_dapplied or variation_selection.is_partial or variation_selection.is_combination)) assert isinstance(perm, FruityMixin) is variation_selection.is_typed if variation_selection.is_rapplied: assert perm != perm.unrapplied if not variation_selection.is_recurrent: perm.unrapplied == perm_space.unrapplied[i] else: assert perm == perm.unrapplied == perm_space.unrapplied[i] if not variation_selection.is_dapplied: sample_domain = \ 'qwertyasdfgzxcvbyuiophjkl;nm,.'[:len(sequence)] assert perm.apply(sample_domain) == sample_domain * perm assert tuple(sample_domain * perm) == tuple( perm_space.get_rapplied(sample_domain)[i]._perm_sequence) if variation_selection.is_dapplied: assert perm != perm.undapplied == perm_space.undapplied[i] else: assert perm == perm.undapplied == perm_space.undapplied[i] if variation_selection.is_combination: if variation_selection.is_typed: with cute_testing.RaiseAssertor(TypeError): perm.uncombinationed else: assert perm != perm.uncombinationed else: assert perm == perm.uncombinationed if variation_selection.is_combination: if variation_selection.is_typed: assert type(perm) == FruityComb else: assert type(perm) == Comb else: if variation_selection.is_typed: assert type(perm) == FruityPerm else: assert type(perm) == Perm if variation_selection.variations <= set( (perming.variations.Variation.DAPPLIED, perming.variations.Variation.RAPPLIED, perming.variations.Variation.COMBINATION)): assert perm.nominal_perm_space == perm_space assert perm.nominal_perm_space == \ perm_space._nominal_perm_space_of_perms == \ perm_space.unsliced.undegreed.unfixed # Give me your unsliced, your undegreed, your unfixed. if not variation_selection.is_fixed and \ not variation_selection.is_degreed: assert perm_space.index(perm) == i assert type(perm)(iter(perm), perm_space=perm_space) == perm assert type(perm)(perm._perm_sequence, perm_space=perm_space) == perm assert perm.length == perm_space.n_elements if variation_selection.is_partial or variation_selection.is_rapplied \ or variation_selection.is_dapplied: with cute_testing.RaiseAssertor(TypeError): ~perm with cute_testing.RaiseAssertor(TypeError): perm.inverse with cute_testing.RaiseAssertor(TypeError): perm**-1 else: assert ~perm == perm.inverse == perm**-1 assert ~~perm == perm.inverse.inverse == perm == perm**1 assert (perm * ~perm) == (~perm * perm) == \ perm.nominal_perm_space[0] assert isinstance(perm**4, Perm) assert isinstance(perm**-7, Perm) perm_set = set(perm) if variation_selection.is_partial: assert len(perm) == actual_n_elements if variation_selection.is_recurrent: assert perm_set <= sequence_set else: assert perm_set < sequence_set assert len(perm_set) == actual_n_elements else: assert perm_set == sequence_set assert len(perm) == len(sequence) for j, (value, key, (key__, value__)) in enumerate( zip(perm, perm.as_dictoid, perm.items)): assert key == key__ assert value == perm.as_dictoid[key] == value__ assert perm.items[j] == (key, value) if not variation_selection.is_recurrent: assert perm.index(value) == key assert perm[key] == value assert key in perm.domain assert value in perm if variation_selection.is_degreed: assert perm.degree == degrees or perm.degree in degrees elif variation_selection.is_partial: assert perm.degree == NotImplemented else: assert 0 <= perm.degree <= len(sequence) ### Testing neighbors: ################################################ # # if variation_selection.is_combination or \ variation_selection.is_recurrent or variation_selection.is_partial: with cute_testing.RaiseAssertor(NotImplementedError): neighbors = perm.get_neighbors(perm_space=perm_space) else: neighbors = perm.get_neighbors(perm_space=perm_space) if variation_selection.is_degreed and perm.degree in (0, 2): assert not neighbors # No neighbors in this case because they'll have a degree of 1 # or 3 which are excluded. else: if perm_space.length >= 5: # (Guarding against cases of really small spaces where # there aren't any neighbors.) assert neighbors for neigbhor in itertools.islice(neighbors, 0, 10): assert neigbhor in perm_space assert len( cute_iter_tools.zip_non_equal((perm, neigbhor), lazy_tuple=True)) == 2 # # ### Finished testing neighbors. ####################################### perm_repr = repr(perm)
class PermSpace(_VariationRemovingMixin, _VariationAddingMixin, _FixedMapManagingMixin, sequence_tools.CuteSequenceMixin, collections.Sequence): ''' A space of permutations on a sequence. Each item in a `PermSpace` is a `Perm`, i.e. a permutation. This is similar to `itertools.permutations`, except it offers far, far more functionality. The permutations may be accessed by index number, the permutation space can have its range and domain specified, some items can be fixed, and more. Here is the simplest possible `PermSpace`: >>> perm_space = PermSpace(3) <PermSpace: 0..2> >>> perm_space[2] <Perm: (1, 0, 2)> >>> tuple(perm_space) (<Perm: (0, 1, 2)>, <Perm: (0, 2, 1)>, <Perm: (1, 0, 2)>, <Perm: (1, 2, 0)>, <Perm: (2, 0, 1)>, <Perm: (2, 1, 0)>) The members are `Perm` objects, which are sequence-like objects that have extra functionality. (See documentation of `Perm` for more info.) The permutations are generated on-demand, not in advance. This means you can easily create something like `PermSpace(1000)`, which has about 10**2500 permutations in it (a number that far exceeds the number of particles in the universe), in a fraction of a second. You can then fetch by index number any permutation of the 10**2500 permutations in a fraction of a second as well. `PermSpace` allows the creation of various special kinds of permutation spaces. For example, you can specify an integer to `n_elements` to set a permutation length that's smaller than the sequence length. (a.k.a. k-permutaions.) This variation of a `PermSpace` is called "partial" and it's one of 8 different variations, that are listed below. - Rapplied (Range-applied): having an arbitrary sequence as a range. To make one, pass your sequence as the first argument instead of the length. - Dapplied (Domain-applied): having an arbitrary sequence as a domain. To make one, pass a sequence into the `domain` argument. - Recurrent: If you provide a sequence (making the space rapplied) and that sequence has repeating items, you've made a recurrent `PermSpace`. It'll be shorter because all of the copies of same item will be considered the same item. (Though they will appear more than once, according to their count in the sequence.) - Fixed: Having a specified number of indices always pointing at certain values, making the space smaller. To make one, pass a dict from each key to the value it should be fixed to as the argument `fixed_map`. - Sliced: A perm space can be sliced like any Python sequence (except you can't change the step.) To make one, use slice notation on an existing perm space, e.g. `perm_space[56:100]`. - Degreed: A perm space can be limited to perms of a certain degree. (A perm's degree is the number of transformations it takes to make it.) To make one, pass into the `degrees` argument either a single degree (like `5`) or a tuple of different degrees (like `(1, 3, 7)`) - Partial: A perm space can be partial, in which case not all elements are used in perms. E.g. you can have a perm space of a sequence of length 5 but with `n_elements=3`, so every perm will have only 3 items. (These are usually called "k-permutations" in math-land.) To make one, pass a number as the argument `n_elements`. - Combination: If you pass in `is_combination=True` or use the subclass `CombSpace`, then you'll have a space of combinations (`Comb`s) instead of perms. `Comb`s are like `Perm``s except there's no order to the elements. (They are always forced into canonical order.) - Typed: If you pass in a perm subclass as `perm_type`, you'll get a typed `PermSpace`, meaning that the perms will use the class you provide rather than the default `Perm`. This is useful when you want to provide extra functionality on top of `Perm` that's specific to your use case. Most of these variations can be used in conjuction with each other, but some cannot. (See `variation_clashes` in `variations.py` for a list of clashes.) For each of these variations, there's a function to make a perm space have that variation and get rid of it. For example, if you want to make a normal perm space be degreed, call `.get_degreed()` on it with the desired degrees. If you want to make a degreed perm space non-degreed, access its `.undegreed` property. The same is true for all other variations. A perm space that has none of these variations is called pure. ''' __metaclass__ = PermSpaceType @classmethod def coerce(cls, argument): '''Make `argument` into something of class `cls` if it isn't.''' if isinstance(argument, cls): return argument else: return cls(argument) @misc_tools.limit_positional_arguments(3) def __init__(self, iterable_or_length, n_elements=None, domain=None, fixed_map=None, degrees=None, is_combination=False, slice_=None, perm_type=None): ### Making basic argument checks: ##################################### # # assert isinstance(iterable_or_length, (collections.Iterable, numbers.Integral)) if isinstance(iterable_or_length, numbers.Integral): assert iterable_or_length >= 0 if slice_ is not None: assert isinstance(slice_, (slice, sequence_tools.CanonicalSlice)) if slice_.step not in (1, None): raise NotImplementedError assert isinstance(n_elements, numbers.Integral) or n_elements is None assert isinstance(is_combination, bool) # # ### Finished making basic argument checks. ############################ ### Figuring out sequence and whether space is rapplied: ############## # # if isinstance(iterable_or_length, numbers.Integral): self.is_rapplied = False self.sequence = sequence_tools.CuteRange(iterable_or_length) self.sequence_length = iterable_or_length else: assert isinstance(iterable_or_length, collections.Iterable) self.sequence = sequence_tools. \ ensure_iterable_is_immutable_sequence(iterable_or_length) range_candidate = sequence_tools.CuteRange(len(self.sequence)) self.is_rapplied = not (cute_iter_tools.are_equal( self.sequence, range_candidate)) self.sequence_length = len(self.sequence) if not self.is_rapplied: self.sequence = sequence_tools.CuteRange(self.sequence_length) # # ### Finished figuring out sequence and whether space is rapplied. ##### ### Figuring out whether sequence is recurrent: ####################### # # if self.is_rapplied: self.is_recurrent = any( count >= 2 for count in self._frozen_ordered_bag.values()) else: self.is_recurrent = False # # ### Finished figuring out whether sequence is recurrent. ############## ### Figuring out number of elements: ################################## # # self.n_elements = self.sequence_length if (n_elements is None) \ else n_elements if not isinstance(self.n_elements, int): raise TypeError('`n_elements` must be an `int`.') if not self.n_elements >= 0: raise TypeError('`n_elements` must be positive or zero.') self.is_partial = (self.n_elements != self.sequence_length) self.indices = sequence_tools.CuteRange(self.n_elements) # # ### Finished figuring out number of elements. ######################### ### Figuring out whether it's a combination: ########################## # # self.is_combination = is_combination # Well that was quick. # # ### Finished figuring out whether it's a combination. ################# ### Figuring out whether space is dapplied: ########################### # # if domain is None: domain = self.indices domain = \ sequence_tools.ensure_iterable_is_immutable_sequence(domain) if self.is_partial: domain = domain[:self.n_elements] self.is_dapplied = not cute_iter_tools.are_equal(domain, self.indices) if self.is_dapplied: if self.is_combination: raise UnallowedVariationSelectionException({ variations.Variation.DAPPLIED: True, variations.Variation.COMBINATION: True, }) self.domain = domain if len(set(self.domain)) < len(self.domain): raise Exception('The domain must not have repeating elements.') else: self.domain = self.indices self.undapplied = self # # ### Finished figuring out whether space is dapplied. ################## ### Figuring out fixed map: ########################################### # # if fixed_map is None: fixed_map = {} if not isinstance(fixed_map, dict): if isinstance(fixed_map, collections.Callable): fixed_map = dict( (item, fixed_map(item)) for item in self.sequence) else: fixed_map = dict(fixed_map) if fixed_map: self.fixed_map = dict( (key, value) for (key, value) in fixed_map.items() if (key in self.domain) and (value in self.sequence)) else: (self.fixed_map, self.free_indices, self.free_keys, self.free_values) = ({}, self.indices, self.domain, self.sequence) self.is_fixed = bool(self.fixed_map) if self.is_fixed: if not (self.is_dapplied or self.is_rapplied or degrees or slice_ or (n_elements is not None) or self.is_combination): self._just_fixed = self else: self._get_just_fixed = lambda: PermSpace( len(self.sequence), fixed_map=self._undapplied_unrapplied_fixed_map, ) else: if not (self.is_dapplied or self.is_rapplied or degrees or slice_ or (n_elements is not None) or self.is_combination): self._just_fixed = self else: self._get_just_fixed = lambda: PermSpace(len(self.sequence)) # # ### Finished figuring out fixed map. ################################## ### Figuring out degrees: ############################################# # # all_degrees = sequence_tools.CuteRange(self.sequence_length) if degrees is None: degrees = () degrees = sequence_tools.to_tuple(degrees, item_type=int) if (not degrees) or cute_iter_tools.are_equal(degrees, all_degrees): self.is_degreed = False self.degrees = all_degrees else: self.is_degreed = True if self.is_combination: raise UnallowedVariationSelectionException({ variations.Variation.DEGREED: True, variations.Variation.COMBINATION: True, }) if self.is_partial: raise UnallowedVariationSelectionException({ variations.Variation.DEGREED: True, variations.Variation.PARTIAL: True, }) if self.is_recurrent: raise UnallowedVariationSelectionException({ variations.Variation.DEGREED: True, variations.Variation.RECURRENT: True, }) # The space is degreed; we canonicalize `degrees` into a sorted # tuple. self.degrees = tuple( sorted(degree for degree in degrees if degree in all_degrees)) # # ### Finished figuring out degrees. #################################### ### Figuring out slice and length: #################################### # # self.slice_ = slice_ self.canonical_slice = sequence_tools.CanonicalSlice( slice_ or slice(float('inf')), self._unsliced_length) self.length = max( self.canonical_slice.stop - self.canonical_slice.start, 0) self.is_sliced = (self.length != self._unsliced_length) # # ### Finished figuring out slice and length. ########################### ### Figuring out perm type: ########################################### # # self.is_typed = perm_type not in (None, self.default_perm_type) self.perm_type = perm_type if self.is_typed else self.default_perm_type assert issubclass(self.perm_type, Perm) # # ### Finished figuring out perm type. ################################## self.is_pure = not (self.is_rapplied or self.is_fixed or self.is_sliced or self.is_degreed or self.is_partial or self.is_combination or self.is_typed) if self.is_pure: self.purified = self if not self.is_rapplied: self.unrapplied = self if not self.is_recurrent: self.unrecurrented = self if not self.is_partial: self.unpartialled = self if not self.is_combination: self.uncombinationed = self # No need do this for `undapplied`, it's already done above. if not self.is_fixed: self.unfixed = self if not self.is_degreed: self.undegreed = self if not self.is_sliced: self.unsliced = self if not self.is_typed: self.untyped = self __init__.signature = funcsigs.signature(__init__.wrapped) @caching.CachedProperty def _unsliced_length(self): ''' The number of perms in the space, ignoring any slicing. This is used as an interim step in calculating the actual length of the space with the slice taken into account. ''' if self.n_elements > self.sequence_length: return 0 if self.is_degreed: assert not self.is_recurrent and not self.is_partial and \ not self.is_combination return sum( math_tools.abs_stirling( self.sequence_length - len(self.fixed_map), self.sequence_length - degree - self._n_cycles_in_fixed_items_of_just_fixed) for degree in self.degrees) elif self.is_fixed: assert not self.is_degreed and not self.is_combination if self.is_recurrent: return calculate_length_of_recurrent_perm_space( self.n_elements - len(self.fixed_map), nifty_collections.FrozenBagBag( nifty_collections.Bag(self.free_values).values())) else: return math_tools.factorial( len(self.free_indices), start=(len(self.free_indices) - (self.n_elements - len(self.fixed_map)) + 1)) else: assert not self.is_degreed and not self.is_fixed if self.is_recurrent: if self.is_combination: return calculate_length_of_recurrent_comb_space( self.n_elements, self._frozen_bag_bag) else: return calculate_length_of_recurrent_perm_space( self.n_elements, self._frozen_bag_bag) else: return math_tools.factorial( self.sequence_length, start=(self.sequence_length - self.n_elements + 1)) // (math_tools.factorial(self.n_elements) if self.is_combination else 1) # This division is always without a remainder, because math. @caching.CachedProperty def variation_selection(self): ''' The selection of variations that describes this space. For example, a rapplied, recurrent, fixed `PermSpace` will get `<VariationSelection #392: rapplied, recurrent, fixed>`. ''' variation_selection = variations.VariationSelection( filter(None, ( variations.Variation.RAPPLIED if self.is_rapplied else None, variations.Variation.RECURRENT if self.is_recurrent else None, variations.Variation.PARTIAL if self.is_partial else None, variations.Variation.COMBINATION if self.is_combination else None, variations.Variation.DAPPLIED if self.is_dapplied else None, variations.Variation.FIXED if self.is_fixed else None, variations.Variation.DEGREED if self.is_degreed else None, variations.Variation.SLICED if self.is_sliced else None, variations.Variation.TYPED if self.is_typed else None, ))) assert variation_selection.is_allowed return variation_selection @caching.CachedProperty def _frozen_ordered_bag(self): ''' A `FrozenOrderedBag` of the items in this space's sequence. This is useful for recurrent perm-spaces, where some counts would be 2 or higher. ''' return nifty_collections.FrozenOrderedBag(self.sequence) _frozen_bag_bag = caching.CachedProperty( lambda self: self._frozen_ordered_bag.frozen_bag_bag, '''A `FrozenBagBag` of items in this space's sequence.''') def __repr__(self): if self.is_dapplied: domain_repr = repr(self.domain) if len(domain_repr) > 40: domain_repr = \ ''.join((domain_repr[:35], ' ... ', domain_repr[-1])) domain_snippet = '%s => ' % domain_repr else: domain_snippet = '' sequence_repr = self.sequence.short_repr if \ hasattr(self.sequence, 'short_repr') else repr(self.sequence) if len(sequence_repr) > 40: sequence_repr = \ ''.join((sequence_repr[:35], ' ... ', sequence_repr[-1])) fixed_map_repr = repr(self.fixed_map) if len(fixed_map_repr) > 40: fixed_map_repr = ''.join( (fixed_map_repr[:35], ' ... ', fixed_map_repr[-1])) return '<%s: %s%s%s%s%s%s%s>%s' % ( type(self).__name__, domain_snippet, sequence_repr, (', n_elements=%s' % (self.n_elements, )) if self.is_partial else '', ', is_combination=True' if self.is_combination else '', (', fixed_map=%s' % (fixed_map_repr, )) if self.is_fixed else '', (', degrees=%s' % (self.degrees, )) if self.is_degreed else '', (', perm_type=%s' % (self.perm_type.__name__, )) if self.is_typed else '', ('[%s:%s]' % (self.slice_.start, self.slice_.stop)) if self.is_sliced else '') def __getitem__(self, i): if isinstance(i, (slice, sequence_tools.CanonicalSlice)): canonical_slice = sequence_tools.CanonicalSlice( i, self.length, offset=self.canonical_slice.start) return PermSpace(self.sequence, domain=self.domain, n_elements=self.n_elements, fixed_map=self.fixed_map, degrees=self.degrees, is_combination=self.is_combination, slice_=canonical_slice, perm_type=self.perm_type) assert isinstance(i, numbers.Integral) if i <= -1: i += self.length if not (0 <= i < self.length): raise IndexError elif self.is_sliced: return self.unsliced[i + self.canonical_slice.start] elif self.is_dapplied: return self.perm_type(self.undapplied[i], perm_space=self) ####################################################################### elif self.is_degreed: if self.is_rapplied: assert not self.is_recurrent and \ not self.is_partial and not self.is_combination and \ not self.is_dapplied and not self.is_sliced return self.perm_type(map(self.sequence.__getitem__, self.unrapplied[i]), perm_space=self) assert not self.is_rapplied and not self.is_recurrent and \ not self.is_partial and not self.is_combination and \ not self.is_dapplied and not self.is_sliced # If that wasn't an example of asserting one's dominance, I don't # know what is. available_values = list(self.free_values) wip_perm_sequence_dict = dict(self.fixed_map) wip_n_cycles_in_fixed_items = \ self._n_cycles_in_fixed_items_of_just_fixed wip_i = i for j in self.sequence: if j in wip_perm_sequence_dict: continue for unused_value in available_values: candidate_perm_sequence_dict = dict(wip_perm_sequence_dict) candidate_perm_sequence_dict[j] = unused_value ### Checking whether we closed a cycle: ################### # # if j == unused_value: closed_cycle = True else: current = j while True: current = candidate_perm_sequence_dict[current] if current == j: closed_cycle = True break elif current not in candidate_perm_sequence_dict: closed_cycle = False break # # ### Finished checking whether we closed a cycle. ########## candidate_n_cycles_in_fixed_items = \ wip_n_cycles_in_fixed_items + closed_cycle candidate_fixed_perm_space_length = sum( math_tools.abs_stirling( self.sequence_length - len(candidate_perm_sequence_dict), self.sequence_length - degree - candidate_n_cycles_in_fixed_items) for degree in self.degrees) if wip_i < candidate_fixed_perm_space_length: available_values.remove(unused_value) wip_perm_sequence_dict[j] = unused_value wip_n_cycles_in_fixed_items = \ candidate_n_cycles_in_fixed_items break wip_i -= candidate_fixed_perm_space_length else: raise RuntimeError assert wip_i == 0 return self.perm_type( (wip_perm_sequence_dict[k] for k in self.domain), self) ####################################################################### elif self.is_recurrent: assert not self.is_dapplied and not self.is_degreed and \ not self.is_sliced available_values = list(self.sequence) reserved_values = nifty_collections.Bag(self.fixed_map.values()) wip_perm_sequence_dict = dict(self.fixed_map) wip_i = i shit_set = set() for j in range(self.n_elements): if j in self.fixed_map: available_values.remove(self.fixed_map[j]) reserved_values[self.fixed_map[j]] -= 1 continue unused_values = [ item for item in nifty_collections.OrderedBag(available_values) - reserved_values if item not in shit_set ] for unused_value in unused_values: wip_perm_sequence_dict[j] = unused_value candidate_sub_perm_space = \ PermSpace._create_with_cut_prefix( self.sequence, n_elements=self.n_elements, fixed_map=wip_perm_sequence_dict, is_combination=self.is_combination, shit_set=shit_set, perm_type=self.perm_type ) if wip_i < candidate_sub_perm_space.length: available_values.remove(unused_value) break else: wip_i -= candidate_sub_perm_space.length if self.is_combination: shit_set.add(wip_perm_sequence_dict[j]) del wip_perm_sequence_dict[j] else: raise RuntimeError assert wip_i == 0 return self.perm_type( dict_tools.get_tuple(wip_perm_sequence_dict, self.domain), self) ####################################################################### elif self.is_fixed: free_values_perm = self._free_values_unsliced_perm_space[i] free_values_perm_iterator = iter(free_values_perm) return self.perm_type( tuple((self._undapplied_fixed_map[m] if ( m in self.fixed_indices ) else next(free_values_perm_iterator)) for m in range(self.sequence_length)), self) ####################################################################### elif self.is_combination: wip_number = self.length - 1 - i wip_perm_sequence = [] for i in range(self.n_elements, 0, -1): for j in range(self.sequence_length, i - 2, -1): candidate = math_tools.binomial(j, i) if candidate <= wip_number: wip_perm_sequence.append(self.sequence[-(j + 1)]) wip_number -= candidate break else: raise RuntimeError result = tuple(wip_perm_sequence) assert len(result) == self.n_elements return self.perm_type(result, self) ####################################################################### else: factoradic_number = math_tools.to_factoradic( i * math.factorial(self.n_unused_elements), n_digits_pad=self.sequence_length) if self.is_partial: factoradic_number = factoradic_number[:-self.n_unused_elements] unused_numbers = list(self.sequence) result = tuple( unused_numbers.pop(factoradic_digit) for factoradic_digit in factoradic_number) assert sequence_tools.get_length(result) == self.n_elements return self.perm_type(result, self) enumerated_sequence = caching.CachedProperty( lambda self: tuple(enumerate(self.sequence))) n_unused_elements = caching.CachedProperty( lambda self: self.sequence_length - self.n_elements, '''In partial perm spaces, number of elements that aren't used.''') __iter__ = lambda self: (self[i] for i in sequence_tools.CuteRange(self.length)) _reduced = property(lambda self: (type( self), self.sequence, self.domain, tuple(sorted(self.fixed_map.items( ))), self.degrees, self.canonical_slice, self.perm_type)) # (No need to include `n_degrees` because it's implied by `domain`. No need # to include `is_combination` because it's implied by `type(self)`.) __eq__ = lambda self, other: (isinstance(other, PermSpace) and self. _reduced == other._reduced) __ne__ = lambda self, other: not (self == other) __hash__ = lambda self: hash(self._reduced) def index(self, perm): '''Get the index number of permutation `perm` in this space.''' if not isinstance(perm, collections.Iterable): raise ValueError try: perm = sequence_tools.ensure_iterable_is_immutable_sequence( perm, allow_unordered=False) except sequence_tools.UnorderedIterableException: raise ValueError('An unordered iterable is never contained in a ' '`PermSpace`. Try an ordered one.') perm_set = set(perm) if not isinstance(perm, UnrecurrentedPerm) \ else set(perm._perm_sequence) if not (perm_set <= set(self.sequence)): raise ValueError if sequence_tools.get_length(perm) != self.n_elements: raise ValueError if not isinstance(perm, self.perm_type): perm = self.perm_type(perm, self) if self.sequence != perm.nominal_perm_space.sequence: # (This also covers `self.rapplied != perm.rapplied`) raise ValueError if self.domain != perm.domain: # (This also covers `self.dapplied != perm.dapplied`) raise ValueError if self.is_degreed and (perm.degree not in self.degrees): raise ValueError # At this point we know the permutation contains the correct items, and # has the correct degree. if perm.is_dapplied: return self.undapplied.index(perm.undapplied) ####################################################################### elif self.is_degreed: if perm.is_rapplied: return self.unrapplied.index(perm.unrapplied) wip_perm_number = 0 wip_perm_sequence_dict = dict(self.fixed_map) unused_values = list(self.free_values) for i, value in enumerate(perm._perm_sequence): if i in self.fixed_indices: continue unused_values.remove(value) lower_values = [j for j in unused_values if j < value] for lower_value in lower_values: temp_fixed_map = dict(wip_perm_sequence_dict) temp_fixed_map[i] = lower_value wip_perm_number += PermSpace( self.sequence_length, degrees=self.degrees, fixed_map=temp_fixed_map).length wip_perm_sequence_dict[self.domain[i]] = value perm_number = wip_perm_number ####################################################################### elif self.is_recurrent: assert not self.is_degreed and not self.is_dapplied wip_perm_number = 0 unused_values = list(self.sequence) reserved_values = list(self.fixed_map.values()) perm_sequence_list = list(perm._perm_sequence) shit_set = set() for i, value in enumerate(perm._perm_sequence): if i in self.fixed_map: if self.fixed_map[i] == value: unused_values.remove(value) reserved_values.remove(value) continue else: raise ValueError lower_values = [ thing for thing in nifty_collections.OrderedSet(unused_values) if (thing not in reserved_values or unused_values.count( thing) > reserved_values.count(thing)) and unused_values.index(thing) < unused_values.index(value) and thing not in shit_set ] unused_values.remove(value) for lower_value in lower_values: temp_fixed_map = dict( enumerate(perm_sequence_list[:i] + [lower_value])) temp_fixed_map.update(self.fixed_map) candidate_sub_perm_space = \ PermSpace._create_with_cut_prefix( self.sequence, n_elements=self.n_elements, fixed_map=temp_fixed_map, is_combination=self.is_combination, shit_set=shit_set, perm_type=self.perm_type ) wip_perm_number += candidate_sub_perm_space.length if self.is_combination: shit_set.add(lower_value) perm_number = wip_perm_number ####################################################################### elif self.is_fixed: assert not self.is_degreed and not self.is_recurrent free_values_perm_sequence = [] for i, perm_item in zip(self.domain, perm._perm_sequence): if i in self.fixed_map: if self.fixed_map[i] != perm_item: raise ValueError else: free_values_perm_sequence.append(perm_item) # At this point we know all the items that should be fixed are # fixed. perm_number = self._free_values_unsliced_perm_space.index( free_values_perm_sequence) ####################################################################### elif self.is_combination: if perm.is_rapplied: return self.unrapplied.index(perm.unrapplied) assert not self.is_rapplied and not self.is_recurrent and \ not self.is_dapplied and not self.is_fixed and \ not self.is_degreed if not cute_iter_tools.is_sorted(perm._perm_sequence): raise ValueError processed_perm_sequence = tuple( self.sequence_length - 1 - item for item in perm._perm_sequence[::-1]) perm_number = self.unsliced.length - 1 - sum( (math_tools.binomial(item, i) for i, item in enumerate(processed_perm_sequence, start=1)), 0) ####################################################################### else: factoradic_number = [] unused_values = list(self.sequence) for i, value in enumerate(perm._perm_sequence): index_of_current_number = unused_values.index(value) factoradic_number.append(index_of_current_number) unused_values.remove(value) perm_number = math_tools.from_factoradic( factoradic_number + [0] * self.n_unused_elements) // math.factorial( self.n_unused_elements) ####################################################################### if perm_number not in self.canonical_slice: raise ValueError return perm_number - self.canonical_slice.start @caching.CachedProperty def short_length_string(self): '''Short string describing size of space, e.g. "12!"''' if not self.is_recurrent and not self.is_partial and \ not self.is_combination and not self.is_fixed and \ not self.is_sliced: assert self.length == math_tools.factorial(self.sequence_length) return misc.get_short_factorial_string(self.sequence_length) else: return str(self.length) __bool__ = lambda self: bool(self.length) __nonzero__ = __bool__ _domain_set = caching.CachedProperty( lambda self: set(self.domain), '''The set of items in this space's domain.''') def __reduce__(self, *args, **kwargs): ####################################################################### # # self._just_fixed # (Getting this generated because we can't save a lambda.) try: del self._get_just_fixed except AttributeError: pass # # ####################################################################### return super(PermSpace, self).__reduce__(*args, **kwargs) def coerce_perm(self, perm): '''Coerce `perm` to be a permutation of this space.''' return self.perm_type(perm, self) prefix = None @classmethod @misc_tools.limit_positional_arguments(3) def _create_with_cut_prefix(cls, sequence, domain=None, n_elements=None, fixed_map=None, degrees=None, is_combination=False, slice_=None, perm_type=None, shit_set=frozenset()): ''' Create a `PermSpace`, cutting a prefix off the start if possible. This is used internally in `PermSpace.__getitem__` and `PermSpace.index`. It's important to cut off the prefix, especially for `CombSpace` because in such cases it obviates the need for a `fixed_map`, and `CombSpace` doesn't work with `fixed_map`. ''' if degrees is not None: raise NotImplementedError prefix = [] fixed_map = dict(fixed_map) for i in sequence_tools.CuteRange(infinity): try: prefix.append(fixed_map[i]) except KeyError: break else: del fixed_map[i] n_elements -= 1 sequence = list(sequence) for item in prefix: if is_combination: sequence = sequence[sequence.index(item) + 1:] else: sequence[sequence.index(item)] = misc.MISSING_ELEMENT # More efficient than removing the element, we filter these out # later. shit_set = set((misc.MISSING_ELEMENT, )) | shit_set sequence = [item for item in sequence if item not in shit_set] fixed_map = dict( (key - len(prefix), value) for key, value in fixed_map.items()) perm_space = cls(sequence, n_elements=n_elements, fixed_map=fixed_map, is_combination=is_combination, slice_=slice_, perm_type=perm_type) perm_space.prefix = tuple(prefix) return perm_space
def __init__(self, iterable_or_length, n_elements=None, domain=None, fixed_map=None, degrees=None, is_combination=False, slice_=None, perm_type=None): ### Making basic argument checks: ##################################### # # assert isinstance(iterable_or_length, (collections.Iterable, numbers.Integral)) if isinstance(iterable_or_length, numbers.Integral): assert iterable_or_length >= 0 if slice_ is not None: assert isinstance(slice_, (slice, sequence_tools.CanonicalSlice)) if slice_.step not in (1, None): raise NotImplementedError assert isinstance(n_elements, numbers.Integral) or n_elements is None assert isinstance(is_combination, bool) # # ### Finished making basic argument checks. ############################ ### Figuring out sequence and whether space is rapplied: ############## # # if isinstance(iterable_or_length, numbers.Integral): self.is_rapplied = False self.sequence = sequence_tools.CuteRange(iterable_or_length) self.sequence_length = iterable_or_length else: assert isinstance(iterable_or_length, collections.Iterable) self.sequence = sequence_tools. \ ensure_iterable_is_immutable_sequence(iterable_or_length) range_candidate = sequence_tools.CuteRange(len(self.sequence)) self.is_rapplied = not (cute_iter_tools.are_equal( self.sequence, range_candidate)) self.sequence_length = len(self.sequence) if not self.is_rapplied: self.sequence = sequence_tools.CuteRange(self.sequence_length) # # ### Finished figuring out sequence and whether space is rapplied. ##### ### Figuring out whether sequence is recurrent: ####################### # # if self.is_rapplied: self.is_recurrent = any( count >= 2 for count in self._frozen_ordered_bag.values()) else: self.is_recurrent = False # # ### Finished figuring out whether sequence is recurrent. ############## ### Figuring out number of elements: ################################## # # self.n_elements = self.sequence_length if (n_elements is None) \ else n_elements if not isinstance(self.n_elements, int): raise TypeError('`n_elements` must be an `int`.') if not self.n_elements >= 0: raise TypeError('`n_elements` must be positive or zero.') self.is_partial = (self.n_elements != self.sequence_length) self.indices = sequence_tools.CuteRange(self.n_elements) # # ### Finished figuring out number of elements. ######################### ### Figuring out whether it's a combination: ########################## # # self.is_combination = is_combination # Well that was quick. # # ### Finished figuring out whether it's a combination. ################# ### Figuring out whether space is dapplied: ########################### # # if domain is None: domain = self.indices domain = \ sequence_tools.ensure_iterable_is_immutable_sequence(domain) if self.is_partial: domain = domain[:self.n_elements] self.is_dapplied = not cute_iter_tools.are_equal(domain, self.indices) if self.is_dapplied: if self.is_combination: raise UnallowedVariationSelectionException({ variations.Variation.DAPPLIED: True, variations.Variation.COMBINATION: True, }) self.domain = domain if len(set(self.domain)) < len(self.domain): raise Exception('The domain must not have repeating elements.') else: self.domain = self.indices self.undapplied = self # # ### Finished figuring out whether space is dapplied. ################## ### Figuring out fixed map: ########################################### # # if fixed_map is None: fixed_map = {} if not isinstance(fixed_map, dict): if isinstance(fixed_map, collections.Callable): fixed_map = dict( (item, fixed_map(item)) for item in self.sequence) else: fixed_map = dict(fixed_map) if fixed_map: self.fixed_map = dict( (key, value) for (key, value) in fixed_map.items() if (key in self.domain) and (value in self.sequence)) else: (self.fixed_map, self.free_indices, self.free_keys, self.free_values) = ({}, self.indices, self.domain, self.sequence) self.is_fixed = bool(self.fixed_map) if self.is_fixed: if not (self.is_dapplied or self.is_rapplied or degrees or slice_ or (n_elements is not None) or self.is_combination): self._just_fixed = self else: self._get_just_fixed = lambda: PermSpace( len(self.sequence), fixed_map=self._undapplied_unrapplied_fixed_map, ) else: if not (self.is_dapplied or self.is_rapplied or degrees or slice_ or (n_elements is not None) or self.is_combination): self._just_fixed = self else: self._get_just_fixed = lambda: PermSpace(len(self.sequence)) # # ### Finished figuring out fixed map. ################################## ### Figuring out degrees: ############################################# # # all_degrees = sequence_tools.CuteRange(self.sequence_length) if degrees is None: degrees = () degrees = sequence_tools.to_tuple(degrees, item_type=int) if (not degrees) or cute_iter_tools.are_equal(degrees, all_degrees): self.is_degreed = False self.degrees = all_degrees else: self.is_degreed = True if self.is_combination: raise UnallowedVariationSelectionException({ variations.Variation.DEGREED: True, variations.Variation.COMBINATION: True, }) if self.is_partial: raise UnallowedVariationSelectionException({ variations.Variation.DEGREED: True, variations.Variation.PARTIAL: True, }) if self.is_recurrent: raise UnallowedVariationSelectionException({ variations.Variation.DEGREED: True, variations.Variation.RECURRENT: True, }) # The space is degreed; we canonicalize `degrees` into a sorted # tuple. self.degrees = tuple( sorted(degree for degree in degrees if degree in all_degrees)) # # ### Finished figuring out degrees. #################################### ### Figuring out slice and length: #################################### # # self.slice_ = slice_ self.canonical_slice = sequence_tools.CanonicalSlice( slice_ or slice(float('inf')), self._unsliced_length) self.length = max( self.canonical_slice.stop - self.canonical_slice.start, 0) self.is_sliced = (self.length != self._unsliced_length) # # ### Finished figuring out slice and length. ########################### ### Figuring out perm type: ########################################### # # self.is_typed = perm_type not in (None, self.default_perm_type) self.perm_type = perm_type if self.is_typed else self.default_perm_type assert issubclass(self.perm_type, Perm) # # ### Finished figuring out perm type. ################################## self.is_pure = not (self.is_rapplied or self.is_fixed or self.is_sliced or self.is_degreed or self.is_partial or self.is_combination or self.is_typed) if self.is_pure: self.purified = self if not self.is_rapplied: self.unrapplied = self if not self.is_recurrent: self.unrecurrented = self if not self.is_partial: self.unpartialled = self if not self.is_combination: self.uncombinationed = self # No need do this for `undapplied`, it's already done above. if not self.is_fixed: self.unfixed = self if not self.is_degreed: self.undegreed = self if not self.is_sliced: self.unsliced = self if not self.is_typed: self.untyped = self
def test_perm_spaces(): pure_0a = PermSpace(4) pure_0b = PermSpace(range(4)) pure_0c = PermSpace(list(range(4))) pure_0d = PermSpace(iter(range(4))) assert pure_0a == pure_0b == pure_0c == pure_0d assert len(pure_0a) == len(pure_0b) == len(pure_0c) == len(pure_0d) assert repr(pure_0a) == repr(pure_0b) == repr(pure_0c) == \ repr(pure_0d) == '<PermSpace: 0..3>' assert repr(PermSpace(sequence_tools.CuteRange(3, 7))) == \ '<PermSpace: 3..6>' assert repr(PermSpace(sequence_tools.CuteRange(3, 7, 2))) == \ '<PermSpace: CuteRange(3, 7, 2)>' assert repr(PermSpace(tuple(sequence_tools.CuteRange(3, 7, 2)))) == \ '<PermSpace: (3, 5)>' assert cute_iter_tools.are_equal(pure_0a, pure_0b, pure_0c, pure_0d) assert set(map(bool, (pure_0a, pure_0b, pure_0c, pure_0d))) == {True} pure_perm_space = pure_0a assert pure_0a.is_pure assert not pure_0a.is_rapplied assert not pure_0a.is_dapplied assert not pure_0a.is_fixed assert not pure_0a.is_sliced first_perm = pure_0a[0] some_perm = pure_0a[7] last_perm = pure_0a[-1] assert first_perm.index(2) == 2 assert first_perm.index(0) == 0 with cute_testing.RaiseAssertor(ValueError): first_perm.index(5) assert last_perm.apply('meow') == 'woem' assert last_perm.apply('meow', str) == 'woem' assert last_perm.apply('meow', tuple) == tuple('woem') with cute_testing.RaiseAssertor(IndexError): pure_0a[- pure_0a.length - 1] with cute_testing.RaiseAssertor(IndexError): pure_0a[- pure_0a.length - 2] with cute_testing.RaiseAssertor(IndexError): pure_0a[- pure_0a.length - 30] with cute_testing.RaiseAssertor(IndexError): pure_0a[pure_0a.length] with cute_testing.RaiseAssertor(IndexError): pure_0a[pure_0a.length + 1] with cute_testing.RaiseAssertor(IndexError): pure_0a[pure_0a.length + 2] with cute_testing.RaiseAssertor(IndexError): pure_0a[pure_0a.length + 300] with cute_testing.RaiseAssertor(): pure_0a[24] assert pure_0a.take_random() in pure_0c # Testing hashing: pure_perm_space_dict = {pure_0a: 'a', pure_0b: 'b', pure_0c: 'c', pure_0d: 'd',} (single_value,) = pure_perm_space_dict.values() assert len(pure_perm_space_dict) == 1 # They're all the same assert pure_perm_space_dict[pure_0a] == pure_perm_space_dict[pure_0b] == \ pure_perm_space_dict[pure_0c] == pure_perm_space_dict[pure_0d] == \ single_value assert None not in pure_0a # Because, damn. assert PermSpace('meow')[0] not in pure_0a assert type(first_perm) == type(some_perm) == type(last_perm) == Perm assert set(some_perm) == set(range(4)) assert tuple(first_perm) == (0, 1, 2, 3) assert tuple(last_perm) == (3, 2, 1, 0) assert Perm.coerce(first_perm) == first_perm assert Perm.coerce(first_perm, pure_0b) == first_perm assert Perm.coerce(tuple(first_perm)) == first_perm assert Perm.coerce(list(first_perm)) == first_perm assert Perm.coerce(tuple(first_perm), pure_0a) == first_perm assert Perm.coerce(list(first_perm), pure_0b) == first_perm assert Perm.coerce(tuple(first_perm), PermSpace(5, n_elements=4)) != \ first_perm assert isinstance(first_perm.items, combi.perming.perm.PermItems) assert first_perm.items[2] == (2, 2) assert repr(first_perm.items) == '<PermItems: %s>' % repr(first_perm) assert isinstance(first_perm.as_dictoid, combi.perming.perm.PermAsDictoid) assert first_perm.as_dictoid[2] == 2 assert dict(first_perm.as_dictoid) == {0: 0, 1: 1, 2: 2, 3: 3} assert not (first_perm != first_perm) assert first_perm == first_perm assert first_perm assert tuple({pure_0a[4]: 1, pure_0b[4]: 2, pure_0c[4]: 3,}.keys()) == \ (pure_0d[4], ) assert some_perm.inverse == ~ some_perm assert ~ ~ some_perm == some_perm assert first_perm in pure_perm_space assert set(first_perm) not in pure_perm_space # No order? Not contained. assert some_perm in pure_perm_space assert last_perm in pure_perm_space assert tuple(first_perm) in pure_perm_space assert list(some_perm) in pure_perm_space assert iter(last_perm) in pure_perm_space assert 'meow' not in pure_perm_space assert (0, 1, 2, 3, 3) not in pure_perm_space assert pure_perm_space.index(first_perm) == 0 assert pure_perm_space.index(last_perm) == \ len(pure_perm_space) - 1 assert pure_perm_space.index(some_perm) == 7 assert 'meow' * Perm((1, 3, 2, 0)) == 'ewom' assert Perm('meow', 'meow') * Perm((1, 3, 2, 0)) == Perm('ewom', 'meow') assert [0, 1, 2, 3] * Perm((0, 1, 2, 3)) == (0, 1, 2, 3) assert Perm((0, 1, 2, 3)) * Perm((0, 1, 2, 3)) == Perm((0, 1, 2, 3)) assert Perm((2, 0, 1, 3)) * Perm((0, 1, 3, 2)) == Perm((2, 0, 3, 1)) assert (Perm((0, 1, 2, 3)) ** (- 2)) == (Perm((0, 1, 2, 3)) ** (- 1)) == \ (Perm((0, 1, 2, 3)) ** (0)) == (Perm((0, 1, 2, 3)) ** (1)) == \ (Perm((0, 1, 2, 3)) ** 2) == (Perm((0, 1, 2, 3)) ** 3) assert set(map(bool, (pure_0a[4:4], pure_0a[3:2]))) == {False} assert pure_0a[2:6][1:-1] == pure_0a[3:5] assert tuple(pure_0a[2:6][1:-1]) == tuple(pure_0a[3:5]) assert pure_0a[2:6][1:-1][1] == pure_0a[3:5][1] assert pure_0a[2:5][1:-1] != pure_0a[3:5] big_perm_space = PermSpace(range(150), fixed_map={1: 5, 70: 3,}, degrees=(3, 5)) assert big_perm_space == PermSpace(range(150), fixed_map={1: 5, 70: 3,}.items(), degrees=(3, 5)) for i in [10**10, 3*11**9-344, 4*12**8-5, 5*3**20+4]: perm = big_perm_space[i] assert big_perm_space.index(perm) == i repr_of_big_perm_space = repr(PermSpace(tuple(range(100, 0, -1)))) assert '...' in repr_of_big_perm_space assert len(repr_of_big_perm_space) <= 100 fixed_perm_space = pure_perm_space.get_fixed({0: 3,}) assert fixed_perm_space.length == 6 assert fixed_perm_space.is_fixed assert not fixed_perm_space.is_pure assert fixed_perm_space.unfixed.is_pure assert fixed_perm_space.unfixed == pure_perm_space assert pickle.loads(pickle.dumps(pure_perm_space)) == pure_perm_space assert pickle.loads(pickle.dumps(pure_0b[2])) == pure_0c[2] assert pickle.loads(pickle.dumps(pure_0b[3])) != pure_0b[4]