From 4a376b94e9ab4abbaf21525dc7dd1e40558059a6 Mon Sep 17 00:00:00 2001 From: asmhatre Date: Thu, 29 Oct 2026 03:56:25 +0000 Subject: [PATCH] Add disarium-number module: loop, String and stream approaches, dry run, counts to 10^8 Co-Authored-By: Claude Sonnet 5.5 Claude-Session: https://claude.ai/code/session_017ic4b4VQVEJWetTT1dYzyJ --- README.md | 1 + disarium-number/README.md | 25 ++++ .../output/01-check-and-series.txt | 9 ++ disarium-number/output/02-list-1-to-1000.txt | 2 + disarium-number/output/03-dry-run.txt | 5 + .../output/04-approaches-agree.txt | 1 + .../output/05-log10-digit-count.txt | 1 + .../output/06-count-to-10-pow-8.txt | 9 ++ disarium-number/output/07-test-results.txt | 7 ++ disarium-number/pom.xml | 27 +++++ disarium-number/scripts/run-all.sh | 22 ++++ disarium-number/scripts/run.sh | 6 + .../java/com/ankurm/disarium/Disarium.java | 108 ++++++++++++++++++ .../main/java/com/ankurm/disarium/Main.java | 65 +++++++++++ .../com/ankurm/disarium/DisariumTest.java | 43 +++++++ 15 files changed, 331 insertions(+) create mode 100644 disarium-number/README.md create mode 100644 disarium-number/output/01-check-and-series.txt create mode 100644 disarium-number/output/02-list-1-to-1000.txt create mode 100644 disarium-number/output/03-dry-run.txt create mode 100644 disarium-number/output/04-approaches-agree.txt create mode 100644 disarium-number/output/05-log10-digit-count.txt create mode 100644 disarium-number/output/06-count-to-10-pow-8.txt create mode 100644 disarium-number/output/07-test-results.txt create mode 100644 disarium-number/pom.xml create mode 100755 disarium-number/scripts/run-all.sh create mode 100755 disarium-number/scripts/run.sh create mode 100644 disarium-number/src/main/java/com/ankurm/disarium/Disarium.java create mode 100644 disarium-number/src/main/java/com/ankurm/disarium/Main.java create mode 100644 disarium-number/src/test/java/com/ankurm/disarium/DisariumTest.java diff --git a/README.md b/README.md index 7d7b881..c27d845 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ One module per article. This repository currently holds: | [`junit-mockito`](junit-mockito/) | Top 30 JUnit 6 and Mockito Interview Questions (2026) | this commit | | [`hashmap-vs-concurrenthashmap`](hashmap-vs-concurrenthashmap/) | HashMap vs ConcurrentHashMap in Java: Internals, Thread Safety and Interview Answers | this commit | | [`multithreading`](multithreading/) | Java Multithreading Programs for Interviews: Odd-Even Printing, Ping-Pong, Ordered Threads, Deadlock | this commit | +| [`disarium-number`](disarium-number/) | Disarium Number in Java: Program, Algorithm and All Disarium Numbers from 1 to 1000 | this commit | ## `collections` module diff --git a/disarium-number/README.md b/disarium-number/README.md new file mode 100644 index 0000000..1483218 --- /dev/null +++ b/disarium-number/README.md @@ -0,0 +1,25 @@ +# disarium-number + +Disarium number checks in plain Java 25: the original `Math.pow` loop, an integer-power loop, a +String version, a Streams version and a `log10` digit-count variant. A test proves they agree, and +`output/` holds the transcripts quoted in the post. Companion to +[Disarium Number in Java: Program, Algorithm and All Disarium Numbers from 1 to 1000](https://ankurm.com/disarium-number-in-java/). + +## Versions + +| Component | Version | +|---|---| +| JDK | 25.0.5+8 (Temurin LTS), `--release 25` | +| JUnit Jupiter | 6.1.3 | +| Maven | 3.9.11 | + +## Run it + +```bash +cd disarium-number +./scripts/run.sh check # also: list | dryrun | approaches | digits | count +mvn test # 6 tests +./scripts/run-all.sh # regenerates output/*.txt (count takes ~2 minutes) +``` + +`count` brute-forces every int up to 10^8; the timing depends on your machine. diff --git a/disarium-number/output/01-check-and-series.txt b/disarium-number/output/01-check-and-series.txt new file mode 100644 index 0000000..7837d2a --- /dev/null +++ b/disarium-number/output/01-check-and-series.txt @@ -0,0 +1,9 @@ + 89 -> Disarium + 135 -> Disarium + 175 -> Disarium + 518 -> Disarium + 598 -> Disarium + 24 -> NOT Disarium + 100 -> NOT Disarium + +Disarium numbers up to 1000: 1 2 3 4 5 6 7 8 9 89 135 175 518 598 diff --git a/disarium-number/output/02-list-1-to-1000.txt b/disarium-number/output/02-list-1-to-1000.txt new file mode 100644 index 0000000..f8af0e1 --- /dev/null +++ b/disarium-number/output/02-list-1-to-1000.txt @@ -0,0 +1,2 @@ +count=14 +1 2 3 4 5 6 7 8 9 89 135 175 518 598 diff --git a/disarium-number/output/03-dry-run.txt b/disarium-number/output/03-dry-run.txt new file mode 100644 index 0000000..1f9cd3b --- /dev/null +++ b/disarium-number/output/03-dry-run.txt @@ -0,0 +1,5 @@ +175 = 1^1 + 7^2 + 5^3 = 1 + 49 + 125 = 175 -> Disarium +89 = 8^1 + 9^2 = 8 + 81 = 89 -> Disarium +135 = 1^1 + 3^2 + 5^3 = 1 + 9 + 125 = 135 -> Disarium +24 = 2^1 + 4^2 = 2 + 16 = 18 -> NOT Disarium (18 != 24) +100 = 1^1 + 0^2 + 0^3 = 1 + 0 + 0 = 1 -> NOT Disarium (1 != 100) diff --git a/disarium-number/output/04-approaches-agree.txt b/disarium-number/output/04-approaches-agree.txt new file mode 100644 index 0000000..4063d26 --- /dev/null +++ b/disarium-number/output/04-approaches-agree.txt @@ -0,0 +1 @@ +compared original, loop, String, stream, log10 for 1..1000000: disagreements=0 diff --git a/disarium-number/output/05-log10-digit-count.txt b/disarium-number/output/05-log10-digit-count.txt new file mode 100644 index 0000000..408ae72 --- /dev/null +++ b/disarium-number/output/05-log10-digit-count.txt @@ -0,0 +1 @@ +log10 vs loop digit count around every power of ten: mismatches=0 diff --git a/disarium-number/output/06-count-to-10-pow-8.txt b/disarium-number/output/06-count-to-10-pow-8.txt new file mode 100644 index 0000000..f6aae0f --- /dev/null +++ b/disarium-number/output/06-count-to-10-pow-8.txt @@ -0,0 +1,9 @@ +up to 10^1: 9 +up to 10^2: 10 +up to 10^3: 14 +up to 10^4: 17 +up to 10^5: 17 +up to 10^6: 17 +up to 10^7: 18 +up to 10^8: 18 +found at or above 1000: 1306 1676 2427 2646798 diff --git a/disarium-number/output/07-test-results.txt b/disarium-number/output/07-test-results.txt new file mode 100644 index 0000000..cb6c992 --- /dev/null +++ b/disarium-number/output/07-test-results.txt @@ -0,0 +1,7 @@ +PASS DisariumTest.allApproachesAgreeUpTo200000 +PASS DisariumTest.dryRunOf175 +PASS DisariumTest.dryRunOf24 +PASS DisariumTest.zeroAndNegativesAreNotDisarium +PASS DisariumTest.biggerKnownOnes +PASS DisariumTest.listUpTo1000 +6 tests, 0 failures diff --git a/disarium-number/pom.xml b/disarium-number/pom.xml new file mode 100644 index 0000000..57502ab --- /dev/null +++ b/disarium-number/pom.xml @@ -0,0 +1,27 @@ + + + 4.0.0 + + com.ankurm.interviewlab + disarium-number + 1.0.0 + disarium-number + Disarium number checks in Java: loop, String and stream approaches, with a dry run and counts + + + 25 + UTF-8 + 6.1.3 + + + + + org.junit.jupiter + junit-jupiter + ${junit.version} + test + + + diff --git a/disarium-number/scripts/run-all.sh b/disarium-number/scripts/run-all.sh new file mode 100755 index 0000000..ea24fdd --- /dev/null +++ b/disarium-number/scripts/run-all.sh @@ -0,0 +1,22 @@ +#!/usr/bin/env bash +# Regenerates every transcript in output/. Needs JDK 25 and Maven 3.9+. +set -euo pipefail +cd "$(dirname "$0")/.." +mkdir -p output +./scripts/run.sh check > output/01-check-and-series.txt +./scripts/run.sh list > output/02-list-1-to-1000.txt +./scripts/run.sh dryrun > output/03-dry-run.txt +./scripts/run.sh approaches > output/04-approaches-agree.txt +./scripts/run.sh digits > output/05-log10-digit-count.txt +./scripts/run.sh count > output/06-count-to-10-pow-8.txt +mvn -B -q test > /dev/null 2>&1 +python3 - <<'PY' > output/07-test-results.txt +import glob, xml.etree.ElementTree as ET +total = fail = 0 +for f in sorted(glob.glob("target/surefire-reports/TEST-*.xml")): + for tc in ET.parse(f).getroot().iter("testcase"): + bad = tc.find("failure") is not None or tc.find("error") is not None + total += 1; fail += bad + print(("FAIL " if bad else "PASS ") + tc.get("classname").split(".")[-1] + "." + tc.get("name")) +print(f"{total} tests, {fail} failures") +PY diff --git a/disarium-number/scripts/run.sh b/disarium-number/scripts/run.sh new file mode 100755 index 0000000..d35db50 --- /dev/null +++ b/disarium-number/scripts/run.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Runs one demo: ./scripts/run.sh check | list | dryrun | approaches | count | digits +set -euo pipefail +cd "$(dirname "$0")/.." +mvn -B -q compile 2>&1 | grep -E 'ERROR' || true +java -cp target/classes com.ankurm.disarium.Main "$@" diff --git a/disarium-number/src/main/java/com/ankurm/disarium/Disarium.java b/disarium-number/src/main/java/com/ankurm/disarium/Disarium.java new file mode 100644 index 0000000..8099cad --- /dev/null +++ b/disarium-number/src/main/java/com/ankurm/disarium/Disarium.java @@ -0,0 +1,108 @@ +package com.ankurm.disarium; + +import java.util.List; +import java.util.stream.IntStream; + +/** Three independent ways to test a Disarium number. All must agree (see DisariumTest). */ +public final class Disarium { + private Disarium() {} + + /** The program from the original post, kept as-is (uses Math.pow). */ + public static boolean isDisariumOriginal(int number) { + int original = number; + int len = countDigits(number); + int sum = 0; + int n = number; + while (n > 0) { + int digit = n % 10; + sum += (int) Math.pow(digit, len); + len--; + n /= 10; + } + return sum == original; + } + + public static int countDigits(int n) { + int count = 0; + while (n > 0) { + count++; + n /= 10; + } + return count; + } + + /** Approach 1: loop over the digits right to left; integer power, no floating point. */ + public static boolean isDisariumLoop(int number) { + if (number <= 0) return false; + int len = countDigits(number); + long sum = 0; + for (int n = number; n > 0; n /= 10, len--) { + sum += intPow(n % 10, len); + } + return sum == number; + } + + /** Approach 2: String of digits, left to right, position = index + 1. */ + public static boolean isDisariumString(int number) { + if (number <= 0) return false; + String s = Integer.toString(number); + long sum = 0; + for (int i = 0; i < s.length(); i++) { + sum += intPow(s.charAt(i) - '0', i + 1); + } + return sum == number; + } + + /** Approach 3: Streams over the characters of the String. */ + public static boolean isDisariumStream(int number) { + if (number <= 0) return false; + String s = Integer.toString(number); + long sum = IntStream.range(0, s.length()) + .mapToLong(i -> intPow(s.charAt(i) - '0', i + 1)) + .sum(); + return sum == number; + } + + /** Digit count via log10 instead of a loop (used by the maths variant). */ + public static int digitsLog10(int n) { + return (int) Math.log10(n) + 1; + } + + /** Approach 1b: same as the loop, but the digit count comes from log10. */ + public static boolean isDisariumLog10(int number) { + if (number <= 0) return false; + int len = digitsLog10(number); + long sum = 0; + for (int n = number; n > 0; n /= 10, len--) { + sum += intPow(n % 10, len); + } + return sum == number; + } + + static long intPow(int base, int exp) { + long r = 1; + for (int i = 0; i < exp; i++) r *= base; + return r; + } + + public static List upTo(int limit) { + return IntStream.rangeClosed(1, limit).filter(Disarium::isDisariumLoop).boxed().toList(); + } + + /** Human-readable dry run: "175 = 1^1 + 7^2 + 5^3 = 1 + 49 + 125 = 175". */ + public static String dryRun(int number) { + String s = Integer.toString(number); + StringBuilder terms = new StringBuilder(), values = new StringBuilder(); + long sum = 0; + for (int i = 0; i < s.length(); i++) { + int d = s.charAt(i) - '0'; + long v = intPow(d, i + 1); + sum += v; + if (i > 0) { terms.append(" + "); values.append(" + "); } + terms.append(d).append('^').append(i + 1); + values.append(v); + } + return number + " = " + terms + " = " + values + " = " + sum + + (sum == number ? " -> Disarium" : " -> NOT Disarium (" + sum + " != " + number + ")"); + } +} diff --git a/disarium-number/src/main/java/com/ankurm/disarium/Main.java b/disarium-number/src/main/java/com/ankurm/disarium/Main.java new file mode 100644 index 0000000..bab2759 --- /dev/null +++ b/disarium-number/src/main/java/com/ankurm/disarium/Main.java @@ -0,0 +1,65 @@ +package com.ankurm.disarium; + +import java.util.List; + +/** Usage: Main check | list | dryrun | count | digits */ +public final class Main { + public static void main(String[] args) { + String mode = args.length > 0 ? args[0] : "list"; + switch (mode) { + case "check" -> { + for (int n : new int[] {89, 135, 175, 518, 598, 24, 100}) { + System.out.printf("%4d -> %s%n", n, Disarium.isDisariumOriginal(n) ? "Disarium" : "NOT Disarium"); + } + System.out.println(); + System.out.println("Disarium numbers up to 1000: " + join(Disarium.upTo(1000))); + } + case "list" -> { + List l = Disarium.upTo(1000); + System.out.println("count=" + l.size()); + System.out.println(join(l)); + } + case "dryrun" -> { + for (int n : new int[] {175, 89, 135, 24, 100}) System.out.println(Disarium.dryRun(n)); + } + case "approaches" -> { + int bad = 0; + for (int n = 1; n <= 1_000_000; n++) { + boolean a = Disarium.isDisariumOriginal(n), b = Disarium.isDisariumLoop(n), + c = Disarium.isDisariumString(n), d = Disarium.isDisariumStream(n), + e = Disarium.isDisariumLog10(n); + if (a != b || b != c || c != d || d != e) { bad++; System.out.println("DISAGREE at " + n); } + } + System.out.println("compared original, loop, String, stream, log10 for 1..1000000: disagreements=" + bad); + } + case "count" -> { + // Brute force up to 10^8 with the loop approach, reporting when each power of ten is passed. + int found = 0; + long limit = 100_000_000L; + long mark = 10; + StringBuilder sb = new StringBuilder(); + for (int n = 1; n <= limit; n++) { + if (Disarium.isDisariumLoop(n)) { found++; if (n >= 1000) sb.append(n).append(' '); } + if (n == mark) { System.out.println("up to 10^" + (int) Math.log10(mark) + ": " + found); mark *= 10; } + } + System.out.println("found at or above 1000: " + sb.toString().trim()); + } + case "digits" -> { + // Where does Math.log10 digit counting go wrong? Compare with the loop for all ints near powers of ten. + int wrong = 0; + long p = 1; + for (int k = 0; k < 10; k++, p *= 10) { + for (long n = Math.max(1, p - 2); n <= p + 2 && n <= Integer.MAX_VALUE; n++) { + if (Disarium.digitsLog10((int) n) != Disarium.countDigits((int) n)) { wrong++; System.out.println("log10 wrong at " + n); } + } + } + System.out.println("log10 vs loop digit count around every power of ten: mismatches=" + wrong); + } + default -> throw new IllegalArgumentException(mode); + } + } + + private static String join(List l) { + return String.join(" ", l.stream().map(String::valueOf).toList()); + } +} diff --git a/disarium-number/src/test/java/com/ankurm/disarium/DisariumTest.java b/disarium-number/src/test/java/com/ankurm/disarium/DisariumTest.java new file mode 100644 index 0000000..2ad2cca --- /dev/null +++ b/disarium-number/src/test/java/com/ankurm/disarium/DisariumTest.java @@ -0,0 +1,43 @@ +package com.ankurm.disarium; + +import static org.junit.jupiter.api.Assertions.*; + +import java.util.List; +import org.junit.jupiter.api.Test; + +class DisariumTest { + static final List TO_1000 = List.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 89, 135, 175, 518, 598); + + @Test void listUpTo1000() { assertEquals(TO_1000, Disarium.upTo(1000)); } + + @Test void allApproachesAgreeUpTo200000() { + for (int n = 1; n <= 200_000; n++) { + boolean e = Disarium.isDisariumLoop(n); + assertEquals(e, Disarium.isDisariumOriginal(n), "original " + n); + assertEquals(e, Disarium.isDisariumString(n), "string " + n); + assertEquals(e, Disarium.isDisariumStream(n), "stream " + n); + assertEquals(e, Disarium.isDisariumLog10(n), "log10 " + n); + } + } + + @Test void dryRunOf175() { + assertEquals("175 = 1^1 + 7^2 + 5^3 = 1 + 49 + 125 = 175 -> Disarium", Disarium.dryRun(175)); + } + + @Test void dryRunOf24() { + assertEquals("24 = 2^1 + 4^2 = 2 + 16 = 18 -> NOT Disarium (18 != 24)", Disarium.dryRun(24)); + } + + @Test void zeroAndNegativesAreNotDisarium() { + assertFalse(Disarium.isDisariumLoop(0)); + assertFalse(Disarium.isDisariumString(-5)); + assertFalse(Disarium.isDisariumStream(0)); + } + + @Test void biggerKnownOnes() { + assertTrue(Disarium.isDisariumLoop(2646798)); + assertTrue(Disarium.isDisariumStream(2646798)); + assertFalse(Disarium.isDisariumStream(12157692)); + assertFalse(Disarium.isDisariumString(2646799)); + } +}