## Perfect Power Sequence

### February 16, 2018

I discovered the Numberphile channel on YouTube a few months ago, and have greatly enjoyed its videos at the intersection of mathematics and programming. Their recent video discusses the Catalan Conjecture:

In the infinite sequence of perfect powers 1, 4, 8, 9, 16, 25, 32, 36, 49, 64, 81, 100, …, the only two adjacent numbers are 2³ = 8 and 3² = 9.

The conjecture has recently been proved, as discussed at Numberphile.

Your task is to write a program that generates the perfect power sequence. When you are finished, you are welcome to read or run a suggested solution, or to post your own solution or discuss the exercise in the comments below.

Pages: 1 2

### 12 Responses to “Perfect Power Sequence”

1. Zack said

Here is my implementation in Julia.

function IsPerfectPower(x::Int64)
y = ceil(Int64, sqrt(x))

``````for z = 2:y
if mod(x ^ (1 / z), 1) == 0; return true; end
end

return false
``````

end

function genseq(n::Int64)
y = zeros(Int64, n)
y = 1

``````for x = 2:n
if IsPerfectPower(x); y[x] = x; end
end

return y[y .> 0]
``````

end

Performance benchmark: on an Intel i7 laptop running on Win7, it generates the sequence of the PP numbers that are less or equal to 10000 in about 0.1 sec, using approx. 85 MB of RAM.

2. Zack said

Typo in my previous commend: 85 Kilobytes, not MB… Sorry about that!

3. matthew said
```import Data.List.Ordered
f n = n*n : merge powers (f (n+1)) where powers = n*n*n : map (n*) powers
main = print (take 20 (nub (1 : (f 2))))
-- [1,4,8,9,16,25,27,32,36,49,64,81,100,121,125,128,144,169,196,216]
```
4. Paul said

Two solutions in Python. I expected the first to be the fastest: merging already sorted sequences. But it turned out to be otherwise. The second is a little faster.

```from ma.mymath import isqrt  # integer square root
from itertools import takewhile, count
from functools import partial
from operator import ge

def genpows(n):
"""generate powers n^2, n^3, ...."""
x = n
while True:
x *= n
yield x

def perpow(limit):
"""generate all powers (up to limit), merge them and handle duplicates"""
maxn, last = isqrt(limit), 1
its = [genpows(i) for i in range(2, maxn+1)]
yield 1
for i in takewhile(partial(ge, limit), merge(*its)):
if i != last:
yield i
last = i

def perpow2(limit):
def nums(i):
return takewhile(partial(ge, limit),
map(partial(pow, i), count(2)))
powers = set((n for i in range(2, isqrt(limit)+1) for n in nums(i)))
return  + sorted(powers)
```
5. Scott said

Here is some Java. It generates up to 10000 in around 60ms on a 3.3GHz i5 iMac:

```import java.util.PriorityQueue;

public class PerfectPowerSequenceGenerator {
private static class NumberSequence {
public long number;
public long nextPerfectPowerValue;

public NumberSequence(long number) {
this.number = number;
this.nextPerfectPowerValue = number * number;
}
}

private PriorityQueue<NumberSequence> sequences = new PriorityQueue<>((a, b) -> {
if (b.nextPerfectPowerValue > a.nextPerfectPowerValue) {
return -1;
} else if (b.nextPerfectPowerValue < a.nextPerfectPowerValue) {
return 1;
}
return 0;
});

private long nextSequence;    // next sequence to add to the heap
private long lastResult = 0;  // last result we returned, to avoid dupes

public PerfectPowerSequenceGenerator() {
nextSequence = 2;
}

public long next() {
long result = 1;
if (lastResult != 0 && !sequences.isEmpty()) {
NumberSequence seq;
do {
seq = sequences.poll();
result = seq.nextPerfectPowerValue;
seq.nextPerfectPowerValue *= seq.number;
} while (result == lastResult);
}

if (nextSequence * nextSequence > result) {
}

lastResult = result;

return result;
}

public static void main(String[] args) {
long startTime = System.nanoTime();
PerfectPowerSequenceGenerator gen = new PerfectPowerSequenceGenerator();
while (true) {
long pp = gen.next();
System.out.println(pp);
if (pp >= 10000) {
break;
}
}
System.out.println("Ran in " + (System.nanoTime() - startTime) / 1000000 + "ms");
}
}
```
6. matthew said

The queue solution works nicely in Python:

```import heapq
def gen(i0,j0,f):
global q
q = [(f(i0,j0),i0,j0)]
last = None
while True:
(n,i,j) = heapq.heappop(q)
heapq.heappush(q,(f(i+1,j),i+1,j))
if i == i0: heapq.heappush(q,(f(i,j+1),i,j+1))
if n != last: yield n
last = n

g = gen(2,2,lambda i,j: i**j)
s = +[next(g) for _ in range(19)]
print(s); print(q)
# [1,4,8,9,16,25,27,32,36,49,64,81,100,121,125,128,144,169,196,216]
# [(225,15,2),(243,3,5),(256,2,8),(256,4,4),(729,3,6),(2187,3,7),(343,7,3)]
```

It’s quite important how the queue is extended – doing

```        if j == j0: heapq.heappush(q,(f(i+1,j),i+1,j))
heapq.heappush(q,(f(i,j+1),i,j+1))
```

still works, but the queue gets much longer.

Incidentally, the code on the solution page seems to be garbled – I think WordPress have messed something up with angle brackets in code.

7. Paul said

@matthew: I tried the priority method too and it worked nicely, but a little slow in Python. The method below takes about 0.1 sec for all powers below 10^9.

