15

Possible Duplicates:
nth ugly number
Find the Kth least number for expression (2^x)*(3^y)*(5^z)

I'm wondering how to solve this problem in a fast and elegant way:

We define "ugly" every number n which can be written in the form: 2^x * 3^y * 5^z;, where x,y and z are natural numbers. Find the 1500th ugly number.

E.g. the first "ugly" numbers are:

1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, ...

I've tried to solve this problem using brute-force, in this way:

import itertools as it

def is_ugly(n):
    '''Return `True` if *n* is an ugly number.'''

    if n == 1:
        return True
    while not n % 2:
        n //= 2
    while not n % 3:
        n //= 3
    while not n % 5:
        n //= 5
    return n == 1

def nth_ugly(n):
    '''Return the nth ugly number.'''

    num = 0
    for i in it.count(1):
        if is_ugly(i):
            num += 1
            if num == n:
                return i

But it takes quite a lot of time, and I'd like to find a faster and better solution.

I know the prime factors of ugly numbers, but I can't think of a way to generate these numbers following the correct order.

I think there must be a way to generate these numbers without having to check all the numbers. The problem is that it seems like exponents of the prime factors are distributed quite randomly.

Look at this table:

n   |number| x | y | z |
------------------------
1   |  1   | 0 | 0 | 0 |
------------------------
2   |  2   | 1 | 0 | 0 |
------------------------
3   |  3   | 0 | 1 | 0 |
------------------------
4   |  4   | 2 | 0 | 0 |
------------------------
5   |  5   | 0 | 0 | 1 |
------------------------
6   |  6   | 1 | 1 | 0 |
------------------------
7   |  8   | 3 | 0 | 0 |
------------------------
8   |  9   | 0 | 2 | 0 |
------------------------
9   |  10  | 1 | 0 | 1 |
------------------------
10  |  12  | 2 | 1 | 0 |
------------------------
11  |  15  | 0 | 1 | 1 |
------------------------
12  |  16  | 4 | 0 | 0 |
------------------------
13  |  18  | 1 | 2 | 0 |
------------------------
14  |  20  | 2 | 0 | 1 |
------------------------
15  |  24  | 3 | 1 | 0 |
------------------------

As you can see x,y and z values don't seem to follow any rule.

Can someone of you find any solution to this problem?

I'm thinking of trying to divide the problem in different parts. Since the problem is determined by the randomness of exponents, I could try to generate independently the powers of 2s,3s,5s and then the numbers of the form 2^x*3^y,2^x*5^z etc. And finally put them together, but I don't know if this will solve my issue.

Cœur
  • 32,421
  • 21
  • 173
  • 232
Bakuriu
  • 85,459
  • 18
  • 168
  • 202
  • Homework? Interview? I had this once as a homework, will post solution below. – Mihai Maruseac Sep 07 '11 at 12:13
  • 2
    according to http://stackoverflow.com/questions/7215315 The 'Alternate version using "Cyclic Iterators"' is a very pretty Python solution for anyone deciding which Python solution to read found in [this page](http://rosettacode.org/wiki/Hamming_numbers#Alternate_version_using_.22Cyclic_Iterators.22) – Xavier Combelle Sep 07 '11 at 13:30
  • It's a problem given some years ago in the exam that gives access to the Udine's School of Excellence. I'm preparing to enter there so I'm trying to solve previous tests. I'm sorry about the duplicate, even if the programming language is different... I just didn't try "ugly numbers" because I thought it was just a random name invented by the author of the test. – Bakuriu Sep 07 '11 at 18:01
  • 2
    While the answer you've got with the O(n) code to find the whole sequence is a good one, it is possible to directly calculate the n-th number of the Hamming's sequence in O(n^(2/3)) time, with very small coefficient. [This Haskell code](http://rosettacode.org/wiki/Hamming_numbers#Direct_calculation_through_triples_enumeration) calculates the 1,000,000-th value in [a few hundredths of a second](https://ideone.com/q3fma) on Ideone.com (case #8, 'd'). – Will Ness Apr 15 '12 at 18:18

4 Answers4

10

Here is a complete solution. O(n) complexity, it generates every number once and in order.

# http://www.cs.utexas.edu/users/EWD/ewd07xx/EWD792.PDF

n = 15
bases = [2, 3, 5]

nums = [1] * n
candidates_indexes = [0 for _ in bases]
candidates = [base for base in bases]

for i in range(1, n):
    nextn = min(candidates)
    nums[i] = nextn

    for index, val in enumerate(candidates):
        if val == nextn:
            candidates_indexes[index] += 1
            candidates[index] = bases[index] * nums[candidates_indexes[index]]

print(nums)
orlp
  • 98,226
  • 29
  • 187
  • 285
2

Here's a solution using a heap. The idea is that we keep track of the exponents along with the ugly product. For each iteration, the algorithm performs up to 3 find_min operations and 3 insert operations, The find_mins can be redundant because you can get to each by adding one to any exponent, and there are three exponents. We do an insert three times because we add one to each exponent and insert that into the heap. To find the nth ugly number, we thus have to perform N operations that are 6 * O(log n), thus the algorithm has time complexity of O(n log n). The heap itself, since it can only grow by a constant amount for each iteration, is SPACE(O(n))

>>> import heapq, itertools
>>> class UglyGen(object):
...     def __init__(self, x, y, z):
...         self.x, self.y, self.z = x, y, z
...         self.value = 2**self.x * 3**self.y * 5**self.z
...     def incr(self):
...         x, y, z = self.x, self.y, self.z
...         return (UglyGen(x+1, y, z),
...                 UglyGen(x, y+1, z),
...                 UglyGen(x, y, z+1))
...     def __lt__(self, other):
...         if not isinstance(other, UglyGen):
...             return NotImplemented
...         return self.value < other.value
... 
>>> def gen_ugly():
...     gens = [UglyGen(0, 0, 0)]
...     last = 0
...     while gens:
...         while gens[0].value == last:
...             heapq.heappop(gens)
...         top = heapq.heappop(gens)
...         succ_items = top.incr()
...         for succ in succ_items:
...             heapq.heappush(gens, succ)
...         last = top.value
...         yield last
... 
>>> def find_nth_ugly(n):
...     for n0, u in itertools.izip(xrange(n), gen_ugly()):
...         pass
...     return u
... 
>>> find_nth_ugly(15)
24
SingleNegationElimination
  • 137,315
  • 28
  • 247
  • 284
  • actually, the heap's space is O(n^(2/3)). Not only the complexity of this solution is suboptimal, but it also over-generates the heap past the requested element. – Will Ness Apr 15 '12 at 18:10
1

use a list (n in the following code) to store all the prev ugly numbers, the next ugly number is the min number of (n*2, n*3, n*5) that is larger then n[-1]:

n = [1]
while len(n) < 1500:
    n.append(min([next(x*s for x in n if x*s>n[-1]) for s in (2,3,5)]))    
print n[-1]

It's a O(n^2) solution, so don't try large number.

HYRY
  • 83,863
  • 22
  • 167
  • 176
0

I suggest you solve this incrementally and store all the ugly numbers you find in a list.

When checking a number for ugliness then, you only have to check whether it's one of your number times 2, 3 or 5.

EDIT: I just tried to implement that like this

ugly = [1]
candidate = 2
while len(ugly) < 1500:
    for u in ugly:
        if any([(u * x == candidate) for x in (2, 3 ,5)]):
            ugly.append(candidate)
            break
    candidate += 1

print ugly[-1]

but this approach stagnates hopelessly. Too naive. :) Use some sort of Sieve of Eratosthenes rather.

Johannes Charra
  • 26,855
  • 5
  • 39
  • 47