Perfect Power Sequence
February 16, 2018
Here’s our solution:
(define-generator (powers)
(define-record-type power (fields val base expo))
(define (lt? a b) (< (power-val a) (power-val b)))
(yield 1)
(let loop ((pq (pq-insert lt? (make-power (expt 2 2) 2 2) pq-empty)) (m 3) (limit (expt 2 3)) (prev 0))
(if (and (not (pq-empty? pq)) ( (generator-take 54 (powers))
(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)
That requires a bit of explanation. Pq is a priority queue that has members like (val, base, expo), one per power, ordered by increasing value. When the generator is just about to produce the perfect power 100, the priority queue consists of (100 10 2) (125 5 3) (243 3 5) (256 4 4) (729 3 6), which is one member for each power sequence; thus, 100 is the next square, 125 is the next cube, 243 is the next fifth power, 256 is the next fourth power, and 729 is the next sixth power. A new power sequence is added to the priority queue every time the sequence passes a new power of 2, so the priority queue will have log₂ n members when the sequence reaches a value of n. The code is careful to maintain duplicates, so that none of the individual power sequences is interrupted, but to print only one copy of any value.
You can run the program at https://ideone.com/XvXveK, where you will also see the code that handles generators and priority queues.
Here is my implementation in Julia.
function IsPerfectPower(x::Int64)
y = ceil(Int64, sqrt(x))
end
function genseq(n::Int64)
y = zeros(Int64, n)
y[1] = 1
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.
Typo in my previous commend: 85 Kilobytes, not MB… Sorry about that!
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 [1] + sorted(powers)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; sequences.add(seq); } while (result == lastResult); } if (nextSequence * nextSequence > result) { sequences.add(new NumberSequence(nextSequence++)); } 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"); } }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 = [1]+[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.
By the way, this is https://oeis.org/A001597
@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 [1] + sorted(allpowers)@Paul: did you do a speed comparison with my priority queue function above?
@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.
@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.
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)[0]; } // 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[0] = 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: