Пример #1
0
def test_nC_nP_nT():
    from sympy.utilities.iterables import (multiset_permutations,
                                           multiset_combinations,
                                           multiset_partitions, partitions,
                                           subsets, permutations)
    from sympy.functions.combinatorial.numbers import (nP, nC, nT, stirling,
                                                       _multiset_histogram,
                                                       _AOP_product)
    from sympy.combinatorics.permutations import Permutation
    from sympy.core.numbers import oo
    from random import choice

    c = string.ascii_lowercase
    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(8):
                check = nP(s, i)
                tot += check
                assert len(list(multiset_permutations(s, i))) == check
                if u:
                    assert nP(len(s), i) == check
            assert nP(s) == tot
        except AssertionError:
            print(s, i, 'failed perm test')
            raise ValueError()

    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(8):
                check = nC(s, i)
                tot += check
                assert len(list(multiset_combinations(s, i))) == check
                if u:
                    assert nC(len(s), i) == check
            assert nC(s) == tot
            if u:
                assert nC(len(s)) == tot
        except AssertionError:
            print(s, i, 'failed combo test')
            raise ValueError()

    for i in range(1, 10):
        tot = 0
        for j in range(1, i + 2):
            check = nT(i, j)
            tot += check
            assert sum(1 for p in partitions(i, j, size=True)
                       if p[0] == j) == check
        assert nT(i) == tot

    for i in range(1, 10):
        tot = 0
        for j in range(1, i + 2):
            check = nT(range(i), j)
            tot += check
            assert len(list(multiset_partitions(list(range(i)), j))) == check
        assert nT(range(i)) == tot

    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(1, 8):
                check = nT(s, i)
                tot += check
                assert len(list(multiset_partitions(s, i))) == check
                if u:
                    assert nT(range(len(s)), i) == check
            if u:
                assert nT(range(len(s))) == tot
            assert nT(s) == tot
        except AssertionError:
            print(s, i, 'failed partition test')
            raise ValueError()

    # tests for Stirling numbers of the first kind that are not tested in the
    # above
    assert [stirling(9, i, kind=1) for i in range(11)
            ] == [0, 40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1, 0]
    perms = list(permutations(range(4)))
    assert [
        sum(1 for p in perms if Permutation(p).cycles == i) for i in range(5)
    ] == [0, 6, 11, 6, 1] == [stirling(4, i, kind=1) for i in range(5)]
    # http://oeis.org/A008275
    assert [
        stirling(n, k, signed=1) for n in range(10) for k in range(1, n + 1)
    ] == [
        1, -1, 1, 2, -3, 1, -6, 11, -6, 1, 24, -50, 35, -10, 1, -120, 274,
        -225, 85, -15, 1, 720, -1764, 1624, -735, 175, -21, 1, -5040, 13068,
        -13132, 6769, -1960, 322, -28, 1, 40320, -109584, 118124, -67284,
        22449, -4536, 546, -36, 1
    ]
    # https://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind
    assert [stirling(n, k, kind=1) for n in range(10)
            for k in range(n + 1)] == [
                1, 0, 1, 0, 1, 1, 0, 2, 3, 1, 0, 6, 11, 6, 1, 0, 24, 50, 35,
                10, 1, 0, 120, 274, 225, 85, 15, 1, 0, 720, 1764, 1624, 735,
                175, 21, 1, 0, 5040, 13068, 13132, 6769, 1960, 322, 28, 1, 0,
                40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1
            ]
    # https://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind
    assert [stirling(n, k, kind=2) for n in range(10)
            for k in range(n + 1)] == [
                1, 0, 1, 0, 1, 1, 0, 1, 3, 1, 0, 1, 7, 6, 1, 0, 1, 15, 25, 10,
                1, 0, 1, 31, 90, 65, 15, 1, 0, 1, 63, 301, 350, 140, 21, 1, 0,
                1, 127, 966, 1701, 1050, 266, 28, 1, 0, 1, 255, 3025, 7770,
                6951, 2646, 462, 36, 1
            ]
    assert stirling(3, 4, kind=1) == stirling(3, 4, kind=1) == 0
    raises(ValueError, lambda: stirling(-2, 2))

    def delta(p):
        if len(p) == 1:
            return oo
        return min(abs(i[0] - i[1]) for i in subsets(p, 2))

    parts = multiset_partitions(range(5), 3)
    d = 2
    assert (sum(1
                for p in parts if all(delta(i) >= d
                                      for i in p)) == stirling(5, 3, d=d) == 7)

    # other coverage tests
    assert nC('abb', 2) == nC('aab', 2) == 2
    assert nP(3, 3, replacement=True) == nP('aabc', 3, replacement=True) == 27
    assert nP(3, 4) == 0
    assert nP('aabc', 5) == 0
    assert nC(4, 2, replacement=True) == nC('abcdd', 2, replacement=True) == \
        len(list(multiset_combinations('aabbccdd', 2))) == 10
    assert nC('abcdd') == sum(nC('abcdd', i) for i in range(6)) == 24
    assert nC(list('abcdd'), 4) == 4
    assert nT('aaaa') == nT(4) == len(list(partitions(4))) == 5
    assert nT('aaab') == len(list(multiset_partitions('aaab'))) == 7
    assert nC('aabb' * 3, 3) == 4  # aaa, bbb, abb, baa
    assert dict(_AOP_product((4, 1, 1, 1))) == {
        0: 1,
        1: 4,
        2: 7,
        3: 8,
        4: 8,
        5: 7,
        6: 4,
        7: 1
    }
    # the following was the first t that showed a problem in a previous form of
    # the function, so it's not as random as it may appear
    t = (3, 9, 4, 6, 6, 5, 5, 2, 10, 4)
    assert sum(_AOP_product(t)[i] for i in range(55)) == 58212000
    raises(ValueError, lambda: _multiset_histogram({1: 'a'}))
