def __getitem__(self, i, allow_out_of_range=False): from python_toolbox import sequence_tools if isinstance(i, numbers.Integral): if i < 0: if i < (-self.length) and not allow_out_of_range: raise IndexError i += self.length if 0 <= i < self.length or allow_out_of_range: return self.start + (self.step * i) else: raise IndexError elif i == infinity: if self.length == infinity: return self.stop else: raise IndexError elif i == -infinity: raise IndexError elif isinstance(i, (slice, sequence_tools.CanonicalSlice)): canonical_slice = sequence_tools.CanonicalSlice( i, iterable_or_length=self) if not ((0 <= canonical_slice.start <= self.length) and ((0 <= canonical_slice.stop <= self.length) or (canonical_slice.stop == self.length == infinity))): raise TypeError return CuteRange( self.__getitem__(canonical_slice.start, allow_out_of_range=True), self.__getitem__(canonical_slice.stop, allow_out_of_range=True), self.step * canonical_slice.step) else: raise TypeError
def exhaust(self, i=infinity): ''' Take items from the internal iterators and save them. This will take enough items so we will have `i` items in total, including the items we had before. ''' from python_toolbox import sequence_tools if self.is_exhausted: return elif isinstance(i, int) or i == infinity: exhaustion_point = _convert_index_to_exhaustion_point(i) else: assert isinstance(i, slice) # todo: can be smart and figure out if it's an empty slice and then # not exhaust. canonical_slice = sequence_tools.CanonicalSlice(i) exhaustion_point = max( _convert_index_to_exhaustion_point(canonical_slice.start), _convert_index_to_exhaustion_point(canonical_slice.stop)) if canonical_slice.step > 0: # Compensating for excluded last item: exhaustion_point -= 1 while len(self.collected_data) <= exhaustion_point: try: with self.lock: self.collected_data.append(next(self._iterator)) except StopIteration: self.is_exhausted = True break
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 self.indices), 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)
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.abc.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.abc.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.abc.Callable): fixed_map = {item: fixed_map(item) for item in self.sequence} else: fixed_map = dict(fixed_map) if fixed_map: self.fixed_map = { 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