Search code examples
pythonrangeproductpython-itertools

product of variable number of range(n)'s


I am trying to understand how to write code that will output all the divisors of a number. The approach that I am most interested in taking begins with a function that returns a dictionary where the keys are the prime divisors and the values are the number of times divisible. I have already written this function like so:

def div_pair(num):
    divPair = {}
    for prime in prime_gen():
        primeDegree = 0
        while num % prime == 0:
            num = int(num / prime)
            primeDegree += 1
        if primeDegree > 0:
            divPair[prime] = primeDegree
        if num == 1:
            return divPair

As an example, the number 84,000 outputs the dictionary

{2: 5, 3: 1, 5: 3, 7: 1}

What I want to do from here is generate powersets(?) of any given values returned by the different numbers divPair would return, and then multiply these powersets by their matched primes. This is an example which uses the kind of code I am trying to use to generate the powersets:

from itertools import product
list(product(range(5+1), range(1+1), range(3+1), range(1+1)))

Outputs this:

[(0, 0, 0, 0),
 (0, 0, 0, 1),
 (0, 0, 1, 0),
 (0, 0, 1, 1),
 (0, 0, 2, 0),
 (0, 0, 2, 1),
 (0, 0, 3, 0),
 (0, 0, 3, 1),
 (0, 1, 0, 0),
 (0, 1, 0, 1),
 (0, 1, 1, 0),
 (0, 1, 1, 1),
 (0, 1, 2, 0),
 (0, 1, 2, 1),
 (0, 1, 3, 0),
 (0, 1, 3, 1),
 (1, 0, 0, 0),
 (1, 0, 0, 1),
 (1, 0, 1, 0),
 (1, 0, 1, 1),
 (1, 0, 2, 0),
 (1, 0, 2, 1),
 (1, 0, 3, 0),
 (1, 0, 3, 1),
 (1, 1, 0, 0),
 (1, 1, 0, 1),
 (1, 1, 1, 0),
 (1, 1, 1, 1),
 (1, 1, 2, 0),
 (1, 1, 2, 1),
 (1, 1, 3, 0),
 (1, 1, 3, 1),
 (2, 0, 0, 0),
 (2, 0, 0, 1),
 (2, 0, 1, 0),
 (2, 0, 1, 1),
 (2, 0, 2, 0),
 (2, 0, 2, 1),
 (2, 0, 3, 0),
 (2, 0, 3, 1),
 (2, 1, 0, 0),
 (2, 1, 0, 1),
 (2, 1, 1, 0),
 (2, 1, 1, 1),
 (2, 1, 2, 0),
 (2, 1, 2, 1),
 (2, 1, 3, 0),
 (2, 1, 3, 1),
 (3, 0, 0, 0),
 (3, 0, 0, 1),
 (3, 0, 1, 0),
 (3, 0, 1, 1),
 (3, 0, 2, 0),
 (3, 0, 2, 1),
 (3, 0, 3, 0),
 (3, 0, 3, 1),
 (3, 1, 0, 0),
 (3, 1, 0, 1),
 (3, 1, 1, 0),
 (3, 1, 1, 1),
 (3, 1, 2, 0),
 (3, 1, 2, 1),
 (3, 1, 3, 0),
 (3, 1, 3, 1),
 (4, 0, 0, 0),
 (4, 0, 0, 1),
 (4, 0, 1, 0),
 (4, 0, 1, 1),
 (4, 0, 2, 0),
 (4, 0, 2, 1),
 (4, 0, 3, 0),
 (4, 0, 3, 1),
 (4, 1, 0, 0),
 (4, 1, 0, 1),
 (4, 1, 1, 0),
 (4, 1, 1, 1),
 (4, 1, 2, 0),
 (4, 1, 2, 1),
 (4, 1, 3, 0),
 (4, 1, 3, 1),
 (5, 0, 0, 0),
 (5, 0, 0, 1),
 (5, 0, 1, 0),
 (5, 0, 1, 1),
 (5, 0, 2, 0),
 (5, 0, 2, 1),
 (5, 0, 3, 0),
 (5, 0, 3, 1),
 (5, 1, 0, 0),
 (5, 1, 0, 1),
 (5, 1, 1, 0),
 (5, 1, 1, 1),
 (5, 1, 2, 0),
 (5, 1, 2, 1),
 (5, 1, 3, 0),
 (5, 1, 3, 1)]

which is really the output that I want. I just need to modify the code to accept divPair.values() in some way. So I write this:

from itertools import product
divPair = div_pair(84000)
list(product(range(i+1) for i in divPair.values()))

which seems to me as if it should be correct, but it outputs this mess:

[(range(0, 6),), (range(0, 2),), (range(0, 4),), (range(0, 2),)]

and I can't figure out how to fix it. There is a post here which offers fantastic solutions to what I am trying to do. I am just trying to work toward them with what I know.


Solution

  • product returns the product of its arguments, and you have passed it a single one, the (range(i+1) for i in divPair.values()) generator. The generator yielded a list of range objects. That's like doing this:

    >>> list(product(['range', 'range', 'range']))
    [('range',), ('range',), ('range',)]
    

     

    You have to pass your ranges as individual arguments.

    Do this:

    list(product(*[range(i+1) for i in divPair.values()]))
    

    (or this)

    list(product(*(range(i+1) for i in divPair.values())))