Given primes and exponents, generating all possible integers smaller than N

Viewed 68

I am looking to write a function that, given a set of exponents and a set of primes, it would generate all possible integers that may be formed using primes from the given set, and raising them to exponents found in the given set of exponents, with the restriction that the integers may not exceed a limit N. The set of primes contains a number of elements not less than the number of elements in the exponents' set. All exponents have to be used, and they are not necessarily distinct. The set of primes is sorted, and so is the list of exponents, from lowest to biggest.

For example, say our set of primes is {11,19,29,31}. The set of exponents is {1,4}, and N = 20,000,000. So, we're talking about products satisying P = a^1*b^4.

P = 11^1 * 19^4 = 1,433,531

P = 11^1 * 29^4 = 7,780,091

P = 11^1 * 31^4 = 10,158,731

P = 19^1 * 29^4 = 13,438,339

P = 19^1 * 31^4 = 17,546,899

P = 29^1 * 31^4 = 26,782,109 ---- discarded due to limit restriction

P = 19^1 * 11^4 = 278,179

P = 29^1 * 11^4 = 424,589

P = 29^1 * 19^4 = 3,779,309

P = 31^1 * 11^4 = 453,871

P = 31^1 * 19^4 = 4,039,951

P = 31^1 * 29^4 = 21,925,711 ---- discarded due to limit restriction

Note that every prime may have any exponent, and it is not necessary for example that the lower prime would have the lower exponent, because exchanging the exponents between the primes would result in different integers. This is not the case however if the set of exponents were {1,1} because then exchanging them between any 2 primes would not change the result, I.E 11^1 * 31^1 = 31^1 * 11^1.

For my purposes, the set of exponents contains very few elements, about 5, but N is very large, more than 10^13, and the set of primes contains many primes, over 50,000 say. It is therefore challenging to create all possible integers efficiently.

The code I have come up with uses recursion. It generates all possible such integers, keeping track of: an arraylist which represents the set of exponents, called partition , used=number of exponents used in our current product, partitionIndex = the index of the exponent we're going to use in the current function call, an arraylist of the primes we have used thus far - primesUsed (to avoid using the same prime twice in the product) and finally, prod = our current product. The recursion will stop when used is as big as our exponents list, meaning we have used all the exponents and our product is viable. Pruning is done when our current product is too big to multiply by any other prime number. Every viable integer is added to an arraylist of products. Here's the code:

import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class test3 {

    public static long N;
    public static long [] primes;
    public static ArrayList <Long> products;
    
    public static void main(String[] args) 
    {
        N = 20000000;
        primes = new long [] {11,19,29,31};
        products = new ArrayList <Long>();
        
        ArrayList <Long> partition = new ArrayList <Long>();
        partition.add(1L);
        partition.add(4L);
        
        generateProducts(partition , 0 , 0 , new ArrayList <Long>() , 1);
        
        //Set <Long> discardDups = new HashSet<>(products);   --- for list {1,4}, can be skipped.
        //products.clear();                                   --- but for {1,1} has to be used, otherwise there are duplicates
        //products.addAll(discardDups);
        
        Collections.sort(products);
        
        System.out.println(products);
        System.out.println(products.size());
    }
    
    public static void generateProducts (List <Long> partition , int used , int partitionIndex , ArrayList <Long> primesUsed , long prod)
    {
        if (used == partition.size())
        {
            products.add(prod);
            return;
        }

        for (int i = 0 ; i < primes.length && primes.length - i + 1 >= partition.size() - partitionIndex && prod * Math.pow(primes[i], partition.get(partitionIndex)) <= N ; i++)
        {
            if (!primesUsed.contains(primes[i]))
            {
                primesUsed.add(primes[i]);
                generateProducts(partition , used + 1 , partitionIndex + 1 , primesUsed , (long) (prod * Math.pow(primes[i], partition.get(partitionIndex))));
                primesUsed.remove(primesUsed.size() - 1);
            }
        }
    }

}

The drawbacks of this function are:

  1. It generates many duplicates. Because the counter in the function starts from 0 everytime, and it checks we're not reusing any prime, then when the exponents are the same, it generates a duplicate, I.E it treats 11^1 * 31^1 and 31^1 * 11^1 differently.

  2. The algorithm overall takes a very long time. Again, I assume, because the counter starts from 0 over and over.

I am well aware I can get rid of the duplicates using a Set but this is extremely time consuming. Also, keeping track of the highest prime index used and starting from it with every function call makes the algorithm faster, but then products with the bigger prime having the lower exponent are not generated.

What I am interested in is a way to generate all such products efficiently. I am not sure if my code can be altered to it. If it can, then it should be able to generate all possible such integers, it should avoid generating duplicates in the first place, and the "stopping" conditions, or pruning, should be better to spare as many redundant function calls as possible.

Perhaps another approach altogether is necessary, but the goal remains the same. I would appreciate any direction as to how to change my code to avoid duplicates in the first place without skipping any possible integer, and making it faster. If duplicates cannot be avoided, then atleast a direction how to reduce the amount of function calls. Any other approach to achieve the same goal is most welcome!

0 Answers
Related