```from ma.mymath import isqrt  # integer square root
from itertools import takewhile, count
from functools import partial
from operator import ge

_small_pp = [1, 4, 8, 9, 16, 25, 27, 32, 36, 49, 64, 81, 100]

def pp2(limit):
def powers(i):
return takewhile(partial(ge, limit),
map(partial(pow, i), count(2)))
nmax = isqrt(limit)
# remove the perfect powers from the range of bases to loop over
if nmax > 120:  # 120 is the first pp after 100 (_small_pp)
excl = pp2(nmax)
else:
excl = _small_pp
bases = set(range(2, nmax+1)) - set(excl)
allpowers = (n for base in bases for n in powers(base))
return  + sorted(allpowers)
```
8. matthew said

@Paul: did you do a speed comparison with my priority queue function above?

9. Paul said

@matthew: I see now that your method works very fast (91 msec for yours and 121 ms for pp2 for all powers up to 10^9). I must have messed up something with my implementation.

10. matthew said

@Paul: that’s about what I made it – also the queue uses less memory, I’m running the program just printing out all the numbers, up to 25528390307779969 after about 30 mins.

For the queue, as mentioned above, it’s significant how the queue is extended.

11. Daniel said

Here’s an implementation in C that uses the approach from the solution.

```#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

typedef unsigned long u_long;

typedef struct {
u_long base;
u_long exponent;
u_long value;
} item_t;

typedef struct {
item_t* array;
size_t count;
size_t capacity;
} heap_t;

typedef struct {
heap_t* heap_p;
size_t n_generated;
} generator_t;

void init_heap(heap_t** heap_pp) {
*heap_pp = malloc(sizeof(heap_t));
heap_t* heap_p = *heap_pp;
heap_p->array = malloc(sizeof(item_t));
heap_p->count = 0;
heap_p->capacity = 1;
}

void clear_heap(heap_t** heap_pp) {
free((*heap_pp)->array);
free(*heap_pp);
*heap_pp = NULL;
}

void swap(item_t* a, item_t* b) {
item_t tmp = *a;
*a = *b;
*b = tmp;
}

void heap_insert(heap_t* heap_p, item_t item) {
size_t new_count = heap_p->count + 1;
if (new_count > heap_p->capacity) {
size_t new_capacity = 2 * (heap_p->capacity + 1) - 1;
heap_p->array = realloc(heap_p->array, new_capacity * sizeof(item_t));
heap_p->capacity = new_capacity;
}
item_t* array = heap_p->array;
// reheapify upward
size_t idx = heap_p->count;
array[idx] = item;
while (1) {
if (idx == 0) break;
size_t parent_idx = (idx-1)/2;
u_long value = item.value;
u_long parent_value = array[parent_idx].value;
if (value > parent_value) break;
swap(&array[idx], &array[parent_idx]);
idx = parent_idx;
}
heap_p->count = new_count;
}

item_t heap_peak(heap_t* heap_p) {
return (heap_p->array);
}

// Popping from an empty heap is not supported
item_t heap_pop(heap_t* heap_p) {
item_t popped_item = heap_peak(heap_p);
size_t new_count = heap_p->count - 1;
if (new_count > 0) {
item_t* array = heap_p->array;
array = array[new_count];
size_t idx = 0;
while (1) {
u_long value = array[idx].value;
size_t left_idx = 2 * idx + 1;
size_t right_idx = left_idx + 1;
if (left_idx > new_count - 1) break;
u_long min_child_value = array[left_idx].value;
size_t min_child_idx = left_idx;
if (right_idx <= new_count - 1) {
u_long right_child_value = array[right_idx].value;
if (right_child_value < min_child_value) {
min_child_value = right_child_value;
min_child_idx = right_idx;
}
}
if (value <= min_child_value) break;
swap(&array[idx], &array[min_child_idx]);
idx = min_child_idx;
}
}
heap_p->count = new_count;
return popped_item;
}

void init_generator(generator_t** generator_pp) {
*generator_pp = malloc(sizeof(generator_t));
generator_t* generator_p = *generator_pp;
init_heap(&(generator_p->heap_p));
item_t item = {.base = 2, .exponent = 2, .value = 4};
heap_insert(generator_p->heap_p, item);
generator_p->n_generated = 0;
}

void clear_generator(generator_t** generator_pp) {
clear_heap(&(*generator_pp)->heap_p);
free(*generator_pp);
*generator_pp = NULL;
}

u_long next(generator_t* generator) {
++(generator->n_generated);
if (generator->n_generated == 1) return 1;
heap_t* heap_p = generator->heap_p;
u_long value = heap_peak(heap_p).value;
while (heap_peak(heap_p).value == value) {
item_t item = heap_pop(heap_p);
if (item.exponent == 2) {
item_t new_item;
new_item.base = item.base + 1;
new_item.exponent = 2;
new_item.value = new_item.base * new_item.base;
heap_insert(heap_p, new_item);
}
++(item.exponent);
item.value *= item.base;
heap_insert(heap_p, item);
}
return value;
}

int main(void) {
generator_t* generator;
init_generator(&generator);
u_long limit = 1764;
while (1) {
u_long value = next(generator);
printf("%lu", value);
if (value >= limit) break;
printf(",");
}
clear_generator(&generator);
return 0;
}
```

Output:

```1,4,8,9,16,25,27,32,36,49,64,81,100,121,125,128,144,169,196,216,225,243,256,289,324,343,361,400,441,484,512,529,576,625,676,729,784,841,900,961,1000,1024,1089,1156,1225,1296,1331,1369,1444,1521,1600,1681,1728,1764
```