Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Finding the closest number that factors given a list of primes

Say I have a number, I can find all the prime factors that make up that number. For instance, 6000 is 2^4 * 3 * 5^3.

If I have a number that doesn't factorize well (given a list of acceptable primes), how can I find the next closest number? For instance, given the number 5917, what is the closest number that factors with the list of primes 2, 3, 5, 7? Which is 6000 in this example.

I have something that will brute force find the answer, but there has to be a more elegant solution.

const UInt32 num = 5917;
const CVector<UInt32> primes = { 2, 3, 5, 7 };
const size_t size = primes.size();

UInt32 x = num;
while (x < num * 2)
{
    const UInt32 y = x;
    for(size_t i = 0; i < size && x > 1; ++i)
    {
        while(x % primes[i] == 0)
        {
            x /= primes[i];
        }
    }

    if (x == 1)
    {
        cout << "Found " << y << endl;
        break;
    }
    else
    {
        x = y + 1;
    }
}

EDIT

I created a test that used the brute force method and the 3 methods provided as answers and got somewhat surprising results. All 4 versions produce correct answers (so thanks for your contributions), however, the brute force method seemed to be the fastest, by an order of magnitude. I tried on a few different systems, compilers, and architectures which all yielded mostly consistent results.

The test code can be found here: http://ideone.com/HAgDsF. Please feel free to make suggestions.

like image 933
steveo225 Avatar asked Mar 24 '16 19:03

steveo225


2 Answers

I suggest the following solution. I assume that primes are ordered from lower to greater. I have also used convenient vector and int types.

vector<int> primes = { 2, 3, 5, 7 };
int num = 5917;
// initialize bestCandidate as a power of some prime greater than num
int bestCandidate = 1;
while (bestCandidate < num) bestCandidate *= primes[0];
set<int> s;
s.insert(1);
while (s.size()) {
    int current = *s.begin();
    s.erase(s.begin());
    for (auto p : primes) { // generate new candidates
        int newCandidate = current * p;
        if (newCandidate < num) {
            // new lower candidates should be stored.
            if (s.find(newCandidate) == s.end())
                s.insert(newCandidate);
        }
        else {
            if (newCandidate < bestCandidate) bestCandidate = newCandidate;
            break; // further iterations will generate only larger numbers
        }
    }
}
cout << bestCandidate;

Demo

Next I want to make an analysis of proposed solutions. Let me use np as a number of primes; n as a number to find the closest result to; minP as a minimum prime in the list.

  1. My solution generates all possible values that are lower than n. New values are generated out of the old ones. Each value is used only once to be the generation source. If new value exceeds n it is considered as a valid candidate. In case the list will contain all the primes lower than n still the algo can perform well. I don't know pretty time complexity formula for the algo but it is the number of valid candidates lower than n multiplied by the log of previous factor. Log comes from set data-structure operations. We can get rid of Log factor if n can be small enough to allocate array of size n to flag which values were already generated, a simple list can hold generation source values instead of set.

  2. Your initial solution has O(n(np + logminP(n))). You check every number to be valid taking then one by one from n to 2n paying np + logminP(n) for each check.

  3. Recursive solution by @anatolyg has a big flaw in "visiting" some valid numbers many times which is very inefficient. It can be fixed by introducing a flags indicating that the number was already "visited". For example 12 = 2*2*3 will be visited from 6 = 2*3 and 4 = 2*2. Minor flaws are numerous context switching and supporting the state of each call. The solution has a global variable which clutters global namespace, this can be solved by adding a function parameter.

  4. Solution by @dasblinkenlight lacks efficiency because already "used" candidates are taken for generation of the new candidates producing numbers already present in the set. Although I've borrowed the idea with set.

Based on the @גלעד ברקן's answer I've created a c++ solution which is indeed seems to be asymptotically more efficient because there is no log factor. However I refused to work with double logarithms and left solution with integers. The idea is simple. We have a list of products lower than num. Each of the products is generated out of the first primesUsed primes. We then try to generate new products using next prime. Such approach guarantees to generate unique products:

vector<int> primes = { 2, 3, 5, 7, 11, 17, 23 };
int num = 100005917;
int bestCandidate = INT_MAX;
list<pair<int, int> > ls;
ls.push_back(make_pair(1, 0));
while (ls.size()) {
    long long currentProd = ls.front().first;
    int primesUsed = ls.front().second;
    ls.pop_front();
    int currentPrime = primes[primesUsed];
    while (currentProd < num) {
        if(primesUsed < primes.size() - 1)
            ls.push_back(make_pair(currentProd, primesUsed + 1));
        currentProd *= currentPrime;
    }
    bestCandidate = min((long long)bestCandidate, currentProd);
}
cout << bestCandidate;

Demo

like image 170
Ivan Gritsenko Avatar answered Sep 28 '22 06:09

Ivan Gritsenko


Rather than trying to arrive at the answer by repeated factoring, you could try generating all possible products until you enumerate all products under target*minPrime, where minPrime is the smallest prime in your set.

Start with a set consisting of 1. Each iteration tries multiplying each number in the current set by each prime. If a new number under the max is found, it is added to the current set. The process repeats itself until no new numbers can be added.

In your case, the first generation would be

1 2 3 5 7

The next generation would be

1 2 3 4 5 6 7 9 10 14 15 21 25 35 49 

After that you would see

Generation 3

1 2 3 4 5 6 7 8 9 10 12 14 15 18 20 21 25 27 28 30 35 42 45 49 50 63 70 75 98 105 125 147 175 245 343

Generation 4

1 2 3 4 5 6 7 8 9 10 12 14 15 16 18 20 21 24 25 27 28 30 35 36 40 42 45 49 50 54 56 60 63 70 75 81 84 90 98 100 105 125 126 135 140 147 150 175 189 196 210 225 245 250 294 315 343 350 375 441 490 525 625 686 735 875 1029 1225 1715 2401 

and so on. After twelve generations your set would no longer grow, at which point you could find the smallest value above the target.

Demo.

like image 33
Sergey Kalinichenko Avatar answered Sep 28 '22 06:09

Sergey Kalinichenko