Пример #2
0
    def stem(self, objects = None, a_type = None ):
        """
        Here we will rearrange objects which are not all distinct.
        
        Named Parameters:
            objects -- These are the type of objects being rearranged. ('words', 'marbles', 'wordlike').
                       'wordlike' is really just like marbles, just a sequence of letters instead of marbles. 
                       The difference is just in how the question can be asked.
            a_type  -- The answer type, either "FR" (free response) or "MC" (multiple choice)
        """
        
        kwargs = {
            'objects': objects,
            'a_type': a_type
        }
        
        
        # Throw away problems whos answers are bigger than THREASH, but keep them in the done list
        INF = float('inf') # For now just take anything
        THREASH = 5000000 # I unset this for the 'words' type 
        
        # ERR_THREASH -- Throw awway error options such that (error - answer) > ERR_THREASH*answer
        ERR_THREASH = 4
        
        # These are the current possible values of the objects option
        OBJECT_TYPES = ["words", "marbles","wordlike"]
        
        if objects == None:
            objects = random.choice(OBJECT_TYPES)
            
        if a_type == None:
            a_type = random.choice(["FR", "MC"])
        
        # Some words with repeating letters
        WORDS = ["mathematics", "sleepless", "senseless", "carelessness"]
        WORDS += ["sleeplessness", "bubblebath", "senescence", "tweedledee"]
        WORDS += ["senselessness", "massless", "scissors", "pulchritude"]
        WORDS += ["losslessness", "inhibition", "knickknack","sweettooth" ]
        
        COLORS = ["red", "green", "blue", "orange", "yellow", "pink", "clear"]
        
        question_stem = ""
        
        def make_denom(l):
            """
            Takes a list [3,4,5] and outputs a string "3!4!5!" and the corresponding value.
            
            Parameters:
                l -- A list of integers
                
            Returns -- A pair (str, int) representing the denominator string representation
                       and value.
            """
            # The denominator in the permutation formula
            l = [i for i in l if i != 1]
            ls = map(str, l)
            denom_string = '!\\,'.join(ls) + "!"
            denom_val = reduce(lambda x,y: x*y, map(sym.factorial, l))
            return denom_string, denom_val
            
        def make_errors(a, n, l):
            """
            This could probably be improved. For now it generates some alternate answers for
            multiple choice, that on't look ridiculous.
            
            Parameters:
                a -- is the correct answer
                n -- is the number of actual objects
                l -- is the list of group sizes
            """
                
            l_ = l[:]
            errors = []
            while sum(l_) < n:
                l_.append(1)
            l_ = sorted(l_, key  = lambda x: -x)
            for k in range(len(l_)):
                for i in [-1, 1]:
                    for j in [-1, 1, 0]:
                        
                        if l_[k] + j > 1:
                            l_[k] = l_[k] + j
                            _, denom_val = make_denom(l_)
                            ans = sym.factorial(max(sum(l_), n + i)) / denom_val
                            if ans not in errors and ans != a and np.abs(a - ans) < ERR_THREASH * a:
                            #if ans not in errors and ans != a:
                                errors.append(ans)
            for i in [0.2, 0.3]:
                errors.append(int(a*(1 + i)))
                errors.append(int(a*(1-i)))
            random.shuffle(errors)
            return errors[0:4]
                
        
        def parse_word(word):
            """
            This convers a word into a dictionary of letters and counts and also 
            produces a sentence describing the situation.

            Examples: 

            parse_word("teepee) returns: ({'e': 4}, "There are 4 e's in teepee", [4])
            parse_word("sleeplessness") returns ({'e': 4, 'l': 2, 's': 5},
                                                 "There are 5 s's, 4 e's, and 2 l's in sleeplessness",
                                                 [5, 4, 2])

            
            Parameters:
            
                word -- a word
                
            Returns -- (dict, string, list) see above for example
            """
            word_dict = {}
            for k,g in iter.groupby(sorted(word), lambda x: x):
                count = len(list(g))
                if count > 1:
                    word_dict[k] = count
            l = ["%s %s's" % (word_dict[key],  key) for key in word_dict]
            
            word_dict_string = "There are " + tools.serialize(*l) + " in \"" + word + "\""
            word_letter_counts = list(word_dict.values())
            
            
            return word_dict, word_dict_string, word_letter_counts
        
        if objects == None:
            objects = random.choice(OBJECT_TYPES)
            
        
    
        if objects == "words": 
            
            THREASH = INF #This should not ba set for actual words
            
            word = random.choice(WORDS)
            
            num_objects = len(word)
            
            word_dict, word_string, group_counts = parse_word(word)
            
            item = (objects, group_counts)
            if item in self.done:
                return self.stem(**kwargs)
            
            self.done.append(item)
            
            denom_string, denom_val = make_denom(group_counts)
            
            answer = nP(word, len(word))
            
            question_stem = "How many distinct rearrangements of the letters in \"" + word  \
                + "\" are there?"
                
            explanation = "%s. So the number of distinct rearrangements of %s is:" % (word_string, word)
                
            
            explanation += "$$\\frac{%s!}{%s} = %s$$" %(len(word), denom_string, answer)
           
           
        elif objects == "marbles":
            
            
            # Get case correct
            def marble_string(i):
                if i == 1:
                    return "marble"
                else:
                    return "marbles"

            random.shuffle(COLORS)
            # You will have 3 - 5 groups with the distribution indicated
            num_groups = random.choice([3,3,4,4,4,5])
            # Each group will have between 1 to 5 members
            group_counts = [random.choice([2,2,3,3,3,4,4,5]) for i in range(num_groups)] 
            num_objects = sum(group_counts)
            
            marble_dict = dict([(COLORS[i], group_counts[i]) for i in range(len(group_counts))])
            
            print marble_dict, num_objects
            
            item = (objects, sorted(group_counts))
            if item in self.done:
                return self.stem(**kwargs)
            
            self.done.append(item)
            
            marble_string = tools.serialize(*["%s %s %s" % (group_counts[i], COLORS[i],                                     marble_string(group_counts[i])) for i in range(num_groups)])

            denom_string, denom_val = make_denom(group_counts)
            
            answer = nP(marble_dict, num_objects)
            
            # If number is too big try again
            if answer > THREASH:
                return self.stem(**kwargs)
            
            question_stem = "How many distint ways are there to arrange %s in a row?" % (marble_string)

            explanation = "The number of distinct rearrangements of the marbles is:"
            
            explanation += "$$\\frac{%s!}{%s} = %s$$" %(num_objects, denom_string, answer)

        elif objects == 'wordlike':
            
            
            # This code is repeated ... yuck
           
            # You will have 3 - 5 groups with the distribution indicated
            num_groups = random.choice([3,3,4,4,4,5])
            # Each group will have between 1 to 5 members
            group_counts = [random.choice([1] + [2]*2 + [3]*3 + [4]*2 + [5]) for i in range(num_groups)] 
            num_objects = sum(group_counts)
            
            item = (objects, sorted(group_counts))
            if item in self.done:
                return self.stem(**kwargs)
            
            self.done.append(item)
            
            def build_string(group_counts):
                alph = list('ABCDEFGHIJKLMNIPQRSTUVWXYZ')
                random.shuffle(alph)
                strg = []
                for i in group_counts:
                    strg += alph.pop()*i
                random.shuffle(strg)
                return ''.join(strg)

            strg = build_string(group_counts)
        
            denom_string, denom_val = make_denom(group_counts)
            
            answer = nP(strg, len(strg))
            
            # If number is too big try again
            if answer > THREASH:
                return self.stem(**kwargs)
            
            question_stem = "How many distint rearrangements of the string \'%s\' are there?"  % (strg)

            explanation = "The number of distinct rearrangements of the string \'%s\' is:"  % (strg)
            
            explanation += "$$\\frac{%s!}{%s} = %s$$" %(num_objects, denom_string, answer)
            
        else:
            print "Invald a_type: " + a_type 
           
        if a_type == "FR":
            question_stem += " Give your answer as an integer."
            answer_mathml = tools.fraction_mml(answer)
            return tools.fully_formatted_question(question_stem, explanation, answer_mathml)
        else:
            errors = make_errors(answer, num_objects, group_counts)
            distractors = [answer] + errors
            distractors = ["$_%s$_" % sym.latex(distractor) for distractor in distractors]
            return tools.fully_formatted_question(question_stem, explanation, answer_choices=distractors)
