Number Search (Most efficient) - java

Given that N is a random number (range 1 to 1000). We need to guess the N and for each guess, one of the following feedbacks may be given:
The guess is correct;
The guess is too large, so you should guess a smaller number;
The guess is too small, so you should guess a larger number.
In case 3, the value of N will increase by P, where P is another random number(range 1 to 200).
If the initial value of N=800 and P=150. You guess in the following sequence:
Example
How do you code the following especially when it involves two number (N and P). I was thinking of using Binary Search but the it would be a problem if we do not know the value of P.
This is my code as of now :
myGuess=0;
checkCode=0;
int lower = 1, upper = 999;
myGuess = (lower+upper)/2;
do{
if (checkCode == 2) {
upper = myGuess - 1;
}
else if (checkCode == 3){
lower = myGuess + 1;
upper += ran.nextInt(200); //Need to guess the P value
}
myGuess = (lower+upper)/2;
}while(checkCode!=1);

The first step is to obtain a working guessing system. This code provides a rough guide to a binary search approach. The second step would the be to analyze how to improve efficiency. (note: can restore some of the S.O.P() to see progress)
private static int doGuess()
{
int lowerBound = 1;
int upperBound = 1000;
int numberToGuess = ThreadLocalRandom.current().nextInt(upperBound) + 1;
int guess = 0;
int steps = 0;
int increases = 0;
while (guess != numberToGuess) {
++steps;
guess = (lowerBound + upperBound) / 2;
// System.out.printf("[%5d] Guessing %d (is: %d)%n",
// steps,
// guess,
// numberToGuess);
if (guess == numberToGuess) {
System.out.printf("Guessed %d in %d steps (%d increases)%n",
numberToGuess,
steps,
increases);
continue;
}
else if (guess > numberToGuess) {
// System.out.println("Guess is too high!");
// adjust upper bound to be guess
upperBound = guess;
}
else {
// System.out.println("Guess is too low; changing number");
numberToGuess += ThreadLocalRandom.current().nextInt(200) + 1;
// adjust lower bound to this guess
lowerBound = guess;
// the number moved, so adjust upper bound by max range
upperBound += 200;
// track increases
++increases;
}
}
return steps;
}
public static void main(String[] args)
{
List<Integer> steps = new ArrayList<>();
int iterations = 10;
for (int i = 0; i < iterations; ++i) {
steps.add(doGuess());
}
IntSummaryStatistics stats =
steps.stream().collect(IntSummaryStatistics::new,
IntSummaryStatistics::accept,
IntSummaryStatistics::combine);
System.out.println(stats);
}
Output:
Guessed 8838 in 145 steps (83 increases)
Guessed 6301 in 106 steps (59 increases)
Guessed 3239 in 58 steps (30 increases)
Guessed 5785 in 109 steps (58 increases)
Guessed 2547 in 56 steps (27 increases)
Guessed 16071 in 300 steps (164 increases)
Guessed 3847 in 54 steps (31 increases)
Guessed 3125 in 42 steps (24 increases)
Guessed 6708 in 93 steps (57 increases)
Guessed 7433 in 143 steps (74 increases)
IntSummaryStatistics{count=10, sum=1106, min=42, average=110.600000, max=300}
[Note: based upon quick simulations, the average across multiple runs is about 115, so efficiency improvements should reduce on average from 115 steps]
[Note: the amount of change in the code is different with each guess that is too low; a comment by the OP might suggest the increase is randomly chosen once, in which case the increase in the number to guess in the above code would need to change]
Edit:
Logically if guessing low moves the the number one is to guess, then using some sort of bias towards picking higher would seem to be logical. As Holger has suggest in the various comments, there are some ways to make adjustments.
I had attempted some basic adjustments prior to seeing Holger's suggestion; I then also attempted to implement his algorithm. However, I have not found the adjustments to make a marked improvement (and some are worse).
Using 100,000 runs, the standard binary search averaged 127.7 steps (note: up slightly from my earlier estimate based upon a lower run count). Assuming I implemented Holger's algorithm correctly, at 100,000 the average was 126.6 steps.
As I lack the math skills (and unfortunately time at the moment) to investigate further, it seems that simple modifications do not seem to radically change the efficiency of the algorithm on average. I did not investigate worse cases. It would be interesting to ask the question over on the Math StackExchange to see if they could provide any definite input. I did do a quick Google search, but did not have time to read the academic papers that might give some improvement (again, with unknown trade-offs in speed and algorithmic complexity).
It is, of course, possible I did not implement Holgen's suggestion properly. Here is the code I used (replacing the change in the guess calculation if too low) based straight from the comment:
if (tryHolgen) {
double risc = 200.0/(upperBound-lowerBound);
if (risc <= 1) {
guess = (upperBound + lowerBound) /2;
}
else {
guess = upperBound -
Math.max((int)((upperBound - lowerBound)/risc/2),1);
}
else {
guess = (lowerBound + upperBound) / 2;
}
I am curious if others have a better implementation than the straight binary search.
It is interesting, though, that a 1..1000 range with a standard binary search would take 8 steps on average with O(log n) complexity. By allowing the guess to change, it moves the average by about 120 steps.

I reworked my solution once I understood what you were trying to do. This will give you some statistics. The current solution incorporates a random number between 0 and 13 for each guess, as well as adding the lower and upper bound together and divide them by 2. Why 13? It seems like it's a sweet spot for this exact task.
public static void main(String args[]) throws IOException {
int numTests = 1000000;
long averageTries = 0;
int maxAttempts = 0;
int minAttempts = Integer.MAX_VALUE;
for (int i = 0; i < numTests; i++) {
int numAttempts = 0;
int answer = (int) (Math.random() * 1000) + 1;
int lower = 1;
int upper = 1000;
int myGuess;
do {
myGuess = (int) (((lower + upper) / 2) + (Math.random() * 14));
numAttempts++;
if (myGuess > answer) {
upper = myGuess;
} else if (myGuess < answer) {
lower = myGuess;
upper += (lower + upper) / 2;
answer += (int) (Math.random() * 200) + 1;
}
} while (myGuess != answer);
averageTries += numAttempts;
if (numAttempts > maxAttempts) {
maxAttempts = numAttempts;
}
if (numAttempts < minAttempts) {
minAttempts = numAttempts;
}
}
System.out.println("Average attempts (of " + numTests + " tests): " + (averageTries / numTests));
System.out.println("Most attempts in one run: " + maxAttempts);
System.out.println("Least attempts in one run: " + minAttempts);
}
Output:
Average attempts (of 1000000 tests): 266
Most attempts in one run: 72228
Least attempts in one run: 1

You can try to do something similar to binary search. Just consider that binary search requires the input to be sorted. If the input is not sorted you have to sort it yourself.
Rather than guessing a random number, just guess the one exactly in the middle of the partition. However compared with binary search which halves each time, in this case it's a moving target, so the bounds of the search need to be adjusted for that.

Related

How to find the 5th perfect number (which is 33550336)? The problem is taking forever to run

I am trying to write a Java method that checks whether a number is a perfect number or not.
A perfect number is a number that is equal to the sum of all its divisor (excluding itself).
For example, 6 is a perfect number because 1+2+3=6. Then, I have to write a Java program to use the method to display the first 5 perfect numbers.
I have no problem with this EXCEPT that it is taking forever to get the 5th perfect number which is 33550336.
I am aware that this is because of the for loop in my isPerfectNumber() method. However, I am very new to coding and I do not know how to come up with a better code.
public class Labreport2q1 {
public static void main(String[] args) {
//Display the 5 first perfect numbers
int counter = 0,
i = 0;
while (counter != 5) {
i++;
isPerfectNumber(i);
if (isPerfectNumber(i)) {
counter++;
System.out.println(i + " ");
}
}
}
public static boolean isPerfectNumber(int a) {
int divisor = 0;
int sum = 0;
for (int i = 1; i < a; i++) {
if (a % i == 0) {
divisor = i;
sum += divisor;
}
}
return sum == a;
}
}
This is the output that is missing the 5th perfect number
Let's check the properties of a perfect number. This Math Overflow question tells us two very interesting things:
A perfect number is never a perfect square.
A perfect number is of the form (2k-1)Ɨ(2k-1).
The 2nd point is very interesting because it reduces our search field to barely nothing. An int in Java is 32 bits. And here we see a direct correlation between powers and bit positions. Thanks to this, instead of making millions and millions of calls to isPerfectNumber, we will be making less than 32 to find the 5th perfect number.
So we can already change the search field, that's your main loop.
int count = 0;
for (int k = 1; count < 5; k++) {
// Compute candidates based on the formula.
int candidate = (1L << (k - 1)) * ((1L << k) - 1);
// Only test candidates, not all the numbers.
if (isPerfectNumber(candidate)) {
count++;
System.out.println(candidate);
}
}
This here is our big win. No other optimization will beat this: why test for 33 million numbers, when you can test less than 100?
But even though we have a tremendous improvement, your application as a whole can still be improved, namely your method isPerfectNumber(int).
Currently, you are still testing way too many numbers. A perfect number is the sum of all proper divisors. So if d divides n, n/d also divides n. And you can add both divisors at once. But the beauty is that you can stop at sqrt(n), because sqrt(n)*sqrt(n) = n, mathematically speaking. So instead of testing n divisors, you will only test sqrt(n) divisors.
Also, this means that you have to start thinking about corner cases. The corner cases are 1 and sqrt(n):
1 is a corner case because you if you divide n by 1, you get n but you don't add n to check if n is a perfect number. You only add 1. So we'll probably start our sum with 1 just to avoid too many ifs.
sqrt(n) is a corner case because we'd have to check whether sqrt(n) is an integer or not and it's tedious. BUT the Math Overflow question I referenced says that no perfect number is a perfect square, so that eases our loop condition.
Then, if at some point sum becomes greater than n, we can stop. The sum of proper divisors being greater than n indicates that n is abundant, and therefore not perfect. It's a small improvement, but a lot of candidates are actually abundant. So you'll probably save a few cycles if you keep it.
Finally, we have to take care of a slight issue: the number 1 as candidate. 1 is the first candidate, and will pass all our tests, so we have to make a special case for it. We'll add that test at the start of the method.
We can now write the method as follow:
static boolean isPerfectNumber(int n) {
// 1 would pass the rest because it has everything of a perfect number
// except that its only divisor is itself, and we need at least 2 divisors.
if (n < 2) return false;
// divisor 1 is such a corner case that it's very easy to handle:
// just start the sum with it already.
int sum = 1;
// We can stop the divisors at sqrt(n), but this is floored.
int sqrt = (int)Math.sqrt(n);
// A perfect number is never a square.
// It's useful to make this test here if we take the function
// without the context of the sparse candidates, because we
// might get some weird results if this method is simply
// copy-pasted and tested on all numbers.
// This condition can be removed in the final program because we
// know that no numbers of the form indicated above is a square.
if (sqrt * sqrt == n) {
return false;
}
// Since sqrt is floored, some values can still be interesting.
// For instance if you take n = 6, floor(sqrt(n)) = 2, and
// 2 is a proper divisor of 6, so we must keep it, we do it by
// using the <= operator.
// Also, sqrt * sqrt != n, so we can safely loop to sqrt
for (int div = 2; div <= sqrt; div++) {
if (n % div == 0) {
// Add both the divisor and n / divisor.
sum += div + n / div;
// Early fail if the number is abundant.
if (sum > n) return false;
}
}
return n == sum;
}
These are such optimizations that you can even find the 7th perfect number under a second, on the condition that you adapt the code for longs instead of ints. And you could still find the 8th within 30 seconds.
So here's that program (test it online). I removed the comments as the explanations are here above.
public class Main {
public static void main(String[] args) {
int count = 0;
for (int k = 1; count < 8; k++) {
long candidate = (1L << (k - 1)) * ((1L << k) - 1);
if (isPerfectNumber(candidate)) {
count++;
System.out.println(candidate);
}
}
}
static boolean isPerfectNumber(long n) {
if (n < 2) return false;
long sum = 1;
long sqrt = (long)Math.sqrt(n);
for (long div = 2; div <= sqrt; div++) {
if (n % div == 0) {
sum += div + n / div;
if (sum > n) return false;
}
}
return n == sum;
}
}
The result of the above program is the list of the first 8 perfect numbers:
6
28
496
8128
33550336
8589869056
137438691328
2305843008139952128
You can find further optimization, notably in the search if you check whether 2k-1 is prime or not as Eran says in their answer, but given that we have less than 100 candidates for longs, I don't find it useful to potentially gain a few milliseconds because computing primes can also be expensive in this program. If you want to check for bigger perfect primes, it makes sense, but here? No: it adds complexity and I tried to keep these optimization rather simple and straight to the point.
There are some heuristics to break early from the loops, but finding the 5th perfect number still took me several minutes (I tried similar heuristics to those suggested in the other answers).
However, you can rely on Euler's proof that all even perfect numbers (and it is still unknown if there are any odd perfect numbers) are of the form:
2i-1(2i-1)
where both i and 2i-1 must be prime.
Therefore, you can write the following loop to find the first 5 perfect numbers very quickly:
int counter = 0,
i = 0;
while (counter != 5) {
i++;
if (isPrime (i)) {
if (isPrime ((int) (Math.pow (2, i) - 1))) {
System.out.println ((int) (Math.pow (2, i -1) * (Math.pow (2, i) - 1)));
counter++;
}
}
}
Output:
6
28
496
8128
33550336
You can read more about it here.
If you switch from int to long, you can use this loop to find the first 7 perfect numbers very quickly:
6
28
496
8128
33550336
8589869056
137438691328
The isPrime method I'm using is:
public static boolean isPrime (int a)
{
if (a == 1)
return false;
else if (a < 3)
return true;
else {
for (int i = 2; i * i <= a; i++) {
if (a % i == 0)
return false;
}
}
return true;
}

My Birthday Problem code is not printing anything

I am an absolute beginner to learning programming and I was given this assignment:
Birthday problem. Suppose that people enter a room one at a time. How people must enter until two share a birthday? Counterintuitively, after 23 people enter the room, there is approximately a 50ā€“50 chance that two share a birthday. This phenomenon is known as the birthday problem or birthday paradox.
Write a program Birthday.java that takes two integer command-line arguments n and trials and performs the following experiment, trials times:
Choose a birthday for the next person, uniformly at random between 0 and nāˆ’1.
Have that person enter the room.
If that person shares a birthday with someone else in the room, stop; otherwise repeat.
In each experiment, count the number of people that enter the room. Print a table that summarizes the results (the count i, the number of times that exactly i people enter the room, and the fraction of times that i or fewer people enter the room) for each possible value of i from 1 until the fraction reaches (or exceeds) 50%.
For more information on the assignment
However, my code won't print. I would really appreciate if someone could help me find the problem to my assignment.
public class Birthday {
public static void main(String[] args) {
int n = Integer.parseInt(args[0]); //number of days
int trials = Integer.parseInt(args[1]);
boolean[] birthdays = new boolean[n];
int[] times = new int[n + 2]; //number of times i people entered the room
int r;
for (int t = 1; t <= trials; t++) {
for (int k = 0; k < n; k++) { //reset birthday
birthdays[k] = false;
}
for (int i = 1; i <= n; i++) { //number of times
r = (int) (Math.random() * (n - 1)); //random birthday
if (birthdays[r] = false) {
birthdays[r] = true;
continue;
}
else if (birthdays[r] = true) {
times[i]++; //number of times i people entered the room + 1
break;
}
}
}
int j = 1;
while ((double) times[j] / trials <= 0.5) {
System.out.print(j + "\t" + times[j] + "\t" + ((double) times[j] / trials));
j++;
System.out.println("");
}
}
}
I can spot two errors from your code
As Scary Wombat pointed out, you are miss double equal sign inside of your if statement.
The assignment is asking you to calculate "fraction of times that i or fewer people enter the room", meaning you need to do a summation for the first i indices and divided by trials.
For example, among 1 million trials, the fraction in which first duplicate birthday happens when 4th person enters is
(times[0] + times[1] + times[2] + times[3])/ 1000000
Here is what I got:
1 0 0.0
2 2810 0.00281
3 5428 0.008238
4 8175 0.016413
As you can see the fraction is calculated by adding the first three elements together and then divided by 1000000 (2810 + 5428 + 8175 = 16413) / 1000000 = 0.016413
The way you are calculating the fraction ((double) times[j] / trials) is not correct.
You are not adding the previous counts as shown in the example. To do so, you can create a new variable to store the sums of previous counts. and use it as your while loop condition. For instance, see below..
csum += times[j]; // this adds the previous counts into a cumulative sum.
This cumulative sum is supposed to be the one u use to divide by trials to get your probability. Cheers!

To find prime factors of a given number, why set the second statement of the for loop to i * i <= userInput instead of i <= userInput?

I'm new to Java and am trying to solve the problem of finding all prime factors of a given number. I have been told if I set the second statement of the for loop to i * i <= userInput instead of i <= userInput, the program will be much more efficient.
I can't get my head around as to why this is the case. What is the reason behind it?
Scanner sc = new Scanner(System.in);
int userInput = sc.nextInt();
sc.close();
System.out.println("Prime factors of " + userInput + " include: ");
for (int i = 2; i * i <= userInput; i++){
while(userInput % i == 0){
System.out.print(i + " ");
userInput /= i;
}
}
if (userInput > 1){
System.out.print(userInput);
} else {
System.out.print("");
}
In fact this code will find not only prime factors - it will search for all factors. If your number is Y can be represented as X * X, any factor below X will have matching factor greater than X. So you need only to check half of all cases. For 14 when you found that 2 is factor, matching factor is 7, so you don't need to check matching factor of 7. Thats why your condition is contain i*i.
Because it cuts the search space from N to the square root of N.
You could write as:
for (int i = 2; i <= Math.sqrt(userInput); i++)
This reduces the number of iterations of the loop from N to square root of N. Eg: if N = 16, then the iterations would be reduced to 4. For a smaller N, this does not seem much but you will see the difference as N increases in value.
Imagine N=16, the factors are:
1,16 -- you are disallowing this anyway but I present if for completeness
2,8
4,4
------ The ones below are already covered and do not need to be looped through --------
8,2
16,1
A faster way to do that would be to find an incrementer and that is linked here.

sum(1/prime[i]^2) >= 1?

While trying to devise an algorithm, I stumbled upon this question. It's not homework.
Let P_i = an array of the first i primes. Now I need the smallest i such that
Sum<n=0..i> 1 / (P_i[n]*P_i[n]) >= 1.
(if such i exists).
An approximation for the i'th prime is i*log(i). So I tried this in Java:
public static viod main(String args[]) {
double sum = 0.0;
long i = 2;
while(sum<1.0) {
sum += 1.0 / (i*Math.log(i)*i*Math.log(i));
i++;
}
System.out.println(i+": "+sum);
}
However the above doesn't finish because it converges to 0.7. However 1/100000000^2 rounds to 0.0 in Java, so that's why it doesn't work. For the same reason it doesn't even work if you replace the 6th line with
sum += 1.0 / (i*i)
while that should reach 1 if I'm not mistaken, because the sum should incease faster than 1/2^i and the latter converges to 1. In other words, this shows that Java rounding causes the sum to not reach 1. I think that the minimum i of my problem should exist.
On the maths side of this question, not the java side:
If I understand the problem, there is no solution (no value of i).
For any finite set P_i of primes {p_1, p_2,...p_i} let N_i be the set of all integers up to p_i, {1,2,3,...,p_i}. The sum 1/p^2 (for all p_n in P_i) will be less than the sum of all 1/x^2 for x in N_i.
The sum of 1/x^2 tends to ~1.65 but since 1 will never be in the set of primes, the sum is limited by ~0.65
You cannot use double for this, because it is not uniform. You should use fractions. I found this class https://github.com/kiprobinson/BigFraction
Then I tried to find whats happening :
public static void main(String args[]) {
BigFraction fraction = BigFraction.valueOf(1, 4);
int n = 10000000, status = 1, num = 3;
double limit = 0.4;
for (int count = 2; count <= n;) {
for (int j = 2; j <= Math.sqrt(num); j++) {
if (num % j == 0) {
status = 0;
break;
}
}
if (status != 0) {
fraction = fraction.add(BigFraction.valueOf(1,BigInteger.valueOf(num).multiply(BigInteger.valueOf(num))));
if (fraction.doubleValue() >= limit){
System.out.println("reached " + limit + " with " + count + " firsts prime numbers");
limit += 0.01;
}
count++;
}
status = 1;
num++;
}
}
This is having this output :
reached 0.4 with 3 firsts prime numbers
reached 0.41000000000000003 with 4 firsts prime numbers
reached 0.42000000000000004 with 5 firsts prime numbers
reached 0.43000000000000005 with 6 firsts prime numbers
reached 0.44000000000000006 with 8 firsts prime numbers
reached 0.45000000000000007 with 22 firsts prime numbers
And nothing more in a minute. I debug it and found that it grows extremely slower and slower, I do not think, it can reach 1 even in infinity :) (but dont know how to prove it).
I guess you might loose the precision you need when you use default Math.log multiplied by float i. I think this can be handled by using an appropriate RoundingMode. Please see setRoundingMode

