5 Best Ways to Check if a Number is an Ugly Number in Python

Rate this post

πŸ’‘ Problem Formulation: Determining whether a number is an ugly number requires checking if its prime factors are limited to 2, 3, and 5. An ugly number is one that has no other prime factors than 2, 3, or 5. The sequence of ugly numbers starts with 1, 2, 3, 4, 5, 6, 8, 9, 10, and so on. For example, inputting the number 6 should result in ‘True’ because it can be factored into 2 Γ— 3, which are both allowable prime factors.

Method 1: Iterative Prime Factorization

This method involves repeatedly dividing the number by 2, 3, and 5 – the only prime factors allowed for an ugly number – until it cannot be divided anymore. If the final number is 1, it started as an ugly number. This method is simple and easy to implement and understand, making it ideal for those new to this concept.

Here’s an example:

def is_ugly(num):
    if num <= 0:
        return False
    for prime in [2, 3, 5]:
        while num % prime == 0:
            num //= prime
    return num == 1

print(is_ugly(6))  # True
print(is_ugly(14)) # False

Output:

True
False

This code snippet defines a function is_ugly() that accepts an integer. The function first eliminates non-positive numbers, which cannot be ugly numbers. Then it attempts to divide the number by each of the prime factors 2, 3, and 5 until it is no longer divisible by those primes. It concludes by returning whether the reduced number is 1.

Method 2: Recursion with Memoization

Method 2 uses recursion with memoization to avoid redundant calculations for numbers that have been already computed. This optimization technique stores the results of expensive function calls and returns the cached result when the same inputs occur again. This is a more advanced method and can be faster for checking multiple numbers.

Here’s an example:

def is_ugly_memo(num, memo={}):
    if num <= 0:
        return False
    elif num == 1:
        return True
    elif num in memo:
        return memo[num]
    
    if num % 2 == 0:
        result = is_ugly_memo(num // 2, memo)
    elif num % 3 == 0:
        result = is_ugly_memo(num // 3, memo)
    elif num % 5 == 0:
        result = is_ugly_memo(num // 5, memo)
    else:
        result = False
    memo[num] = result
    return result

print(is_ugly_memo(6))  # True

Output:

True

The function is_ugly_memo() works similarly to Method 1 but adds a dictionary to store previously computed results. If the current number is encountered again, it retrieves the result from the dictionary without recomputing. This makes the function more efficient, especially when processing large numbers or multiple checks.

Method 3: Dynamic Programming

Dynamic programming is an approach used to solve problems by combining the solutions of subproblems. This method builds up the solution by solving smaller instances of the problem first, which can make it more efficient for finding several ugly numbers in sequence.

Here’s an example:

def nth_ugly_number(n):
    ugly_nums = [1]
    i2 = i3 = i5 = 0
    while len(ugly_nums) < n:
        next2, next3, next5 = 2*ugly_nums[i2], 3*ugly_nums[i3], 5*ugly_nums[i5]
        next_ugly = min(next2, next3, next5)
        ugly_nums.append(next_ugly)
        if next_ugly == next2: i2 += 1
        if next_ugly == next3: i3 += 1
        if next_ugly == next5: i5 += 1
    return ugly_nums[n-1]

print(nth_ugly_number(10))  # Should return the 10th ugly number

Output:

12

This function nth_ugly_number() calculates ugly numbers one by one, using three pointers (i2, i3, i5) to keep track of the multiplication of 2, 3, and 5. The next ugly number is then the minimum of these three possibilities. This ensures the sequence of ugly numbers is generated in the correct order without having to check each number individually.

Method 4: Using Sets for Minimization

This method to determine ugly numbers uses sets to keep track of seen multiples and takes advantage of the min function to always work with the smallest current ugly number. This is more efficient than checking each number individually and helps to generate them in order without duplicates.

Here’s an example:

def get_all_ugly_numbers(n):
    ugly_numbers = {1}
    for _ in range(n):
        next_ugly = min(ugly_numbers)
        ugly_numbers.remove(next_ugly)
        ugly_numbers |= {next_ugly * 2, next_ugly * 3, next_ugly * 5}
    return next_ugly

print(get_all_ugly_numbers(10))

Output:

12

Here, get_all_ugly_numbers() function starts with a set containing only the number 1, which is the first ugly number. It then iterates, repeatedly taking the smallest number from the set and adding its multiples by 2, 3, and 5 to the set. This progression ensures that the ugly numbers are generated sequentially up to the nth ugly number.

Bonus One-Liner Method 5: Functional Approach

This one-liner method uses a functional approach with Python list comprehension and the built-in all() function for brevity and elegance. It’s a compact way to check if a number is ugly in a single line of code, but it might not be as readable or efficient for newcomers.

Here’s an example:

is_ugly = lambda num: num > 0 and all(num % i for i in [2, 3, 5]) if num <= max([2, 3, 5]) else num // max(filter(lambda x: num % x == 0, [2, 3, 5])) in [2, 3, 5]

print(is_ugly(6))  # True

Output:

True

This one-liner defines a lambda function is_ugly which checks if the number is positive and factors into the primes 2, 3, and 5. It’s concise but requires a solid understanding of lambda functions and list comprehensions to be understood at first glance.

Summary/Discussion

  • Method 1: Iterative Prime Factorization. Simple and straightforward. Not optimized for large numbers or multiple checks.
  • Method 2: Recursion with Memoization. More efficient for multiple checks. Slightly more complex due to recursion and memoization.
  • Method 3: Dynamic Programming. Efficient for generating ugly numbers in sequence. Can be complex to understand but reduces unnecessary checks.
  • Method 4: Using Sets for Minimization. Generates ugly numbers without duplicates. Can be memory intensive for large sequences.
  • Method 5: Functional Approach. Concise one-liner. Not as readable and may be slow for large numbers or sequences.