Пример #3
0
def test_nC_nP_nT():
    from sympy.utilities.iterables import (
        multiset_permutations, multiset_combinations, multiset_partitions,
        partitions, subsets, permutations)
    from sympy.functions.combinatorial.numbers import (
        nP, nC, nT, stirling, _multiset_histogram, _AOP_product)
    from sympy.combinatorics.permutations import Permutation
    from sympy.core.numbers import oo
    from random import choice

    c = string.ascii_lowercase
    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(8):
                check = nP(s, i)
                tot += check
                assert len(list(multiset_permutations(s, i))) == check
                if u:
                    assert nP(len(s), i) == check
            assert nP(s) == tot
        except AssertionError:
            print(s, i, 'failed perm test')
            raise ValueError()

    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(8):
                check = nC(s, i)
                tot += check
                assert len(list(multiset_combinations(s, i))) == check
                if u:
                    assert nC(len(s), i) == check
            assert nC(s) == tot
            if u:
                assert nC(len(s)) == tot
        except AssertionError:
            print(s, i, 'failed combo test')
            raise ValueError()

    for i in range(1, 10):
        tot = 0
        for j in range(1, i + 2):
            check = nT(i, j)
            tot += check
            assert sum(1 for p in partitions(i, j, size=True) if p[0] == j) == check
        assert nT(i) == tot

    for i in range(1, 10):
        tot = 0
        for j in range(1, i + 2):
            check = nT(range(i), j)
            tot += check
            assert len(list(multiset_partitions(range(i), j))) == check
        assert nT(range(i)) == tot

    for i in range(100):
        s = ''.join(choice(c) for i in range(7))
        u = len(s) == len(set(s))
        try:
            tot = 0
            for i in range(1, 8):
                check = nT(s, i)
                tot += check
                assert len(list(multiset_partitions(s, i))) == check
                if u:
                    assert nT(range(len(s)), i) == check
            if u:
                assert nT(range(len(s))) == tot
            assert nT(s) == tot
        except AssertionError:
            print(s, i, 'failed partition test')
            raise ValueError()

    # tests for Stirling numbers of the first kind that are not tested in the
    # above
    assert [stirling(9, i, kind=1) for i in range(11)] == [
        0, 40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1, 0]
    perms = list(permutations(range(4)))
    assert [sum(1 for p in perms if Permutation(p).cycles == i)
            for i in range(5)] == [0, 6, 11, 6, 1] == [
            stirling(4, i, kind=1) for i in range(5)]
    # http://oeis.org/A008275
    assert [stirling(n, k, signed=1)
        for n in range(10) for k in range(1, n + 1)] == [
            1, -1,
            1, 2, -3,
            1, -6, 11, -6,
            1, 24, -50, 35, -10,
            1, -120, 274, -225, 85, -15,
            1, 720, -1764, 1624, -735, 175, -21,
            1, -5040, 13068, -13132, 6769, -1960, 322, -28,
            1, 40320, -109584, 118124, -67284, 22449, -4536, 546, -36, 1]
    # http://en.wikipedia.org/wiki/Stirling_numbers_of_the_first_kind
    assert  [stirling(n, k, kind=1)
        for n in range(10) for k in range(n+1)] == [
            1,
            0, 1,
            0, 1, 1,
            0, 2, 3, 1,
            0, 6, 11, 6, 1,
            0, 24, 50, 35, 10, 1,
            0, 120, 274, 225, 85, 15, 1,
            0, 720, 1764, 1624, 735, 175, 21, 1,
            0, 5040, 13068, 13132, 6769, 1960, 322, 28, 1,
            0, 40320, 109584, 118124, 67284, 22449, 4536, 546, 36, 1]
    # http://en.wikipedia.org/wiki/Stirling_numbers_of_the_second_kind
    assert [stirling(n, k, kind=2)
        for n in range(10) for k in range(n+1)] == [
            1,
            0, 1,
            0, 1, 1,
            0, 1, 3, 1,
            0, 1, 7, 6, 1,
            0, 1, 15, 25, 10, 1,
            0, 1, 31, 90, 65, 15, 1,
            0, 1, 63, 301, 350, 140, 21, 1,
            0, 1, 127, 966, 1701, 1050, 266, 28, 1,
            0, 1, 255, 3025, 7770, 6951, 2646, 462, 36, 1]
    assert stirling(3, 4, kind=1) == stirling(3, 4, kind=1) == 0
    raises(ValueError, lambda: stirling(-2, 2))

    def delta(p):
        if len(p) == 1:
            return oo
        return min(abs(i[0] - i[1]) for i in subsets(p, 2))
    parts = multiset_partitions(range(5), 3)
    d = 2
    assert (sum(1 for p in parts if all(delta(i) >= d for i in p)) ==
            stirling(5, 3, d=d) == 7)

    # other coverage tests
    assert nC('abb', 2) == nC('aab', 2) == 2
    assert nP(3, 3, replacement=True) == nP('aabc', 3, replacement=True) == 27
    assert nP(3, 4) == 0
    assert nP('aabc', 5) == 0
    assert nC(4, 2, replacement=True) == nC('abcdd', 2, replacement=True) == \
        len(list(multiset_combinations('aabbccdd', 2))) == 10
    assert nC('abcdd') == sum(nC('abcdd', i) for i in range(6)) == 24
    assert nC(list('abcdd'), 4) == 4
    assert nT('aaaa') == nT(4) == len(list(partitions(4))) == 5
    assert nT('aaab') == len(list(multiset_partitions('aaab'))) == 7
    assert nC('aabb'*3, 3) == 4  # aaa, bbb, abb, baa
    assert dict(_AOP_product((4,1,1,1))) == {
        0: 1, 1: 4, 2: 7, 3: 8, 4: 8, 5: 7, 6: 4, 7: 1}
    # the following was the first t that showed a problem in a previous form of
    # the function, so it's not as random as it may appear
    t = (3, 9, 4, 6, 6, 5, 5, 2, 10, 4)
    assert sum(_AOP_product(t)[i] for i in range(55)) == 58212000
    raises(ValueError, lambda: _multiset_histogram({1:'a'}))