Bounding this program to determine the sum of reciprocal integers not containing zero

Let A denote the set of positive integers whose decimal representation does not contain the digit 0. The sum of the reciprocals of the elements in A is known to be 23.10345.
Ex. 1,2,3,4,5,6,7,8,9,11-19,21-29,31-39,41-49,51-59,61-69,71-79,81-89,91-99,111-119, ...
Then take the reciprocal of each number, and sum the total.
How can this be verified numerically?
Write a computer program to verify this number.
Here is what I have written so far, I need help bounding this problem as this currently takes too long to complete:
Code in Java
import java.util.*;
public class recip
{
public static void main(String[] args)
{
int current = 0; double total = 0;
while(total < 23.10245)
{
if(Integer.toString(current).contains("0"))
{
current++;
}
else
{
total = total + (1/(double)current);
current++;
}
System.out.println("Total: " + total);
}
}
}
This is not that hard when approached properly.
Assume for example that you want to find the sum of reciprocals of all integers starting (i.e. the left-most digits) with 123 and ending with k non-zero digits. Obviously there are 9k such integers and the reciprocal of each of these integers is in the range 1/(124*10k) .. 1/(123*10k). Hence the sum of reciprocals of all these integers is bounded by (9/10)k/124 and (9/10)k/123.
To find bounds for sum of all reciprocals starting with 123 one has to add up the bounds above for every k>=0. This is a geometric serie, hence it can be derived that the sum of reciprocals of integers starting with 123 is bounded by 10*(9/10)k/124 and 10*(9/10)k/123.
The same method can of course be applied for any combination of left-most digits.
The more digits we examine on the left, the more accurate the result becomes.
Here is an implementation of this approach in python:
def approx(t,k):
"""Returns a lower bound and an upper bound on the sum of reciprocals of
positive integers starting with t not containing 0 in its decimal
representation.
k is the recursion depth of the search, i.e. we append k more digits
to t, before approximating the sum. A larger k gives more accurate
results, but takes longer."""
if k == 0:
return 10.0/(t+1), 10.0/t
else:
if t > 0:
low, up = 1.0/t, 1.0/t
else:
low, up = 0, 0
for i in range(10*t+1, 10*t+10):
l,u = approx(i, k-1)
low += l
up += u
return low, up
Calling approx(0, 8) for example gives the lower and upper bound:
23.103447707... and 23.103448107....
which is close to the claim 23.10345 given by the OP.
There are methods that converge faster to the sum in question, but they require more math.
A much better approximation of the sum can be found here. A generalization of the problem are the Kempner series.
For all values of current greater than some threshold N, 1.0/(double)current will be sufficiently small that total does not increase as a result of adding 1.0/(double)current. Thus, the termination criterion should be something like
while(total != total + (1.0/(double)current))
instead of testing against the limit that is known a priori. Your loop will stop when current reaches this special value of N.
I suspect that casting to string and then checking for the character '0' is the step that takes too long. If you want to avoid all zeroes, might help to increase current thus:
(Edited -- thanks to Aaron McSmooth)
current++;
for( int i = 10000000; i >= 10; i = i / 10 )
{
if ( current % i ) == 0
{
current = current + ( i / 10 );
}
}
This is untested, but the concept should be clear: whenever you hit a multiple of a power of ten (e.g. 300 or 20000), you add the next lower power of 10 (in our examples 10 + 1 and 1000 + 100 + 10 + 1, respectively) until there are no more zeroes in your number.
Change your while loop accordingly and see if this doesn't help performance to the point were your problem becomes manageable.
Oh, and you might want to restrict the System.out output a bit as well. Would every tenth, one hundreth or 10000th iteration be enough?
Edit the second:
After some sleep, I suspect my answer might be a little short-sighted (blame the late hour, if you will). I simply hoped that, oh, one million iterations of current would get you to the solution and left it at that, instead of calculating the correction cases using log( current ) etc.
On second thought, I see two problems with this whole problem. One is that your target number of 23.10345 is a leeeeettle to round for my tastes. After all, you are adding thousands of items like "1/17", "1/11111" and so on, with infinite decimal representations, and it is highly unlikely that they add up to exactly 23.10345. If some specialist for numerical mathematics says so, fine -- but then I'd like to see the algorithm by which they arrived at this conclusion.
The other problem is related to the first and concerns the limited in-memory binary representation of your rational numbers. You might get by using BigDecimals, but I have my doubts.
So, basically, I suggest you reprogram the numerical algorithm instead of going for the brute force solution. Sorry.
Edit the third:
Out of curiosity, I wrote this in C++ to test my theories. It's run for 6 minutes now and is at about 14.5 (roughly 550 mio. iterations). We'll see.
Current version is
double total = 0;
long long current = 0, currPowerCeiling = 10, iteration = 0;
while( total < 23.01245 )
{
current++;
iteration++;
if( current >= currPowerCeiling )
currPowerCeiling *= 10;
for( long long power = currPowerCeiling; power >= 10; power = power / 10 )
{
if( ( current % power ) == 0 )
{
current = current + ( power / 10 );
}
}
total += ( 1.0 / current );
if( ! ( iteration % 1000000 ) )
std::cout << iteration / 1000000 << " Mio iterations: " << current << "\t -> " << total << std::endl;
}
std::cout << current << "\t" << total << std::endl;
Calculating currPowerCeiling (or however one might call this) by hand saves some log10 and pow calculations each iteration. Every little bit helps -- but it still takes forever...
Edit the fourth:
Status is around 66,000 mio iterations, total is up to 16.2583, runtime is at around 13 hours. Not looking good, Bobby S. -- I suggest a more mathematical approach.
How about storing the current number as a byte array where each array element is a digit 0-9? That way, you can detect zeroes very quickly (comparing bytes using == instead of String.contains).
The downside would be that you'll need to implement the incrementing yourself instead of using ++. You'll also need to devise a way to mark "nonexistent" digits so that you don't detect them as zeroes. Storing -1 for nonexistent digits sounds like a reasonable solution.
For a signed 32-bit integer, this program will never stop. It will actually converge towards -2097156. Since the maximum harmonic number (the sum of integral reciprocals from 1 to N) of a signed 32-bit integer is ~14.66, this loop will never terminate, even when current wraps around from 2^31 - 1 to -2^31. Since the reciprocal of the largest negative 32-bit integer is ~-4.6566e-10, every time current returns to 0, the sum will be negative. Given that the largest number representable by a double such that number + + 1/2^31 == number is 2^52/2^31, you get roughly -2097156 as the converging value.
Having said that, and assuming you don't have a direct way of calculating the harmonic number of an arbitrary integer, there are a few things you can do to speed up your inner loop. First, the most expensive operation is going to be System.out.println; that has to interact with the console in which case your program will eventually have to flush the buffer to the console (if any). There are cases where that may not actually happen, but since you are using that for debugging they are not relevant to this question.
However, you also spend a lot of time determining whether a number has a zero. You can flip that test around to generate ranges of integers such that within that range you are guaranteed not to have an integer with a zero digit. That is really simple to do incrementally (in C++, but trivial enough to convert to Java):
class c_advance_to_next_non_zero_decimal
{
public:
c_advance_to_next_non_zero_decimal(): next(0), max_set_digit_index(0)
{
std::fill_n(digits, digit_count, 0);
return;
}
int advance_to_next_non_zero_decimal()
{
assert((next % 10) == 0);
int offset= 1;
digits[0]+= 1;
for (int digit_index= 1, digit_value= 10; digit_index<=max_set_digit_index; ++digit_index, digit_value*= 10)
{
if (digits[digit_index]==0)
{
digits[digit_index]= 1;
offset+= digit_value;
}
}
next+= offset;
return next;
}
int advance_to_next_zero_decimal()
{
assert((next % 10)!=0);
assert(digits[0]==(next % 10));
int offset= 10 - digits[0];
digits[0]+= offset;
assert(digits[0]==10);
// propagate carries forward
for (int digit_index= 0; digits[digit_index]==10 && digit_index<digit_count; ++digit_index)
{
digits[digit_index]= 0;
digits[digit_index + 1]+= 1;
max_set_digit_index= max(digit_index + 1, max_set_digit_index);
}
next+= offset;
return next;
}
private:
int next;
static const size_t digit_count= 10; // log10(2**31)
int max_set_digit_index;
int digits[digit_count];
};
What the code above does is to iterate over every range of numbers such that the range only contains numbers without zeroes. It works by determining how to go from N000... to N111... and from N111... to (N+1)000..., carrying (N+1) into 1(0)000... if necessary.
On my laptop, I can generate the harmonic number of 2^31 - 1 in 8.73226 seconds.
public class SumOfReciprocalWithoutZero {
public static void main(String[] args) {
int maxSize=Integer.MAX_VALUE/10;
long time=-System.currentTimeMillis();
BitSet b=new BitSet(maxSize);
setNumbersWithZeros(10,maxSize,b);
double sum=0.0;
for(int i=1;i<maxSize;i++)
{
if(!b.get(i))
{
sum+=1.0d/(double)i;
}
}
time+=System.currentTimeMillis();
System.out.println("Total: "+sum+"\nTimeTaken : "+time+" ms");
}
static void setNumbersWithZeros(int srt,int end,BitSet b)
{
for(int j=srt;j<end;j*=10)
{
for(int i=1;i<=10;i++)
{
int num=j*i;
b.set(num);
}
if(j>=100)
setInbetween(j, b);
}
}
static void setInbetween(int strt,BitSet b)
{
int bitToSet;
bitToSet=strt;
for(int i=1;i<=10;i++)
{
int nxtInt=-1;
while((nxtInt=b.nextSetBit(nxtInt+1))!=strt)
{
b.set(bitToSet+nxtInt);
}
nxtInt=-1;
int lim=strt/10;
while((nxtInt=b.nextClearBit(nxtInt+1))<lim)
{
b.set(bitToSet+nxtInt);
}
bitToSet=strt*i;
}
}
}
This is an implementation using BitSet.I calculated the sum of reciprocal's for all integer's in range (1-Integer.MAX_VALUE/10).The sum comes upto 13.722766931560747.This is the maximum I could calculate using BitSet since the maximum range for BitSet is Integer.MAX_VALUE.I need to divide it by 10 and limit the range to avoid overflow.But there is significant improvement in speed.I'm just posting this code in-case it might give you some new idea to improve your code.(Increase your memory using the VM argument -Xmx[Size>350]m)
Output:
Total: 13.722766931560747
TimeTaken : 60382 ms
UPDATE:
Java Porting of a previous , deleted answer :
public static void main(String[] args) {
long current =11;
double tot=1 + 1.0/2 + 1.0/3 + 1.0/4 + 1.0/5 + 1.0/6 + 1.0/7 + 1.0/8 + 1.0/9;
long i=0;
while(true)
{
current=next_current(current);
if(i%10000!=0)
System.out.println(i+" "+current+" "+tot);
for(int j=0;j<9;j++)
{
tot+=(1.0/current + 1.0/(current + 1) + 1.0/(current + 2) + 1.0/(current + 3) + 1.0/(current + 4) +
1.0/(current + 5) + 1.0/(current + 6) + 1.0/(current + 7) + 1.0/(current + 8));
current += 10;
}
i++;
}
}
static long next_current(long n){
long m=(long)Math.pow(10,(int)Math.log10(n));
boolean found_zero=false;
while(m>=1)
{
if(found_zero)
n+=m;
else if((n/m)%10==0)
{
n=n-(n%m)+m;
found_zero=true;
}
m=m/10;
}
return n;
}

Categories