Created
July 27, 2021 11:05
-
-
Save MrGraversen/d0797578bf5d6f7e1e27acf319aa8b98 to your computer and use it in GitHub Desktop.
Java Prime Numbers (Educational)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Welcome to an amazing prime number app! | |
Please enter an integer between 1 and 1000000. Enter 'generate' to generate all. Enter 'quit' to exit. | |
generate | |
Testing if 1 is a prime... | |
[DEBUG]: User input '1' missed the cache, which contains 0 entries. | |
[DEBUG]: Operation took 21889 µs | |
It's NOT a prime! | |
Testing if 2 is a prime... | |
[DEBUG]: User input '2' missed the cache, which contains 1 entries. | |
[DEBUG]: Operation took 201 µs | |
It's a prime! | |
Testing if 3 is a prime... | |
[DEBUG]: User input '3' missed the cache, which contains 2 entries. | |
[DEBUG]: Operation took 155 µs | |
It's a prime! | |
Testing if 4 is a prime... | |
[DEBUG]: User input '4' missed the cache, which contains 3 entries. | |
[DEBUG]: Operation took 155 µs | |
It's NOT a prime! | |
Testing if 5 is a prime... | |
[DEBUG]: User input '5' missed the cache, which contains 4 entries. | |
[DEBUG]: Operation took 181 µs | |
It's a prime! | |
Testing if 6 is a prime... | |
[DEBUG]: User input '6' missed the cache, which contains 5 entries. | |
[DEBUG]: Operation took 144 µs | |
It's NOT a prime! | |
Testing if 7 is a prime... | |
[DEBUG]: User input '7' missed the cache, which contains 6 entries. | |
[DEBUG]: Operation took 151 µs | |
It's a prime! | |
Testing if 8 is a prime... | |
[DEBUG]: User input '8' missed the cache, which contains 7 entries. | |
[DEBUG]: Operation took 145 µs | |
It's NOT a prime! | |
Testing if 9 is a prime... | |
[DEBUG]: User input '9' missed the cache, which contains 8 entries. | |
[DEBUG]: Operation took 147 µs | |
It's NOT a prime! | |
Testing if 10 is a prime... | |
[DEBUG]: User input '10' missed the cache, which contains 9 entries. | |
[DEBUG]: Operation took 132 µs | |
It's NOT a prime! | |
(999980 primes skipped) | |
Testing if 999990 is a prime... | |
[DEBUG]: User input '999990' missed the cache, which contains 999989 entries. | |
[DEBUG]: Operation took 4 µs | |
It's NOT a prime! | |
Testing if 999991 is a prime... | |
[DEBUG]: User input '999991' missed the cache, which contains 999990 entries. | |
[DEBUG]: Operation took 5 µs | |
It's NOT a prime! | |
Testing if 999992 is a prime... | |
[DEBUG]: User input '999992' missed the cache, which contains 999991 entries. | |
[DEBUG]: Operation took 5 µs | |
It's NOT a prime! | |
Testing if 999993 is a prime... | |
[DEBUG]: User input '999993' missed the cache, which contains 999992 entries. | |
[DEBUG]: Operation took 4 µs | |
It's NOT a prime! | |
Testing if 999994 is a prime... | |
[DEBUG]: User input '999994' missed the cache, which contains 999993 entries. | |
[DEBUG]: Operation took 4 µs | |
It's NOT a prime! | |
Testing if 999995 is a prime... | |
[DEBUG]: User input '999995' missed the cache, which contains 999994 entries. | |
[DEBUG]: Operation took 5 µs | |
It's NOT a prime! | |
Testing if 999996 is a prime... | |
[DEBUG]: User input '999996' missed the cache, which contains 999995 entries. | |
[DEBUG]: Operation took 4 µs | |
It's NOT a prime! | |
Testing if 999997 is a prime... | |
[DEBUG]: User input '999997' missed the cache, which contains 999996 entries. | |
[DEBUG]: Operation took 5 µs | |
It's NOT a prime! | |
Testing if 999998 is a prime... | |
[DEBUG]: User input '999998' missed the cache, which contains 999997 entries. | |
[DEBUG]: Operation took 4 µs | |
It's NOT a prime! | |
Testing if 999999 is a prime... | |
[DEBUG]: User input '999999' missed the cache, which contains 999998 entries. | |
[DEBUG]: Operation took 3 µs | |
It's NOT a prime! | |
Testing if 1000000 is a prime... | |
[DEBUG]: User input '1000000' missed the cache, which contains 999999 entries. | |
[DEBUG]: Operation took 5 µs | |
It's NOT a prime! | |
(Notice the operation time and how much impact JVM "warmup" has) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
(Now the JVM has sped up the process so significantly, we can't even measure the operation in microseconds most of the time) | |
(Skipped to end of output) | |
Testing if 999980 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999981 is a prime... | |
[DEBUG]: Operation took 1 µs | |
It's NOT a prime! | |
Testing if 999982 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999983 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's a prime! | |
Testing if 999984 is a prime... | |
[DEBUG]: Operation took 1 µs | |
It's NOT a prime! | |
Testing if 999985 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999986 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999987 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999988 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999989 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999990 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999991 is a prime... | |
[DEBUG]: Operation took 1 µs | |
It's NOT a prime! | |
Testing if 999992 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999993 is a prime... | |
[DEBUG]: Operation took 1 µs | |
It's NOT a prime! | |
Testing if 999994 is a prime... | |
[DEBUG]: Operation took 1 µs | |
It's NOT a prime! | |
Testing if 999995 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999996 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999997 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999998 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 999999 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! | |
Testing if 1000000 is a prime... | |
[DEBUG]: Operation took 0 µs | |
It's NOT a prime! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Welcome to an amazing prime number app! | |
Please enter an integer between 1 and 1000000. Enter 'generate' to generate all. Enter 'quit' to exit. | |
1337 | |
Testing if 1337 is a prime... | |
[DEBUG]: User input '1337' missed the cache, which contains 0 entries. | |
[DEBUG]: Operation took 19850 µs | |
It's NOT a prime! | |
Please enter an integer between 1 and 1000000. Enter 'generate' to generate all. Enter 'quit' to exit. | |
1337 | |
Testing if 1337 is a prime... | |
[DEBUG]: Operation took 40 µs | |
It's NOT a prime! | |
Please enter an integer between 1 and 1000000. Enter 'generate' to generate all. Enter 'quit' to exit. | |
(Notice the performance impact of memoization during the second computation) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import java.time.Duration; | |
import java.time.LocalDateTime; | |
import java.time.temporal.ChronoUnit; | |
import java.util.*; | |
import java.util.concurrent.Callable; | |
import java.util.function.Predicate; | |
import java.util.function.UnaryOperator; | |
import java.util.stream.IntStream; | |
public class PrimesApp { | |
static final int PRIMES_LOWER_BOUND = 1; | |
static final int PRIMES_UPPER_BOUND = 1_000_000; | |
static final String EXIT_COMMAND = "quit"; | |
static final String GENERATE_COMMAND = "generate"; | |
static final boolean DEBUG_ENABLED = true; | |
private static final Scanner USER_INPUT = new Scanner(System.in); | |
private static final Map<Integer, Boolean> PRIMES_CACHE = new HashMap<>(PRIMES_UPPER_BOUND); | |
public static void main(String[] args) { | |
System.out.println("Welcome to an amazing prime number app!"); | |
checkPrimes(); | |
} | |
static Predicate<Integer> isWithinBounds() { | |
return input -> input <= PRIMES_UPPER_BOUND && input >= PRIMES_LOWER_BOUND; | |
} | |
static Predicate<Integer> isPrimeNumber() { | |
return input -> { | |
if (input == 1) { | |
return false; | |
} | |
if (input == 2 || input == 3) { | |
return true; | |
} | |
if (input % 2 == 0) { | |
return false; | |
} | |
final var inputSquared = (int) Math.sqrt(input) + 1; | |
for (int i = 3; i < inputSquared; i += 2) { | |
if (input % i == 0) { | |
return false; | |
} | |
} | |
return true; | |
}; | |
} | |
static Predicate<String> isExitCommand() { | |
return EXIT_COMMAND::equalsIgnoreCase; | |
} | |
static Predicate<String> isGenerateCommand() { | |
return GENERATE_COMMAND::equalsIgnoreCase; | |
} | |
static <T> T measureExecution(Callable<T> command) { | |
try { | |
Objects.requireNonNull(command, "Parameter 'command' must not be null"); | |
final var startedAt = LocalDateTime.now(); | |
final var value = command.call(); | |
final var executionDuration = Duration.between(startedAt, LocalDateTime.now()); | |
debugLog("Operation took %s µs", executionDuration.dividedBy(ChronoUnit.MICROS.getDuration())); | |
return value; | |
} catch (Exception e) { | |
exitFatally(e.getMessage()); | |
return null; | |
} | |
} | |
private static void checkPrimes() { | |
final var userInput = readUserInput(); | |
if (isWithinBounds().negate().test(userInput)) { | |
exitFatally("Whoops! %d is outside allowed range (%d - %d)!", userInput, PRIMES_LOWER_BOUND, PRIMES_UPPER_BOUND); | |
} | |
doCheckPrime(userInput); | |
checkPrimes(); | |
} | |
private static void doCheckPrime(int userInput) { | |
System.out.printf("Testing if %d is a prime...%n", userInput); | |
final var isPrime | |
= measureExecution(() -> PRIMES_CACHE.computeIfAbsent(userInput, cacheMissed().andThen(isPrimeNumber()::test))); | |
System.out.println(isPrime ? "It's a prime!" : "It's NOT a prime!"); | |
} | |
private static int readUserInput() { | |
System.out.printf( | |
"Please enter an integer between %d and %d. Enter '%s' to generate all. Enter '%s' to exit.%n", | |
PRIMES_LOWER_BOUND, | |
PRIMES_UPPER_BOUND, | |
GENERATE_COMMAND, | |
EXIT_COMMAND | |
); | |
try { | |
final var nextUserInput = USER_INPUT.nextLine(); | |
if (isExitCommand().test(nextUserInput)) { | |
exitNicely(); | |
} else if (isGenerateCommand().test(nextUserInput)) { | |
generateAllPrimes(); | |
return readUserInput(); | |
} | |
return Integer.parseInt(nextUserInput); | |
} catch (NumberFormatException e) { | |
System.err.printf("It seems like you didn't enter an integer: %s%n", e.getMessage()); | |
return readUserInput(); | |
} catch (NoSuchElementException e) { | |
// Be quiet 🤫 | |
return 0; | |
} | |
} | |
private static void generateAllPrimes() { | |
IntStream.rangeClosed(PRIMES_LOWER_BOUND, PRIMES_UPPER_BOUND).forEach(PrimesApp::doCheckPrime); | |
} | |
private static void exitFatally(String errorMessage, Object... args) { | |
System.err.printf(errorMessage + "%n", args); | |
System.exit(1); | |
} | |
private static void exitNicely() { | |
System.out.println("Have a nice day!"); | |
System.exit(0); | |
} | |
private static UnaryOperator<Integer> cacheMissed() { | |
return input -> { | |
debugLog("User input '%d' missed the cache, which contains %d entries.", input, PRIMES_CACHE.size()); | |
return input; | |
}; | |
} | |
private static void debugLog(String message, Object... args) { | |
if (DEBUG_ENABLED) { | |
System.out.println("[DEBUG]: " + String.format(message, args)); | |
} | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
import org.junit.jupiter.api.Test; | |
import org.junit.jupiter.params.ParameterizedTest; | |
import org.junit.jupiter.params.provider.ValueSource; | |
import static org.junit.jupiter.api.Assertions.*; | |
class PrimesAppTest { | |
@Test | |
void lowerBound_lessThan_upperBound() { | |
assertTrue(PrimesApp.PRIMES_LOWER_BOUND < PrimesApp.PRIMES_UPPER_BOUND); | |
} | |
@Test | |
void lowerBound_upperBound_positive() { | |
assertTrue(PrimesApp.PRIMES_LOWER_BOUND > 0); | |
assertTrue(PrimesApp.PRIMES_UPPER_BOUND > 0); | |
} | |
@Test | |
void isWithinBounds_insideBounds() { | |
final var input = PrimesApp.PRIMES_LOWER_BOUND + 100; | |
final var isWithinBounds = PrimesApp.isWithinBounds().test(input); | |
assertTrue(isWithinBounds); | |
} | |
@Test | |
void isWithinBounds_outsideBounds() { | |
final var input = PrimesApp.PRIMES_UPPER_BOUND + 100; | |
final var isWithinBounds = PrimesApp.isWithinBounds().test(input); | |
assertFalse(isWithinBounds); | |
} | |
@ParameterizedTest | |
@ValueSource(ints = {2, 3, 5, 7, 11, 13, 17, 19, 23, 29}) | |
void isPrimeNumber_primes(int prime) { | |
final var isPrime = PrimesApp.isPrimeNumber().test(prime); | |
assertTrue(isPrime); | |
} | |
@ParameterizedTest | |
@ValueSource(ints = {4, 6, 8, 9, 10, 12, 14, 15, 16, 18}) | |
void isPrimeNumber_nonPrimes(int nonPrime) { | |
final var isPrime = PrimesApp.isPrimeNumber().test(nonPrime); | |
assertFalse(isPrime); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment