Example #1
0
 def test_frozen_bag_bag(self):
     bag = self.bag_type('meeeow')
     assert bag.frozen_bag_bag == \
                               nifty_collections.FrozenBagBag({3: 1, 1: 3,})
     if not isinstance(bag, collections.Hashable):
         bag['o'] += 2
         assert bag.frozen_bag_bag == \
                               nifty_collections.FrozenBagBag({3: 2, 1: 2,})
Example #2
0
    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)
Example #3
0
def calculate_length_of_recurrent_comb_space(k, fbb):
    '''
    Calculate the length of a recurrent `CombSpace`.

    `k` is the `n_elements` of the space, i.e. the length of each perm. `fbb`
    is the space's `FrozenBagBag`, meaning a bag where each key is the number
    of recurrences of an item and each count is the number of different items
    that have this number of recurrences. (See documentation of `FrozenBagBag`
    for more info.)

    It's assumed that the space is not fixed, not degreed and not sliced.
    '''
    cache = _length_of_recurrent_comb_space_cache
    if not isinstance(fbb, nifty_collections.FrozenBagBag):
        fbb = nifty_collections.FrozenBagBag(fbb)
    ### Checking for edge cases: ##############################################
    #                                                                         #
    if k == 0:
        return 1
    elif k == 1:
        assert fbb
        # (Works because `FrozenBagBag` has a functioning `__bool__`,
        # unlike Python's `Counter`.)
        return fbb.n_elements
    #                                                                         #
    ### Finished checking for edge cases. #####################################

    try:
        return cache[(k, fbb)]
    except KeyError:
        pass

    # This is a 2-phase algorithm, similar to recursion but not really
    # recursion since we don't want to abuse the stack.
    #
    # In the first phase, we get all the sub-FBBs that we need to solve for to
    # get a solution for this FBB, and then for these sub-FBBs we get the
    # sub-sub-FBBs we need to solve in order to solve them, and we continue
    # until we reach trivial FBBs.
    #
    # In the second phase, we'll go over the levels of FBBs, starting with the
    # simplest ones and making our way up to the original FBB. The simplest
    # FBBs will be solved trivially, and then as they get progressively more
    # complex, each FBB will be solved using the solutions of its sub-FBB.
    # Every solution will be stored in the global cache.

    ### Doing phase one, getting all sub-FBBs: ################################
    #                                                                         #
    levels = []
    current_fbbs = {fbb}
    while len(levels) < k and current_fbbs:
        k_ = k - len(levels)
        levels.append({
            fbb_: fbb_.get_sub_fbbs_for_one_key_and_previous_piles_removed()
            for fbb_ in current_fbbs if (k_, fbb_) not in cache
        })
        current_fbbs = set(itertools.chain(*levels[-1].values()))
    #                                                                         #
    ### Finished doing phase one, getting all sub-FBBs. #######################

    ### Doing phase two, solving FBBs from trivial to complex: ################
    #                                                                         #
    for k_, level in enumerate(reversed(levels), (k - len(levels) + 1)):
        if k_ == 1:
            for fbb_, sub_fbbs in level.items():
                cache[(k_, fbb_)] = len(sub_fbbs)
        else:
            for fbb_, sub_fbbs in level.items():
                cache[(k_, fbb_)] = sum(
                    (cache[(k_ - 1, sub_fbb)] for sub_fbb in sub_fbbs))
    #                                                                         #
    ### Finished doing phase two, solving FBBs from trivial to complex. #######

    return cache[(k, fbb)]