Skip to content

Commit 22002c9

Browse files
authored
Generalize NthUglyNumber (TheAlgorithms#4209)
1 parent 4bbc4bd commit 22002c9

File tree

2 files changed

+156
-44
lines changed

2 files changed

+156
-44
lines changed
Lines changed: 69 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,82 @@
1-
// Ugly numbers are numbers whose only prime factors are 2, 3 or 5. The sequence 1, 2, 3, 4, 5, 6, 8, 9, 10, 12, 15, … shows the first 11 ugly numbers.
2-
// By convention, 1 is included.
3-
// A program to find the nth Ugly number
4-
// Algorithm :
5-
// Initialize three-pointers two, three, and five pointing to zero.
6-
// Take 3 variables nm2, nm3, and nm5 to keep track of next multiple of 2,3 and 5.
7-
// Make an array of size n to store the ugly numbers with 1 at 0th index.
8-
// Initialize a variable next which stores the value of the last element in the array.
9-
// Run a loop n-1 times and perform steps 6,7 and 8.
10-
// Update the values of nm2, nm3, nm5 as ugly[two]*2, ugly[three]*3, ugly[5]*5 respectively.
11-
// Select the minimum value from nm2, nm3, and nm5 and increment the pointer related to it.
12-
// Store the minimum value in variable next and array.
13-
// Return next.
141
package com.thealgorithms.maths;
152

16-
import java.util.*;
3+
import java.util.HashMap;
4+
import java.util.ArrayList;
5+
import java.util.Arrays;
6+
import java.lang.IllegalArgumentException;
177

18-
class NthUglyNumber {
198

20-
/* Function to get the nth ugly number*/
21-
public long getNthUglyNo(int n) {
22-
long[] ugly = new long[n];
23-
int two = 0, three = 0, five = 0;
24-
long nm2 = 2, nm3 = 3, nm5 = 5;
25-
long next = 1;
9+
/**
10+
* @brief class computing the n-th ugly number (when they are sorted)
11+
* @details the ugly numbers with base [2, 3, 5] are all numbers of the form 2^a*3^b^5^c,
12+
* where the exponents a, b, c are non-negative integers.
13+
* Some properties of ugly numbers:
14+
* - base [2, 3, 5] ugly numbers are the 5-smooth numbers, cf. https://oeis.org/A051037
15+
* - base [2, 3, 5, 7] ugly numbers are 7-smooth numbers, cf. https://oeis.org/A002473
16+
* - base [2] ugly numbers are the non-negative powers of 2,
17+
* - the base [2, 3, 5] ugly numbers are the same as base [5, 6, 2, 3, 5] ugly numbers
18+
*/
19+
public class NthUglyNumber {
20+
ArrayList<Long> uglyNumbers = new ArrayList<>(Arrays.asList(1L));
21+
final int[] baseNumbers;
22+
HashMap<Integer, Integer> positions = new HashMap<>();
2623

27-
ugly[0] = 1;
24+
/**
25+
* @brief initialized the object allowing to compute ugly numbers with given base
26+
* @param baseNumbers the given base of ugly numbers
27+
* @exception IllegalArgumentException baseNumber is empty
28+
*/
29+
NthUglyNumber(int[] baseNumbers) {
30+
if (baseNumbers.length == 0) {
31+
throw new IllegalArgumentException("baseNumbers must be non-empty.");
32+
}
2833

29-
for (int i = 1; i < n; i++) {
30-
next = Math.min(nm2, Math.min(nm3, nm5));
34+
this.baseNumbers = baseNumbers;
35+
for (final var baseNumber : baseNumbers) {
36+
this.positions.put(baseNumber, 0);
37+
}
38+
}
3139

32-
ugly[i] = next;
33-
if (next == nm2) {
34-
two = two + 1;
35-
nm2 = ugly[two] * 2;
36-
}
37-
if (next == nm3) {
38-
three = three + 1;
39-
nm3 = ugly[three] * 3;
40-
}
41-
if (next == nm5) {
42-
five = five + 1;
43-
nm5 = ugly[five] * 5;
40+
/**
41+
* @param n the zero-based-index of the queried ugly number
42+
* @exception IllegalArgumentException n is negative
43+
* @return the n-th ugly number (starting from index 0)
44+
*/
45+
public Long get(int n) {
46+
if (n < 0) {
47+
throw new IllegalArgumentException("n must be non-negative.");
48+
}
49+
50+
while (uglyNumbers.size() <= n) {
51+
addUglyNumber();
52+
}
53+
54+
return uglyNumbers.get(n);
55+
}
56+
57+
private void addUglyNumber() {
58+
uglyNumbers.add(computeMinimalCandidate());
59+
updatePositions();
60+
}
61+
62+
private void updatePositions() {
63+
final var lastUglyNumber = uglyNumbers.get(uglyNumbers.size() - 1);
64+
for (final var baseNumber : baseNumbers) {
65+
if (computeCandidate(baseNumber) == lastUglyNumber) {
66+
positions.put(baseNumber, positions.get(baseNumber) + 1);
4467
}
4568
}
46-
return next;
4769
}
4870

49-
public static void main(String[] args) {
50-
Scanner sc = new Scanner(System.in);
51-
System.out.println("Enter the value of n : ");
52-
int n = sc.nextInt();
53-
NthUglyNumber ob = new NthUglyNumber();
54-
long ugly = ob.getNthUglyNo(n);
55-
System.out.println("nth Ugly number is : " + ugly);
71+
private long computeCandidate(int candidateBase) {
72+
return candidateBase * uglyNumbers.get(positions.get(candidateBase));
73+
}
74+
75+
private long computeMinimalCandidate() {
76+
long res = Long.MAX_VALUE;
77+
for (final var baseNumber : baseNumbers) {
78+
res = Math.min(res, computeCandidate(baseNumber));
79+
}
80+
return res;
5681
}
5782
}
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
package com.thealgorithms.maths;
2+
3+
import static org.junit.jupiter.api.Assertions.assertEquals;
4+
import static org.junit.jupiter.api.Assertions.assertThrows;
5+
6+
import java.util.HashMap;
7+
8+
import org.junit.jupiter.api.Test;
9+
10+
public class NthUglyNumberTest {
11+
@Test
12+
public void testGetWithNewObject() {
13+
HashMap<Integer, Long> testCases = new HashMap<>();
14+
testCases.put(0, 1L);
15+
testCases.put(1, 2L);
16+
testCases.put(2, 3L);
17+
testCases.put(3, 4L);
18+
testCases.put(4, 5L);
19+
testCases.put(5, 6L);
20+
testCases.put(9, 12L);
21+
testCases.put(19, 36L);
22+
testCases.put(52, 270L);
23+
testCases.put(1078, 84934656L);
24+
testCases.put(1963, 6973568802L);
25+
26+
for (final var tc : testCases.entrySet()) {
27+
var uglyNumbers = new NthUglyNumber(new int[] {2, 3, 5});
28+
assertEquals(uglyNumbers.get(tc.getKey()), tc.getValue());
29+
30+
var otherUglyNumbers = new NthUglyNumber(new int[] {5, 25, 6, 2, 3, 5});
31+
assertEquals(otherUglyNumbers.get(tc.getKey()), tc.getValue());
32+
}
33+
}
34+
35+
@Test
36+
public void testGetWithSameObject() {
37+
HashMap<Integer, Long> testCases = new HashMap<>();
38+
testCases.put(0, 1L);
39+
testCases.put(1, 2L);
40+
testCases.put(2, 3L);
41+
testCases.put(3, 4L);
42+
testCases.put(4, 5L);
43+
testCases.put(5, 6L);
44+
testCases.put(6, 7L);
45+
testCases.put(1499, 1984500L);
46+
testCases.put(1572, 2449440L);
47+
testCases.put(1658, 3072000L);
48+
testCases.put(6625, 4300800000L);
49+
50+
var uglyNumbers = new NthUglyNumber(new int[] {7, 2, 5, 3});
51+
for (final var tc : testCases.entrySet()) {
52+
assertEquals(uglyNumbers.get(tc.getKey()), tc.getValue());
53+
}
54+
55+
assertEquals(uglyNumbers.get(999), 385875);
56+
}
57+
58+
@Test
59+
public void testGetWithBase1() {
60+
var uglyNumbers = new NthUglyNumber(new int[] {1});
61+
assertEquals(uglyNumbers.get(10), 1);
62+
}
63+
64+
@Test
65+
public void testGetWithBase2() {
66+
var uglyNumbers = new NthUglyNumber(new int[] {2});
67+
assertEquals(uglyNumbers.get(5), 32);
68+
}
69+
70+
71+
@Test
72+
public void testGetThrowsAnErrorForNegativeInput() {
73+
var uglyNumbers = new NthUglyNumber(new int[] {1, 2});
74+
assertThrows(
75+
IllegalArgumentException.class,
76+
() -> uglyNumbers.get(-1)
77+
);
78+
}
79+
80+
@Test
81+
public void testConstructorThrowsAnErrorForEmptyInput() {
82+
assertThrows(
83+
IllegalArgumentException.class,
84+
() -> new NthUglyNumber(new int[] {})
85+
);
86+
}
87+
}

0 commit comments

Comments
 (0)