3

I am given an integer N and I have to find the first N elements that are divisable only by 2,3 and/or 5, and not by any other prime number.

For example:

N = 3
Results: 2,3,4
N = 5
Results: 2,3,4,5,6

Mistake number = 55..55/5 = 11..11 which is a prime number. As 55..55 is divisable by a prime different from 2,3 and 5, it doesn't count.

I guess I need a recursive function, but I cant imagine what the algorithm would look like

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
waplet
  • 151
  • 1
  • 9

4 Answers4

2

The only numbers that are only divisible by 2, 3 or 5 are the powers 2i × 3j × 5k for ijk = 0, 1, ....

Those numbers are easily generated.

Kerrek SB
  • 464,522
  • 92
  • 875
  • 1,084
2

The numbers you're seeking are of the form 2^n * 3^m * 5^k, with n, m and k positive integers, with n+m+k > 0.

I'd pre-generate a sorted array and just print out the first N.

Luchian Grigore
  • 253,575
  • 64
  • 457
  • 625
2

We can efficiently generate the sequence in order by merging the appropriate multiples of the sequence of Hamming numbers, that is the classical algorithm.

If n > 1 is a Hamming number divisible by p, then n/p is also a Hamming number, and if m is a Hamming number and p one of 2, 3, or 5, then m*p is also a Hamming number.

So we can describe the sequence of Hamming numbers as

H = 1 : (2*H ∪ 3*H ∪ 5*H)

where p*H is the sorted sequence obtained by multiplying all Hamming numbers with p, and means the sorted union (so with H = 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, ..., e.g. 2*H = 2, 4, 6, 8, 10, 12, 16, 18, 20, 24, ... and 2*H ∪ 3*H = (2, 4, 6, 8, 10, 12, 16, ...) ∪ (3, 6, 9, 12, 15, ...) = (2, 3, 4, 6, 8, 9, 10, 12, 15, 16, ...)).

This algorithm has two downsides, though. First, it produces duplicates that must be eliminated in the merging () step. Second, to generate the Hamming numbers near N, the Hamming numbers near N/5, N/3 and N/2 need to be known, and the simplest way to achieve that is to keep the part of the sequence between N/5 and N in memory, which requires quite a bit of memory for large N.

A variant that addresses both issues starts with the sequence of powers of 5,

P = 1, 5, 25, 125, 625, 3125, ...

and in a first step produces the numbers having no prime factors except 3 or 5,

T = P ∪ 3*T   (= 1 : (5*P ∪ 3*T))

(a number n having no prime factors except 3 and 5 is either a power of 5 (n ∈ P), or it is divisible by 3 and n/3 also has no prime factors except 3 and 5 (n ∈ 3*T)). Obviously, the sequences P and 3*T are disjoint, so no duplicates are produced here.

Then, finally we obtain the sequence of Hamming numbers via

H = T ∪ 2*H

Again, it is evident that no duplicates are produced, and to generate the Hamming numbers near N, we need to know the sequence T near N, which requires knowing P near N and T near N/3, and the sequence H near N/2. Keeping only the part of H between N/2 and N, and the part of T between N/3 and N in memory requires much less space than keeping the part of H between N/5 and N in memory.

A rough translation of my Haskell code to C++ (unidiomatic, undoubtedly, but I hardly ever write C++, and the C++ I learned is ancient) yields

#include <iostream>
#include <cstdlib>
#include <vector>
#include <algorithm>
#include <gmpxx.h>

class Node {
public:
    Node(mpz_class n) : val(n) { next = 0; };
    mpz_class val;
    Node *next;
};

class ListGenerator {
public:
    virtual mpz_class getNext() = 0;
    virtual ~ListGenerator() {};
};

class PurePowers : public ListGenerator {
    mpz_class multiplier, value;
public:
    PurePowers(mpz_class p) : multiplier(p), value(p) {};
    mpz_class getNext() {
        mpz_class temp = value;
        value *= multiplier;
        return temp;
    }
    // default destructor is fine here
    // ~PurePowers() {}
};

class Merger : public ListGenerator {
    mpz_class multiplier, thunk_value, self_value;
    // generator of input sequence
    // to be merged with our own output
    ListGenerator *thunk;
    // list of our output we need to remember
    // to generate the next numbers
    // Invariant: list is never empty, and sorted
    Node *head, *tail;
public:
    Merger(mpz_class p, ListGenerator *gen) : multiplier(p) {
        thunk = gen;
        // first output would be 1 (skipped here, though)
        head = new Node(1);
        tail = head;
        thunk_value = thunk->getNext();
        self_value = multiplier;
    }
    mpz_class getNext() {
        if (thunk_value < self_value) {
            // next value from the input sequence is
            // smaller than the next value obtained
            // by multiplying our output with the multiplier
            mpz_class num = thunk_value;
            // get next value of input sequence
            thunk_value = thunk->getNext();
            // and append our next output to the bookkeeping list
            tail->next = new Node(num);
            tail = tail->next;
            return num;
        } else {
            // multiplier * head->val is smaller than next input
            mpz_class num = self_value;
            // append our next output to the list
            tail->next = new Node(num);
            tail = tail->next;
            // and delete old head, which is no longer needed
            Node *temp = head->next;
            delete head;
            head = temp;
            // remember next value obtained from multiplying our own output
            self_value = head->val * multiplier;
            return num;
        }
    }
    ~Merger() {
        // delete wrapped thunk
        delete thunk;
        // and list of our output
        while (head != tail) {
            Node *temp = head->next;
            delete head;
            head = temp;
        }
        delete tail;
    }
};

// wrap list generator to include 1 in the output
class Hamming : public ListGenerator {
    mpz_class value;
    ListGenerator *thunk;
public:
    Hamming(ListGenerator *gen) : value(1) {
        thunk = gen;
    }
    // construct a Hamming number generator from a list of primes
    // If the vector is empty or contains anything but primes,
    // horrible things may happen, I don't care
    Hamming(std::vector<unsigned long> primes) : value(1) {
        std::sort(primes.begin(), primes.end());
        ListGenerator *gn = new PurePowers(primes.back());
        primes.pop_back();
        while(primes.size() > 0) {
            gn = new Merger(primes.back(), gn);
            primes.pop_back();
        }
        thunk = gn;
    }
    mpz_class getNext() {
        mpz_class num = value;
        value = thunk->getNext();
        return num;
    }
    ~Hamming() { delete thunk; }
};

int main(int argc, char *argv[]) {
    if (argc < 3) {
        std::cout << "Not enough arguments provided.\n";
        std::cout << "Usage: ./hamming start_index count [Primes]" << std::endl;
        return 0;
    }
    unsigned long start, count, n;
    std::vector<unsigned long> v;
    start = strtoul(argv[1],NULL,0);
    count = strtoul(argv[2],NULL,0);
    if (argc == 3) {
        v.push_back(2);
        v.push_back(3);
        v.push_back(5);
    } else {
        for(int i = 3; i < argc; ++i) {
            v.push_back(strtoul(argv[i],NULL,0));
        }
    }
    Hamming *ham = new Hamming(v);
    mpz_class h;
    for(n = 0; n < start; ++n) {
        h = ham->getNext();
    }
    for(n = 0; n < count; ++n) {
        h = ham->getNext();
        std::cout << h << std::endl;
    }
    delete ham;
    return 0;
}

which does the job without being too inefficient:

$ ./hamming 0 20
1
2
3
4
5
6
8
9
10
12
15
16
18
20
24
25
27
30
32
36
$ time ./hamming 1000000 2
519381797917090766274082018159448243742493816603938969600000000000000000000000000000
519386406319142860380252256170487374054333610204770704575899579187200000000000000000

real    0m0.310s
user    0m0.307s
sys     0m0.003s
$ time ./hamming 100000000 1
181401839647817990674757344419030541037525904195621195857845491990723972119434480014547
971472123342746229857874163510572099698677464132177627571993937027608855262121141058201
642782634676692520729286408851801352254407007080772018525749444961547851562500000000000
000000000000000000000000000000000000000000000000000000000000000000000000000000000000000
00000000000000000000000000000000000000000000

real    0m52.138s
user    0m52.111s
sys     0m0.050s

(the Haskell version is faster, GHC can optimise idiomatic Haskell better than I can optimise unidiomatic C++)

Community
  • 1
  • 1
Daniel Fischer
  • 181,706
  • 17
  • 308
  • 431
0

there's always the brute force way:

int[] A = int[N];
int i=0;
int j=2;
while(i<N)
{
    if(j%2==0)
    {
        if(j/2==1 || A contains j/2)
        {
             A[i]=j;
             i++;
        }
    }
    else if(j%3==0)
    {
        if(j/3==1 || A contains j/3)
        {
             A[i]=j;
             i++;
        }
    }
    else if(j%5==0)
    {
        if(j/5==1 || A contains j/5)
        {
             A[i]=j;
             i++;
        }
    }
    j++;
}

for the "A contains X" parts you can use binary search in range 0 to i-1 because A is sorted there.

pseudoDust
  • 1,336
  • 1
  • 10
  • 18