views:

4498

answers:

4

Here's the very dumb way:

def divisorGenerator(n):
    for i in xrange(1,n/2+1):
        if n%i == 0: yield i
    yield n

The result I'd like to get is similar to this one, but I'd like a smarter algorithm (this one it's too much slow and dumb :-)

I can find prime factors and their multiplicity fast enough. I've an generator that generates factor in this way:

(factor1, multiplicity1)
(factor2, multiplicity2)
(factor3, multiplicity3)
and so on...

i.e. the output of

for i in factorGenerator(100):
    print i

is:

(2, 2)
(5, 2)

I don't know how much is this useful for what I want to do (I coded it for other problems), anyway I'd like a smarter way to make

for i in divisorGen(100):
    print i

output this:

1
2
4
5
10
20
25
50
100


UPDATE: Many thanks to Greg Hewgill and his "smart way" :) Calculating all divisors of 100000000 took 0.01s with his way against the 39s that the dumb way took on my machine, very cool :D

UPDATE 2: Stop saying this is a duplicate of this post. Calculating the number of divisor of a given number doesn't need to calculate all the divisors. It's a different problem, if you think it's not then look for "Divisor function" on wikipedia. Read the questions and the answer before posting, if you do not understand what is the topic just don't add not useful and already given answers.

A: 

To expand on what Shimi has said, you should only be running your loop from 1 to the square root of n. Then to find the pair, do n / i, and this will cover the whole problem space.

As was also noted, this is a NP, or 'difficult' problem. Exhaustive search, the way you are doing it, is about as good as it gets for guaranteed answers. This fact is used by encryption algorithms and the like to help secure them. If someone were to solve this problem, most if not all of our current 'secure' communication would be rendered insecure.

Not knowing Python, but guessing from what's been posted so far, try something like this:

def divisorGenerator(n):
    for i in xrange(1,sqrt(n)+1):
        if n%i == 0:
            yield i;
            yield n/i;

Which should output a list like:

1
100
2
50
4
25
5
20
10
10

It just needs a uniqueness check on the last two items, as well as a quick sort and you're done.

Matthew Scharley
I do not understand why it should be end on sqrt(n). It would stop on 10 in the case of n = 100... it is wrong :O
Andrea Ambu
Because, once you have a list of elements between 1..10, you can generate any of the elements between 11..100 trivialy. You get {1, 2, 4, 5, 10}. Divide 100 by each of these elements and you {100, 50, 20, 25, 10}.
Matthew Scharley
Factors are ALWAYS generated in pairs, by virtue of the definition. By only searching to sqrt(n), you're cutting your work by a power 2.
Matthew Scharley
It's very faster than the version in my post, but it's still too slow than the version using prime factors
Andrea Ambu
I agree this isn't the best solution. I was simply pointing out a 'better' way of doing the 'dumb' search that would already save alot of time.
Matthew Scharley
Factorization has not been shown to be NP-hard. http://en.wikipedia.org/wiki/Integer_factorizationAnd the problem was to find all the divisors given that the prime factors (the hard part) had already been found.
Jamie
Which means factorisation is an NP-hard algorithm by extension. The fact they already have the prime factors is beside the point. The example given was exhaustive search, which is slow and hard.
Matthew Scharley
+11  A: 

Given your factorGenerator function, here is a divisorGen that should work:

def divisorGen(n):
    factors = list(factorGenerator(n))
    nfactors = len(factors)
    f = [0] * nfactors
    while True:
        yield reduce(lambda x, y: x*y, [factors[x][0]**f[x] for x in range(nfactors)], 1)
        i = 0
        while True:
            f[i] += 1
            if f[i] <= factors[i][1]:
                break
            f[i] = 0
            i += 1
            if i >= nfactors:
                return

The overall efficiency of this algorithm will depend entirely on the efficiency of the factorGenerator.

Greg Hewgill
wow it took 0.01 for calculating all divisors of 100000000 against the 39s that took the dumb way (stopping at n/2) very cool, thank you!
Andrea Ambu
For those of us who don't understand Pythonese, what is this actually doing?
Matthew Scharley
monoxide: this computes all multiplicative combinations of the given factors. Most of it should be self-explanatory; the "yield" line is like a return but keeps going after returning a value. [0]*nfactors creates a list of zeros of length nfactors. reduce(...) computes the product of the factors.
Greg Hewgill
The reduce and lambda notation were the parts that were actually confusing me. I tried implementing an algorithm to do this in C# using a recursive function to walk array of factors and multiply them all together, but it seems to have horrible performance on numbers like 1024 that have many factors
Matthew Scharley
A: 

Anyone interested in improving their algorithmic and mathematical chops would do well to check out Project Euler:

Project Euler is a series of challenging mathematical/computer programming problems that will require more than just mathematical insights to solve. Although mathematics will help you arrive at elegant and efficient methods, the use of a computer and programming skills will be required to solve most problems.

The motivation for starting Project Euler, and its continuation, is to provide a platform for the inquiring mind to delve into unfamiliar areas and learn new concepts in a fun and recreational context.

Factorizing numbers features in many of the early problems, and once you've solved a problem, you gain access to a forum thread where you can read the ways others have tackled it. Highly recommended!

Paul Dixon
What of the early problems needs to calculate all the divisors?
Andrea Ambu
Problems #12: What is the value of the first triangle number to have over five hundred divisors?
Ferruccio
There is no need to calculate all the divisors of a number to know how many they are. You should look for "divisor function" on wikipedia.
Andrea Ambu
This is exactly the point I'm making - you solve the problem your own way, then by reading the other solutions afterwards, learn better methods.
Paul Dixon
I already did that problem time ago :D I do not find anyone who has calculated all the divisor :D Do you know any other problem so I can see if I've already solved it :P ? :D
Andrea Ambu
I can already feel my life slipping away because of this website. Damn you!
Matthew Scharley
StackOverflow or project Euler? :P
Andrea Ambu
I have to admit, both...
Matthew Scharley
+2  A: 

I like Greg solution, but I wish it was more python like. I feel it would be faster and more readable; so after some time of coding I came out with this.

The first two functions are needed to make the cartesian product of lists. And can be reused whnever this problem arises. By the way, I had to program this myself, if anyone knows of a standard solution for this problem, please feel free to contact me.

"Factorgenerator" now returns a dictionary. And then the dictionary is fed into "divisors", who uses it to generate first a list of lists, where each list is the list of the factors of the form p^n with p prime. Then we make the cartesian product of those lists, and we finally use Greg' solution to generate the divisor. We sort them, and return them.

I tested it and it seem to be a bit faster than the previous version. I tested it as part of a bigger program, so I can't really say how much is it faster though.

Pietro Speroni (pietrosperoni dot it)

from math import sqrt


##############################################################
### cartesian product of lists ##################################
##############################################################

def appendEs2Sequences(sequences,es):
    result=[]
    if not sequences:
        for e in es:
            result.append([e])
    else:
        for e in es:
            result+=[seq+[e] for seq in sequences]
    return result


def cartesianproduct(lists):
    """
    given a list of lists,
    returns all the possible combinations taking one element from each list
    The list does not have to be of equal length
    """
    return reduce(appendEs2Sequences,lists,[])

##############################################################
### prime factors of a natural ##################################
##############################################################

def primefactors(n):
    '''lists prime factors, from greatest to smallest'''  
    i = 2
    while i<=sqrt(n):
        if n%i==0:
            l = primefactors(n/i)
            l.append(i)
            return l
        i+=1
    return [n]      # n is prime


##############################################################
### factorization of a natural ##################################
##############################################################

def factorGenerator(n):
    p = primefactors(n)
    factors={}
    for p1 in p:
        try:
            factors[p1]+=1
        except KeyError:
            factors[p1]=1
    return factors

def divisors(n):
    factors = factorGenerator(n)
    divisors=[]
    listexponents=[map(lambda x:k**x,range(0,factors[k]+1)) for k in factors.keys()]
    listfactors=cartesianproduct(listexponents)
    for f in listfactors:
        divisors.append(reduce(lambda x, y: x*y, f, 1))
    divisors.sort()
    return divisors



print divisors(60668796879)

P.S. it is the first time I am posting to stackoverflow. I am looking forward for any feedback.

Pietro Speroni
In Python 2.6 there is a itertools.product().
J.F. Sebastian
A version that use generators instead of list.append everywhere could be cleaner.
J.F. Sebastian
Sieve of Eratosthenes could be used to generate prime numbers less then or equal sqrt(n) http://stackoverflow.com/questions/188425/project-euler-problem#193605
J.F. Sebastian
Coding style: exponents = [k**x for k, v in factors.items() for x in range(v+1)]
J.F. Sebastian