I'm trying to write a short program that plays a dice game (normal 6 sided dice) for me. The first roll's number is added to the score. After the first roll, if I roll a 6 then the game stops and the score is recorded (without adding the 6). If a 6 is rolled on the first roll, that's fine and it's added like any other number 1 through 5. I'm trying to run a bunch of iterations of this game such that I have a long list of scores pre-bust (a bust being a rolled 6). I rearrange those scores to be in order from smallest to largest and then find the median of the list which is the score at which it is optimal to stop.
For some reason I keep getting 13 when I run the program but I know for a fact that the answer should be 15. Would using Random in Java would have some sort of effect on the median? I don't exactly know how Random generates the numbers and whether it creates them with equal opportunity. Also, is there anything that just pops out that shouldn't work?
import java.util.*;
public class DiceRoller {
private static Random r = new Random();
private static final int games = 10001;
private static int[] totalScores = new int[games];
private static int index = 0;
public static void main(String[] args) {
int score = 0; boolean firstRoll = true;
while (index < games) {
int roll = roll();
if (firstRoll) {
score += roll;
firstRoll = false;
} else {
if (roll == 6) {
totalScores[index] = score;
index++;
score = 0; firstRoll = true;
} else {
score += roll;
}
}
}
System.out.println("The median is " + median() + ".");
}
public static int roll() {
return r.nextInt(6) + 1;
}
public static int median() {
Arrays.sort(totalScores);
int temp = totalScores[games / 2];
return temp;
}
}
You get 13 because that's the correct result. A little mathematics: if S is the random variable representing the score of any one of these games, then you can consider the Probability generating function f(z) of S. From the description of the game, this probability generating function satisfies the equation:
f(z) = (z + z^2 + z^3 + z^4 + z^5 + z^6) / 36 + f(z)(z + z^2 + z^3 + z^4 + z^5) / 6
This takes a bit of thought, or familiarity with this sort of construction: the left-hand term on the right-hand side takes account of the probabilities of getting 1 through 6 in a simple 2-roll game; the right-hand term involving f(z) takes account of games involving 3 or more rolls, expressing them in terms of the final pre-6 roll (which must be in the range 1 through 5) and the preceding rolls, whose probabilities we can express recursively using f again.
Anyway, after getting this far, one can rearrange to describe f as a rational function of z, and then expand as a power series, which begins:
f(z) = 1/36*z + 7/216*z^2 + 49/1296*z^3 + 343/7776*z^4 + 2401/46656*z^5 + 16807/279936*z^6 + 63217/1679616*z^7 + 388087/10077696*z^8 + 2335585/60466176*z^9 + 13681927/362797056*z^10 + 77103313/2176782336*z^11 + 409031959/13060694016*z^12 + 2371648321/78364164096*z^13 + 13583773735/470184984576*z^14 + ...
(I used Pari/GP to get this.)
The coefficient of z^k then describes the probability of the value of the game being k; thus there's a 1 in 36 chance of the score being 1, a 7 in 216 chance of getting 2, and so on. The sum of the first 12 coefficients is 0.472828864487196328..., while the sum of the first 13 coefficients is 0.5030933144224321950968.... So the median is indeed 13.
To provide an independent check, I wrote a quick Python program:
from __future__ import division
import random
def roll():
return random.randint(1, 6)
def play():
score = roll()
while True:
throw = roll()
if throw == 6:
break
score += throw
return score
all_scores = sorted(play() for _ in xrange(1000001))
print "median is: ",all_scores[len(all_scores) // 2]
print "fraction of scores <= 12: ",all_scores.index(13) / len(all_scores)
print "fraction of scores <= 13: ",all_scores.index(14) / len(all_scores)
Sure enough, here are the results:
iwasawa:~ mdickinson$ python dice_game.py
median is: 13
fraction of scores <= 12: 0.472811527188
fraction of scores <= 13: 0.502863497137
So to answer your question, the results you're seeing are not evidence of any sort of weakness in Java's random number generation.
Random is not perfectly random and has some deficiencies. However for this use case you are very unlikely to notice the difference. You can assume every value 1 to 6 is equally likely.
For comparison here is another solution which counts the number of occurrences of a total rather than recording every value. As you can see this performs well even if you have 1000x more games. This works best when you have a small number of outcomes and a high number duplicates. It is naturally sorted.
import java.util.Random;
public class DiceRoller {
private static final int MAX_VALUE = 300; // assume at most this total
private static final int GAMES = 10000001;
public static void main(String... args) {
int[] count = new int[MAX_VALUE];
Random rand = new Random();
for (int i = 0; i < GAMES; i++)
count[totalScore(rand)]++;
System.out.println("The median is " + median(count, GAMES) + ".");
}
private static int median(int[] count, int games) {
int findTotal = games/2;
for (int i = 0; i < count.length; i++) {
findTotal -= count[i];
if (findTotal <= 0) return i;
}
throw new AssertionError();
}
private static int totalScore(Random rand) {
int total = rand.nextInt(6) + 1;
for(int n;(n = rand.nextInt(6) + 1) != 6;)
total += n;
return total;
}
}
Here is some code that shows you the distribution of the results. It doesn't really answer the question, but maybe it helps you in your research.
package so7297660;
import java.util.Random;
public class DiceRoller {
private static final int N = 10000000;
private static final Random r = new Random();
private static final int[] result = new int[100];
public static int roll() {
return r.nextInt(6) + 1;
}
private static int singleGame() {
int score = roll();
while (true) {
int roll = roll();
if (roll == 6) {
return score;
} else {
score += roll;
}
}
}
private static int median() {
int n = 0;
for (int i = 0; i < result.length; i++) {
if (n + result[i] >= N / 2) {
return i;
}
n += result[i];
}
throw new IllegalStateException();
}
public static void main(String[] args) {
for (int i = 0; i < N; i++) {
int score = singleGame();
int index = Math.min(score, result.length - 1);
result[index]++;
}
for (int i = 0; i < result.length; i++) {
System.out.println(i + "\t" + result[i]);
}
System.out.println("median\t" + median());
}
}
Related
Working on a problem in which I take a user's input (1-10) and guess what number they are thinking of using binary search, and update the range dependent on their answer (e.g. if it is greater than 5, I update the lowerLimit to 6) but am having trouble with the logic.
I use the middle cell as reference by adding 1 to the middle cell when they say it is greater than it, but I believe this is where I get confused. I can't figure out how to intertwine my if/else statement to update the number correctly.
Main method:
public class Main {
public static void main(String[] args) {
// test your program here
GuessingGame game = new GuessingGame();
game.play(1,10);
}
}
GuessingGame method (play method is the one I'm working with):
import java.util.Scanner;
public class GuessingGame {
private Scanner reader;
public GuessingGame() {
// use only this scanner, othervise the tests do not work
this.reader = new Scanner(System.in);
}
public void play(int LL, int UL) {
instructions(LL, UL);
int limit = howManyTimesHalvable(UL - LL);
int finalNumber = 0;
int midPoint = average(LL, UL);
int avgLL;
int avgUL;
for(int i = 0; i < limit; i++){
if(isGreaterThan(midPoint)){
midPoint++;
LL = midPoint;
midPoint = average(UL,LL);
finalNumber = LL;
}else{
midPoint--;
UL = midPoint;
midPoint = average(UL,LL);
finalNumber = LL;
}
if(UL == LL){
break;
}
}
System.out.println("Your number is : " + finalNumber);
}
public boolean isGreaterThan(int value){
System.out.println("Is your number greater than " + value + "?");
return reader.nextLine().equals("y");
}
public int average(int firstNumber, int secondNumber){
int total = firstNumber + secondNumber ;
return total / 2;
}
public void instructions(int lowerLimit, int upperLimit) {
int maxQuestions = howManyTimesHalvable(upperLimit - lowerLimit);
System.out.println("Think of a number between " + lowerLimit + "..." + upperLimit + ".");
System.out.println("I promise you that I can guess the number you are thinking with " + maxQuestions + " questions.");
System.out.println("");
System.out.println("Next I'll present you a series of questions. Answer them honestly.");
System.out.println("");
}
// a helper method:
public static int howManyTimesHalvable(int number) {
// we create a base two logarithm of the given value
// Below we swap the base number to base two logarithms!
return (int) (Math.log(number) / Math.log(2)) + 1;
}
}
I would like to know how to update the ranges accordingly, when a user says that the number that they've guessed is higher or lower than what is shown to them.
Edit, example entries:
Looking for number 9,
LL: 1
UL: 10
limit:4
finalNumber:0
midPoint:5
i: 0
Is your number greater than 5?
y
LL: 6
UL: 10
limit:4
finalNumber:6
midPoint:8
i: 1
Is your number greater than 8?
LL: 9
UL: 10
limit:4
finalNumber:9
midPoint:9
i: 2
Is your number greater than 9?
n
LL: 9
UL: 8
limit:4
finalNumber:9
midPoint:8
i: 3
Is your number greater than 8?
y
Your number is : 9
You should not have midPoint--;
Since you are asking the question as "is Grater than"? Your new upper limit should be midpoint if the answer is no.
My question is why isn't the code generating the amount of numbers that the users enters? Right now the code is only generating one number. Here is the original question given to me:
"In your main method, prompt the user for a number n. Write a method
called assessRandomness that generates a random number between 1 and
100 'n' times and return the percentage of times the number was less than
or equal to 50. Call your assessRandomness method from main and display
the result to the user from main. Do not interact with the user from
within the assessRandomness method."
output:
How many random numbers should I generate? 10
<assume the random numbers generated were 11 7 50 61 52 3 92 100 81 66>
40% of the numbers were 50 or less
my code:
public static void main(String[] args) {
Scanner in = new Scanner(System.in);
System.out.println("how many random numbers should I generate?: ");
int number = in.nextInt();
assessRandomness(number);
}
public static double assessRandomness(int n){
int random = (int)(Math.random()*100);
int randomNumbersLessthan50 = 0;
if (random <= 50)
{
double getPercentage = random/randomNumbersLessthan50;
}
else
{
System.out.println(random);
}
return random;
}
I don't see any kind of loop within assessRandomness.
Try
for(int x = 1; x <= n; x++){ ... }
as first line in assessRandomness, it should finally look like
public static double assessRandomness(int n){
int counterLessThan50 = 0;
for ( int x = 1; x <= n; x++)
if( (int)(Math.random()*100) <= 50 ) counterLessThan50++;
return (double) counterLessThan50 / n;
}
There's no repetition in your code to do something n times.
Here's one way to do it in one line using a stream:
public static double assessRandomness(int n) {
return Stream.generate(Math::random).limit(n).map(r -> r * 100 + 1).filter(r -> r <= 50).count() / (double)n;
}
Note that converting Math.random() to a number in the range 1-100 is pointless; this will give the same result:
public static double assessRandomness(int n) {
return Stream.generate(Math::random).limit(n).filter(n -> n < .5).count() / (double)n;
}
And is easier to read.
At the moment, your assessRandomness method never uses the variable n.
At first you should initialize a variable which counts the number of created randoms that are bigger than 50 (this will be your retutn value). You should then do a loop from 0 until n. For each loop run you should create a random value between 0 and 100. Then you should check wether the value is bigger than 50. If so, count up your previously created variable. When the loop has finished, return the count variable and print it in the main method.
This should help you understand better how to do something like this.
public static void main(String[] args) {
System.out.println("how many random numbers should I generate?: ");
Scanner in = new Scanner(System.in);
int number = in.nextInt();
int[] arrayPlaceHolderInMainMethod = new int[number];
arrayPlaceHolderInMainMethod = generateRandomNumberArray(number);
assessRandomness(arrayPlaceHolderInMainMethod);
}
public static void assessRandomness(int[] inputArray) {
int randomNumbersLessthan50 = 0;
int randomNumbersGreaterthan50 = 0;
int random = 0;
for (int i = 0; i < inputArray.length; i++) {
random = inputArray[i];
}
if (random <= 50) {
randomNumbersLessthan50 += 1;
} else {
randomNumbersGreaterthan50 += 1;
}
System.out.println(">50: " + randomNumbersGreaterthan50 + " Less: " + randomNumbersLessthan50);
}
public static int[] generateRandomNumberArray(int numberPickedByUser) {
int[] arrayOfRandomNumbers = new int[numberPickedByUser];
for (int i = 0; i < numberPickedByUser; i++) {
arrayOfRandomNumbers[i] = (int) (Math.random() * 100 + 1);
}
return arrayOfRandomNumbers;
}
I am having an issue with a dice rolling program that I'm trying to create (just uses console).
Here is the dice rolling class file itself:
import java.util.Random;
public class rtd
{
public static int[] rollthedice(int numSides, int numRolls)
{
int[] rollCounter = new int[numSides];
for (int counter = 0; counter < numRolls; counter++)
{
Random randRoll = new Random();
int die = randRoll.nextInt(numSides) + 1;
if ((counter + 1) == die)
{
rollCounter[counter] = die;
}
}
return rollCounter;
}
}
The problem with the class is that for some reason, the for loop refuses to function when I am testing the class to see if it works with the following class in the same folder:
public class tester
{
public static void main(String[] args)
{
rtd roller = new rtd();
int[] results = new int[6];
results = rtd.rollthedice(6, 20);
int rollNumber = 1;
for (int counter = 0; counter < results.length; counter++)
{
System.out.println(rollNumber + " " + results[counter]);
rollNumber++;
}
}
}
When I run the "tester" class, the results show that only one roll was completed, meaning that the for loop did not repeat the code for the specified number of rolls. Can anyone offer me a possible solution or explanation? If you see other flaws, let me know.
I believe the issue may be in my IDE (BlueJ).
First of all, you should follow the naming conventions of the language. I know you just started. Please find time to read.
I modified your code without changing the class and method names even though I wanted to. I will leave it to you as an exercise.
Here's the modified version of rtd class. Please see the comments in source code.
public class rtd
{
public static int[] rollthedice(int numSides, int numRolls)
{
// An array of total number of rolls (NOT sides) to save the result of all rolls
int[] rollCounter = new int[numRolls];
// Let's roll n-th times where n is numRolls
for (int counter = 0; counter < numRolls; counter++)
{
// Let's get a random number between 1 to numSides (A die usually has 6 sides with 1 to 6 dots)
int randomSide = getRand(1, numSides);
// Let's store the current roll result in array
rollCounter[counter] = randomSide;
}
return rollCounter;
}
/**
* This method returns a number between a given range inclusive
*/
public static int getRand(int min, int max)
{
return min + (int)(Math.random() * ((max - min) + 1));
}
}
Also, you can improve your tester class like this-
public class tester
{
public static void main(String[] args)
{
int[] results = rtd.rollthedice(6, 20);
// Since counter starts with 0, we always add 1 so we can read from 1 to 20
for (int counter = 0; counter < results.length; counter++)
{
System.out.println("Roll Number: " + (counter + 1) + " Side Picked: " + results[counter]);
}
}
}
The comments in source should be pretty easy to understand. If you have questions, please ask.
I have been having a lot of trouble with this and it is due soon, and I was wondering if anyone knows how to fix my problem. I have to create a program where:
"Your task is to implement a number guesser that works on the principle of a binary search. In each step, the computer cuts the query interval in half. When the interval contains a single number, it proclaims the answer. The user of the program selects a number between 1 and 100. The computer is then asked to guess the number."
The sample output goes:
Is your number greater than 50? (computer is asking this)
no (user responds with yes or no)
Is your number greater than 25?
no
Is your number greater than 13?
no
Is your number greater than 7?
yes
Is your number greater than 10?
yes
Is your number greater than 12?
yes
Is your number 13?
yes
13 is the answer. (computer declares final answer)
Thank you for playing the guessing game.
My sample output in contrast goes:
Is your number greater than 50?
no
Is your number greater than 25?
no
Is your number greater than 13?
no
Is your number greater than 7?
yes
Is your number greater than 10?
yes
Is your number greater than 11?
yes
Is your number greater than 12?
yes
Is your number 12?
yes
12 is the answer.
Thank you for playing the guessing game.
with some variation based on what edits I make.
The code is as follows:
//import statements
import java.util.Scanner;
import java.util.ArrayList;
public class Numbers
{
//constant to initialize the ArrayList
private final int AT_MOST = 100;
//anArrayList of type ArrayList<Integer> which is to hold the values from 1 - 100
private ArrayList<Integer> anArrayList;
/**
* Constructor of the Numbers() class which initializes all of the instance fields
*/
public Numbers()
{
anArrayList = new ArrayList<Integer>();
int i =0;
//while loop to initialize anArrayList with values from 1-100
while(i < AT_MOST)
{
anArrayList.add(i+1);
i++;
}
}
public void search()
{
int low = 0;
int high = anArrayList.size();
int i = 0;
int j = 0;
while(low <= high)
{
int mid = (low + high)/2;
mid = anArrayList.get(mid - 1);
Scanner in = new Scanner(System.in);
System.out.println("Is your number greater than " + mid + "?");
String answer = in.nextLine();
if(answer.equalsIgnoreCase("yes"))
{
low = mid + 1;
}
else if (answer.equalsIgnoreCase("no"))
{
high = mid - 1;
low++;
}
if(low == high+1)
{
Scanner in2 = new Scanner(System.in);
System.out.println("Is your number " + mid + "?");
String finalAnswer = in2.nextLine();
if(finalAnswer.equalsIgnoreCase("yes"))
{
System.out.println(mid + " is the answer.");
System.out.println("Thank you for playing the guessing game.");
low = high + 1;;
}
else
{
System.out.println("Please play again, something went wrong!");
low = high + 1;
}
}
}
}
}
Of course this also has a tester class which is relatively short:
public class NumbersGuesser
{
public static void main(String[] args)
{
//creates a new numbers object
Numbers newNumber = new Numbers();
//run method is called, game is played.
newNumber.search();
}
}
Since you made an effort to solve the problem, I went ahead and restructured your Numbers class.
The first thing I did was get rid of the ArrayList. You can just as easily do arithmetic on integers as traverse the ArrayList.
I added a couple of integers, distance and direction. After each guess, the distance is cut in half. The computer guesses high or low until the distance is reduced to zero. At that point, the number is somewhere between the low and the high, inclusive.
The direction just tells us whether we need to guess lower (-1) or higher (+1) for the next guess.
I pulled the high low scanner code into its own method. It looks confusing at first, but all it does is tell us whether to guess higher (true) or lower (false). By moving this code into its own method, I could concentrate on the guessing logic.
Finally, I closed the scanner at the end of the processing.
//import statements
import java.util.Scanner;
public class Numbers {
// constants to start the game
private final int AT_LEAST = 0;
private final int AT_MOST = 100;
/**
* Constructor of the Numbers() class
*/
public Numbers() {
}
public void search() {
int low = AT_LEAST;
int high = AT_MOST;
int guess = (low + high) / 2;
int distance = guess / 2;
int direction = 1;
System.out.println("Guess a number between " + low + " and " + high
+ ".");
Scanner in = new Scanner(System.in);
do {
boolean greaterThan = getHighLowResponse(in, direction, guess);
if (greaterThan) {
low = guess;
guess += distance;
direction = 1;
} else {
high = guess;
guess -= distance;
direction = -1;
}
distance /= 2;
} while (distance != 0);
for (int i = low; i <= high; i++) {
System.out.println("Is your number " + i + "?");
String finalAnswer = in.nextLine().toLowerCase();
if (finalAnswer.equalsIgnoreCase("yes")) {
System.out.println(i + " is the answer.");
System.out.println("Thank you for playing the guessing game.");
break;
}
}
in.close();
}
private boolean getHighLowResponse(Scanner in, int direction, int guess) {
do {
System.out.println("Is your number " + getDirection(direction)
+ " than " + guess + "?");
String answer = in.nextLine().toLowerCase();
if (direction < 0) {
if (answer.equals("yes"))
return false;
if (answer.equals("no"))
return true;
} else {
if (answer.equals("yes"))
return true;
if (answer.equals("no"))
return false;
}
} while (true);
}
private String getDirection(int direction) {
if (direction < 0) {
return "less";
} else {
return "greater";
}
}
}
It actually is problem to find lucky number - those numbers whose sum of digits and sum of square of digits are prime. I have implemented Sieve of Eratosthenes. Now to optimize it further I commented my getDigitSum method, that I suppose was heavy and replaced with two hard-coded value , but it is still taking minutes to solve one test case. Here is a reference to actual problem asked
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Set;
import java.util.TreeSet;
public class Solution {
private static int[] getDigitSum(long num) {
long sum = 0;
long squareSum = 0;
for (long tempNum = num; tempNum > 0; tempNum = tempNum / 10) {
if (tempNum < 0) {
sum = sum + tempNum;
squareSum = squareSum + (tempNum * tempNum);
} else {
long temp = tempNum % 10;
sum = sum + temp;
squareSum = squareSum + (temp * temp);
}
}
int[] twosums = new int[2];
twosums[0] = Integer.parseInt(sum+"");
twosums[1] = Integer.parseInt(squareSum+"");
// System.out.println("sum Of digits: " + twoDoubles[0]);
// System.out.println("squareSum Of digits: " + twoDoubles[1]);
return twosums;
}
public static Set<Integer> getPrimeSet(int maxValue) {
boolean[] primeArray = new boolean[maxValue + 1];
for (int i = 2; i < primeArray.length; i++) {
primeArray[i] = true;
}
Set<Integer> primeSet = new TreeSet<Integer>();
for (int i = 2; i < maxValue; i++) {
if (primeArray[i]) {
primeSet.add(i);
markMutiplesAsComposite(primeArray, i);
}
}
return primeSet;
}
public static void markMutiplesAsComposite(boolean[] primeArray, int value) {
for (int i = 2; i*value < primeArray.length; i++) {
primeArray[i * value] = false;
}
}
public static void main(String args[]) throws NumberFormatException,
IOException {
// getDigitSum(80001001000l);
//System.out.println(getPrimeSet(1600));
Set set = getPrimeSet(1600);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int totalCases = Integer.parseInt(br.readLine());
for (int cases = 0; cases < totalCases; cases++) {
String[] str = br.readLine().split(" ");
long startRange = Long.parseLong(str[0]);
long endRange = Long.parseLong(str[1]);
int luckyCount = 0;
for (long num = startRange; num <= endRange; num++) {
int[] longArray = getDigitSum(num); \\this method was commented for testing purpose and was replaced with any two hardcoded values
if(set.contains(longArray[0]) && set.contains(longArray[1])){
luckyCount++;
}
}
System.out.println(luckyCount);
}
}
}
what I should use to cache the result so that it takes lesser amount of time to search, currently it takes huge no. of minutes to complete 10000 test cases with range 1 99999999999999(18 times 9 -the worst case) , even thought the search values have been hard-coded for testing purpose( 1600, 1501 ).
You need a different algorithm. Caching is not your problem.
If the range is large - and you can bet some will be - even a loop doing almost nothing would take a very long time. The end of the range is constrained to be no more than 1018, if I understand correctly. Suppose the start of the range is half that. Then you'd iterate over 5*1017 numbers. Say you have a 2.5 GHz CPU, so you have 2.5*109 clock cycles per second. If each iteration took one cycle, that'd be 2*108 CPU-seconds. A year has about 3.1*107 seconds, so the loop would take roughly six and a half years.
Attack the problem from the other side. The sum of the squares of the digits can be at most 18*92, that's 1458, a rather small number. The sum of the digits itself can be at most 18*9 = 162.
For the primes less than 162, find out all possible decompositions as the sum of at most 18 digits (ignoring 0). Discard those decompositions for which the sum of the squares is not prime. Not too many combinations are left. Then find out how many numbers within the specified range you can construct using each of the possible decompositions (filling with zeros if required).
There are few places in this implementation that can be improved. In order to to start attacking the issues i made few changes first to get an idea of the main problems:
made the total start cases be the value 1 and set the range to be a billion (1,000,000,000) to have a large amount of iterations. also I use the method "getDigitSum" but commented out the code that actually makes the sum of digits to see how the rest runs: following are the methods that were modified for an initial test run:
private static int[] getDigitSum(long num) {
long sum = 0;
long squareSum = 0;
// for (long tempNum = num; tempNum > 0; tempNum = tempNum / 10) {
// if (tempNum < 0) {
// sum = sum + tempNum;
// squareSum = squareSum + (tempNum * tempNum);
// } else {
// long temp = tempNum % 10;
// sum = sum + temp;
// squareSum = squareSum + (temp * temp);
//
// }
// }
int[] twosums = new int[2];
twosums[0] = Integer.parseInt(sum+"");
twosums[1] = Integer.parseInt(squareSum+"");
// System.out.println("sum Of digits: " + twoDoubles[0]);
// System.out.println("squareSum Of digits: " + twoDoubles[1]);
return twosums;
}
and
public static void main(String args[]) throws NumberFormatException,
IOException {
// getDigitSum(80001001000l);
//System.out.println(getPrimeSet(1600));
Set set = getPrimeSet(1600);
//BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int totalCases = 1;
for (int cases = 0; cases < totalCases; cases++) {
//String[] str = br.readLine().split(" ");
long startRange = Long.parseLong("1");
long endRange = Long.parseLong("1000000000");
int luckyCount = 0;
for (long num = startRange; num <= endRange; num++) {
int[] longArray = getDigitSum(num); //this method was commented for testing purpose and was replaced with any two hardcoded values
if(set.contains(longArray[0]) && set.contains(longArray[1])){
luckyCount++;
}
}
System.out.println(luckyCount);
}
}
Running the code takes 5 minutes 8 seconds.
now we can start optimizing it step by step. I will now mention the various points in the implementation that can be optimized.
1- in the method getDigitSum(long num)
int[] twosums = new int[2];
twosums[0] = Integer.parseInt(sum+"");
twosums[1] = Integer.parseInt(squareSum+"");
the above is not good. on every call to this method, two String objects are created , e.g. (sum+"") , before they are parsed into an int. considering the method is called billion times in my test, that produces two billion String object creation operations. since you know that the value is an int (according to the math in there and based on the links you provided), it would be enough to use casting:
twosums[0] = (int)sum;
twosums[1] = (int)squareSum;
2- In the "Main" method, you have the following
for (long num = startRange; num <= endRange; num++) {
int[] longArray = getDigitSum(num); \\this method was commented for testing purpose and was replaced with any two hardcoded values
if(set.contains(longArray[0]) && set.contains(longArray[1])){
luckyCount++;
}
}
here there are few issues:
a- set.contains(longArray[0]) will create an Integer object (with autoboxing) because contains method requires an object. this is a big waste and is not necessary. in our example, billion Integer objects will be created. Also, usage of set, whether it is a treeset or hash set is not the best for our case.
what you are trying to do is to get a set that contains the prime numbers in the range 1 .. 1600. this way, to check if a number in the range is prime, you check if it is contained in the set. This is not good as there are billions of calls to the set contains method. instead, your boolean array that you made when filling the set can be used: to find if the number 1500 is prime, simply access the index 1500 in the array. this is much faster solution. since its only 1600 elements (1600 is greater than max sum of sqaures of digits of your worst case), the wasted memory for the false locations is not an issue compared to the gain in speed.
b- int[] longArray = getDigitSum(num);
an int array is being allocated and returned. that will happen billion times. in our case, we can define it once outside the loop and send it to the method where it gets filled. on billion iterations, this saved 7 seconds, not a big change by itslef. but if the test cases are repeated 1000 times as you plan, that is 7000 second.
therefore, after modifying the code to implement all of the above, here is what you will have:
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Set;
import java.util.TreeSet;
public class Solution {
private static void getDigitSum(long num,int[] arr) {
long sum = 0;
long squareSum = 0;
// for (long tempNum = num; tempNum > 0; tempNum = tempNum / 10) {
// if (tempNum < 0) {
// sum = sum + tempNum;
// squareSum = squareSum + (tempNum * tempNum);
// } else {
// long temp = tempNum % 10;
// sum = sum + temp;
// squareSum = squareSum + (temp * temp);
//
// }
// }
arr[0] = (int)sum;
arr[1] = (int)squareSum;
// System.out.println("sum Of digits: " + twoDoubles[0]);
// System.out.println("squareSum Of digits: " + twoDoubles[1]);
}
public static boolean[] getPrimeSet(int maxValue) {
boolean[] primeArray = new boolean[maxValue + 1];
for (int i = 2; i < primeArray.length; i++) {
primeArray[i] = true;
}
for (int i = 2; i < maxValue; i++) {
if (primeArray[i]) {
markMutiplesAsComposite(primeArray, i);
}
}
return primeArray;
}
public static void markMutiplesAsComposite(boolean[] primeArray, int value) {
for (int i = 2; i*value < primeArray.length; i++) {
primeArray[i * value] = false;
}
}
public static void main(String args[]) throws NumberFormatException,
IOException {
// getDigitSum(80001001000l);
//System.out.println(getPrimeSet(1600));
boolean[] primeArray = getPrimeSet(1600);
//BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
int totalCases = 1;
for (int cases = 0; cases < totalCases; cases++) {
//String[] str = br.readLine().split(" ");
long startRange = Long.parseLong("1");
long endRange = Long.parseLong("1000000000");
int luckyCount = 0;
int[] longArray=new int[2];
for (long num = startRange; num <= endRange; num++) {
getDigitSum(num,longArray); //this method was commented for testing purpose and was replaced with any two hardcoded values
if(primeArray[longArray[0]] && primeArray[longArray[1]]){
luckyCount++;
}
}
System.out.println(luckyCount);
}
}
}
Running the code takes 4 seconds.
the billion iterations cost 4 seconds instead of 5 minutes 8 seconds, that is an improvement. the only issue left is the actual calculation of the sum of digits and sum of squares of digits. that code i commented out (as you can see in the code i posted). if you uncomment it, the runtime will take 6-7 minutes. and here, there is nothing to improve except if you find some mathematical way to have incremental calculation based on previous results.