Reputation: 296
Given number X
, find numbers A
and B
such that X = A^B
and A
is the smallest possible number. B
is less than 301.
String[] power2(String X) {
double n = Double.parseDouble(X);
double i = 1;
double j = 1;
String[] result = {"1", "1"};
boolean flag = false;
if (n > 1) {
for (i = 1; i < n; i++) {
for (j = 1; j < 301; j++) {
if (Math.pow(i, j) == n) {
flag = true;
break;
}
}
if (flag) {
break;
}
}
}
result[0] = i + "";
result[1] = j + "";
return result;
}
This works fine but its not efficient.I am looking for more faster algorithm.
Upvotes: 0
Views: 147
Reputation: 5588
A couple of things:
b = 1
, e.g. 100003 = 100003 ^ 1
but your function returns [100003.0, 301.0]
. Due to loss of precision on doubles, your function will not return a valid result for 10000000000600000000009 = 100000000003 ^ 2
. It will keep increasing base for a very long time until it eventually returns wrong result.2 ^ 301
is a number with 90 digits. Hence BigInteger
is the only choice. Which unfortunately will decrease performance.x
. If x = 9
then for a = 2
and b = 4
we already have 16
so we can stop increasing b
and increase a
instead (and reset b
to 1). Your function however after checking 2 ^ 4
will check also 2 ^ 5
, 2 ^ 6
and 300 more.Here is what I came up with:
private static final int MAX_B = 300;
public String[] power2(BigInteger x) {
increaseBase:
for (BigInteger a = BigInteger.ONE; a.compareTo(x) <= 0; a = a.add(BigInteger.ONE)) {
for (int b = 2; b <= MAX_B; b++) {
BigInteger result = a.pow(b);
if (result.equals(x)) {
return new String[] {String.valueOf(a), String.valueOf(b)};
}
if (result.compareTo(x) == 1) {
continue increaseBase;
}
}
}
return new String[] {String.valueOf(x), "1"};
}
For values below 2 billion, it is likely to be slower, however it is able to handle numbers of any size.
Further optimizations that can be done:
a
as long as a < roundDown(sqrt(x))
instead of until a < x
x
the result wasAnother idea which might be actually the most efficient one is to find prime factors of x
. If we know that prime factors are 2, 2, 2, 2, 13, 13
then we can easily calculate that a = 2 * 2 * 13
and b = 2
.
EDIT:
Here is the implementation of my prime factors idea. Tests and imports at the bottom.
public class PowerCalculator {
public String[] power2(String x) {
@SuppressWarnings("MismatchedQueryAndUpdateOfCollection") // update via increaseCountFor method
PrimeFactorToCount primeFactorToCount = new PrimeFactorToCount();
BigInteger number = new BigInteger(x);
BigInteger divisor = new BigInteger("2");
while (!number.equals(BigInteger.ONE)) {
if (number.remainder(divisor).equals(BigInteger.ZERO)) {
number = number.divide(divisor);
primeFactorToCount.increaseCountFor(divisor);
} else {
if (gcd(primeFactorToCount.values()) == 1) {
return new String[] {x, "1"};
}
divisor = divisor.add(BigInteger.ONE);
}
}
int gcd = gcd(primeFactorToCount.values());
return primeFactorToCount.entrySet().stream()
.map(entry -> new Exponentiation(entry.getKey(), entry.getValue()))
.map(exponentiation -> new Exponentiation(exponentiation.base.pow(exponentiation.exponent / gcd), gcd))
.reduce((a, b) -> new Exponentiation(a.base.multiply(b.base), gcd))
.map(Exponentiation::asStringArray)
.get();
}
private static int gcd(Collection<Integer> values) {
return values.stream()
.mapToLong(v -> v)
.mapToObj(BigInteger::valueOf)
.reduce(BigInteger::gcd)
.map(BigInteger::intValue)
.orElse(0);
}
private class Exponentiation {
private final BigInteger base;
private final int exponent;
private Exponentiation(BigInteger base, int exponent) {
this.base = base;
this.exponent = exponent;
}
private String[] asStringArray() {
return new String[] {String.valueOf(base), String.valueOf(exponent)};
}
}
private class PrimeFactorToCount extends HashMap<BigInteger, Integer> {
private void increaseCountFor(BigInteger primeFactor) {
if (containsKey(primeFactor)) {
put(primeFactor, get(primeFactor) + 1);
} else {
put(primeFactor, 1);
}
}
}
}
@RunWith(Parameterized.class)
public class PowerCalculatorTest {
private final uk.co.jpawlak.maptoobjectconverter.PowerCalculator powerCalculator = new uk.co.jpawlak.maptoobjectconverter.PowerCalculator();
@Parameterized.Parameters(name = "{index}: returns {1} for {0}")
public static Collection data() {
return asList(new Object[][] {
{input(3), expected(3, 1)},
{input(7 * 7), expected(7, 2)},
{input(2 * 2 * 2), expected(2, 3)},
{input(3 * 3 * 5 * 5), expected(3 * 5, 2)},
{input(3 * 3 * 5 * 5 * 5 * 5), expected(3 * 5 * 5, 2)},
{input(3 * 3 * 3 * 3 * 3 * 3 * 5 * 5), expected(3 * 3 * 3 * 5, 2)},
{input(2 * 3 * 3), expected(2 * 3 * 3, 1)},
});
}
private final String input;
private final String[] expected;
public PowerCalculatorTest(String input, List<String> expected) {
this.input = input;
this.expected = expected.stream().toArray(String[]::new);
}
@Test
public void test() {
String[] actual = powerCalculator.power2(input);
assertThat(actual, sameBeanAs(expected));
}
private static String input(long input) {
return String.valueOf(input);
}
private static List<String> expected(long base, long exponent) {
return ImmutableList.of(String.valueOf(base), String.valueOf(exponent));
}
}
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
import java.math.BigInteger;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import static com.shazam.shazamcrest.MatcherAssert.assertThat;
import static com.shazam.shazamcrest.matcher.Matchers.sameBeanAs;
import static java.util.Arrays.asList;
The greatest common divisor of exponents is the final exponent of the result. If we have number 108 = 2 ^ 2 * 3 ^ 3
, the final exponent can be only 1
. That's why there is a check for this in the loop when we are about to check next divisor - so it "fails fast". It is also the reason while in the return statement, gcd
is used as exponent. Let's have a look at number 5184:
5184 = 2^6 * 3^4 = (2^3)^2 * (3^2)^2 = 8^2 * 9^2 = (8*9)^2 = 72^2
Greatest common divisor of 6 and 4 is 2.
Further optimizations that can be done is in finding prime factors of the number. If you tried it on square of large prime number such as 100000020383 ^ 2
the loop will have to do over 1 billion iterations before it finds first prime factor. This however is completely different problem so I will not be describing it here.
Upvotes: 1
Reputation:
Try this
long[] power2(long x) {
double logX = Math.log(x);
for (int b = 300; b > 1; --b) {
double a = Math.exp(logX / b);
if (Math.abs(a - Math.round(a)) < 0.0005)
return new long[] { Math.round(a), b };
}
return new long[] { x, 1 };
}
Upvotes: 0