Skip to content

Instantly share code, notes, and snippets.

@MrGraversen
Created July 27, 2021 11:05
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save MrGraversen/d0797578bf5d6f7e1e27acf319aa8b98 to your computer and use it in GitHub Desktop.
Save MrGraversen/d0797578bf5d6f7e1e27acf319aa8b98 to your computer and use it in GitHub Desktop.
Java Prime Numbers (Educational)
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)
(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!
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)
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));
}
}
}
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