Search code examples
mathsequences

Generating an item from an ordered sequence of exponentials


I am writing a solution for the following problem.

A is a list containing all elements 2^I * 3^Q where I and Q are integers in an ascending order.

Write a function f such that:

f(N) returns A[N]

The first few elements are:

A[0] = 1

A[1] = 2

A[2] = 3

A[3] = 4

A[4] = 6

A[5] = 8

A[6] = 9

A[7] = 12

My solution was to generate a list containing the first 225 elements by double looping in 15 times each, then sorted this list and return A[N]

Is there any way to generate the N'th element of this sequence without creating and sorting a list first?


Solution

  • Here are two approaches that solve your problem without creating such a large list. Each approach has its disadvantages.

    First, you could set a counter to 0. Then scan all the integers from 1 on up. For each integer, divide out all the multiples of 2 and of 3 in its factorization. If 1 remains, increment the counter; otherwise, leave the counter unchanged. When the counter reaches N you have found the value of A[N]. For example, you increase the counter for integers 1, 2, 3, and 4, but not for 5. This approach uses very little memory but would take much time.

    A second approach uses a min priority queue, such as Python's heapq. Again, set a counter to zero, but also initialize the priority queue to hold only the number 1 and note that the highest power of 3 seen so far is also 1. Increment the counter then peek at the minimum number in the queue. If the counter is N you just got your value of A[N]. Otherwise, pop that min value and immediately push double its value. (The pop and push can be done in one operation in many priority queues.) If that value was a the highest power of 3 seen so far, also push three times its value and note that this new value is now the highest power of 3.

    This second approach uses a priority queue that takes some memory but the largest size will only be on the order of the square root of N. I would expect the time to be roughly equal to your sorting the large list, but I am not sure. This approach has the most complicated code and requires you to have a min priority queue.

    Your algorithm has the advantage of simplicity and the disadvantage of a large list. In fact, given N it is not at all obvious the maximum powers of 2 and of 3 are, so you would be required to make the list much larger than needed. For example, your case of calculating "the first 225 elements by double looping in 15 times each" actually only works up to N=82.

    Below I have Python code for all three approaches. Using timeit for N=200 I got these timings:

    1.19 ms  for sorting a long list (your approach) (powerof2=powerof3=30)
    8.44 s   for factoring increasing integers
    88 µs    for the min priority queue (maximum size of the queue was 17)
    

    The priority queue wins by a large margin--much larger than I expected. Here is the Python 3.6.4 code for all three approaches:

    """A is a list containing all elements 2^I * 3^Q where I and Q are
    integers in an ascending order. Write a function f such that
    f(N) returns A[N].
    
    Do this without actually building the list A.
    
    Based on the question <https://stackoverflow.com/questions/49615681/
    generating-an-item-from-an-ordered-sequence-of-exponentials>
    """
    import heapq  # min priority queue
    
    def ordered_exponential_0(N, powerof2, powerof3):
        """Return the Nth (zero-based) product of powers of 2 and 3.
        This uses the questioner's algorithm
        """
        A = [2**p2 * 3**p3 for p2 in range(powerof2) for p3 in range(powerof3)]
        A.sort()
        return A[N]
    
    def ordered_exponential_1(N):
        """Return the Nth (zero-based) product of powers of 2 and 3.
        This uses the algorithm of factoring increasing integers.
        """
        i = 0
        result = 1
        while i < N:
            result += 1
            num = result
            while num % 2 == 0:
                num //= 2
            while num % 3 == 0:
                num //= 3
            if num == 1:
                i += 1
        return result
    
    def ordered_exponential_2(N):
        """Return the Nth (zero-based) product of powers of 2 and 3.
        This uses the algorithm using a priority queue.
        """
        i = 0
        powerproducts = [1]  # initialize min priority queue to only 1
        highestpowerof3 = 1
        while i < N:
            powerproduct = powerproducts[0]  # next product of powers of 2 & 3
            heapq.heapreplace(powerproducts, 2 * powerproduct)
            if powerproduct == highestpowerof3:
                highestpowerof3 *= 3
                heapq.heappush(powerproducts, highestpowerof3)
            i += 1
        return powerproducts[0]