Пример #4
0
 def stem(self, q_type = None, a_type = None, include_PC = False, 
          seed = datetime.datetime.now().microsecond, perm_style = None, choose_style = None):
     """
     There are three possible types of problems: F = factorials, e.g. 5!, P = 
     permutations, e.g. 5!/3! = (5 - 2)!, and C = combinations, e.g. 5!/(3!2!).
     
     Named Parameters:
     
         q_type     -- "Fact", "Perm", or "Comb" for factorial, permutation, or combination
         a_type     -- "MC" or "FR" for multiplechoice and free response
         include_PC -- This is a boolean. If true include mention of permutations/combinations
                       in both problems and explanations.
         perm_style -- perm_styles = ['P(%s,%s)', '{}_%s P_%s'], if choose_style
                       is < len(choose_styles), then it chooses the associated style, 
                       else it chooses randomly
                       
         choose_style -- choose_styles = ['{%s \\choose %s}', 'C(%s,%s)', '{}_%s C_%s'], if choose_style
                         is < len(choose_styles), then it chooses the associated style, else it 
                         chooses randomly   
         seed         -- A seed for the random ops, to make reproducible output
     """
     
     # Assume if the user sets the perm_style or choose_style, assume include_PC should be True
     if perm_style is not None or choose_style is not None:
         include_PC = True
     
     kwargs = {
                 'q_type': q_type,
                 'a_type': a_type,
                 'include_PC': include_PC,
                 'perm_style': perm_style,
                 'choose_style': choose_style,
              }
     
     
     # These numbers can get big lets set a threashold
     THREASH = 300000
     
     # If we exceed the threashold restart with current settings
     ## I wish I had a better wa to get current values???
     def check():
         if answer > THREASH:
             return self.stem(**kwargs)
         else: 
             pass
     
     q_type_orig = q_type
     a_type_orig = a_type
     
     # About 1/5 simple factorials, 2/5 perms, 3/5 combinations
     if q_type == None:
         q_type = random.choice(["Fact"] + ["Perm"] * 2 + ["Comb"] * 3)
     
     if a_type == None:
         a_type = random.choice(["MC", "FR"])
             
     # Actually set the data for the problem
     if q_type == "Fact":
         N = random.randint(5,7) # Change these to modify range
         R = 0
     else:
         N = random.randint(7,11) # Change these to modify range
         R = random.randint(3, N - 4) # To make the explanations work it is good to have N - R > 3
         
         
     
     # Choose the "choose" --- Oh have to be a little tricky here since at one place
     # we have choose(n,k) and another we have choose(%s,$s)%(power,x_pow) 
     
     choose_styles = ['{%s \\choose %s}', 'C(%s,%s)', '{}_{%s} C_{%s}']
     perm_styles = ['P(%s,%s)', '{}_{%s} P_{%s}']
     
     if choose_style not in range(len(choose_styles)):
         choose_ = random.choice(choose_styles)
     else:
         choose_ = choose_styles[choose_style]
         
         
     # Now choose the "perm"
     
     if perm_style not in range(len(perm_styles)):
         perm_ = random.choice(perm_styles)
     else:
         perm_ = perm_styles[perm_style]
         
     
     def choose(a ,b):
         if q_type == "Comb":
             choice =  choose_ % (a, b)
         else:
             choice =  perm_ % (a, b)
         return choice
     
      
     
     
     q_data = (q_type, N, R) # This determines the  problem
     
     if q_data in self.done:
         return self.stem(q_type_orig, a_type_orig, include_PC) # Try again
     else:
         self.done.append(q_data)  # Mark this data as being used
         
    
     # If include_PC is True, this will be overriden below.
     question_stem_options = ["Evaluate the following expression involving factorials."]
     question_stem = random.choice(question_stem_options)
     
     explanation = "Recall the definition: "
     
     
     if q_type == "Fact":
         
         answer = sym.factorial(N)
         check()
         
         if a_type == "MC":
             errors = [sym.factorial(N+1), sym.factorial(N-1), 
                       sym.factorial(N)/sym.factorial(random.randint(1,N-1))]
         
         question_stem += "$$%s!$$" % (N)
         
         explanation += "$_%s! = %s = %s$_" % (N, FactCombPerm.format_fact(N), answer)
                                      
     elif q_type == "Perm":
         
         if include_PC == True:
             question_stem = "Evaluate the following permutation."
         
         answer = nP(N, N - R)
         check()
         
         
         if a_type == "MC":
             
             errors = list(set([nP(N + i, N - (R + i)) for i in range(-3,4) if i != 0]))
             errors = [e for e in errors if e != answer]
             random.shuffle(errors)
             errors = errors[:4] # This provides 4 distractions
             
             
         
         if include_PC:
             explanation_prefix = "%s = \\frac{%s!}{(%s - %s)!} =" % (choose(N, N - R), N, N, N - R)
         else:
             explanation_prefix = ""
     
         explanation += "$_%s\\frac{%s!}{%s!} = \\frac{%s}{%s} =  %s = %s$_" \
         % (explanation_prefix, N, R, FactCombPerm.format_fact(N), 
            FactCombPerm.format_fact(R), FactCombPerm.format_fact(N, R+1), answer)
       
         if include_PC:
             question_stem += "$$%s$$" %(choose(N, N - R))
         else:
             question_stem += "$$\\frac{%s!}{%s!}$$" % (N, R)
         
     else:
         
         if include_PC == True:
             question_stem = "Evaluate the following combination."
             
         answer = nC(N, R) 
         check()
         
         if a_type == "MC":
             errors = list(set([nC(N + i, R + i // 2) for i in range(-3,4) if i != 0 and R + i // 2 > 0]))
             errors = [e for e in errors if e != answer]
             random.shuffle(errors)
             errors = errors[:4] # This provides 4 distractions
             
             
         if include_PC:
             explanation_prefix = "%s = \\frac{%s!}{%s!\\,(%s - %s)!} = " % (choose(N, R), N, R, N, R)
         else:
             explanation_prefix = ""
     
       
         if R >= N - R:
             explanation += tools.align("%s\\frac{%s!}{%s!\,%s!}" \
                                        % (explanation_prefix, N, R, N-R),
                                        "\\frac{%s}{(%s)(%s)}" \
                                        % (FactCombPerm.format_fact(N), FactCombPerm.format_fact(R), 
                                           FactCombPerm.format_fact(N-R)), 
                                        "\\frac{%s}{%s} = %s" \
                                        % (FactCombPerm.format_fact(N, R + 1), FactCombPerm.format_fact(N - R), answer))
         else:
             explanation += tools.align("%s\\frac{%s!}{%s!\,%s!}" \
                                        % (explanation_prefix, N, R, N-R),
                                        "\\frac{%s}{(%s)(%s)}" \
                                        % (FactCombPerm.format_fact(N), FactCombPerm.format_fact(R),
                                           FactCombPerm.format_fact(N-R)), 
                                        "\\frac{%s}{%s} = %s" \
                                        % (FactCombPerm.format_fact(N, (N - R) + 1), FactCombPerm.format_fact(R), answer))
             
             
         
         if include_PC:
             question_stem += "$$%s$$" % (choose(N, R))
         else:
             question_stem += "$$\\frac{%s!}{%s!\\,%s!}$$" % (N, R, N - R)
     
         explanation += "<br>"                           
 
 
 
     if a_type == "FR":
         question_stem += "Give your answer as an integer."
         answer_mathml = tools.fraction_mml(answer)
         return tools.fully_formatted_question(question_stem, explanation, answer_mathml)
     else:
         distractors = [answer] + errors
         distractors = ["$_%s$_" % sym.latex(distractor) for distractor in distractors]
         return tools.fully_formatted_question(question_stem, explanation, answer_choices=distractors)