Add disarium-number module: loop, String and stream approaches, dry run, counts to 10^8
Co-Authored-By: Claude Sonnet 5.5 <[email protected]> Claude-Session: https://claude.ai/code/session_017ic4b4VQVEJWetTT1dYzyJ
This commit is contained in:
@@ -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 |
|
| [`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 |
|
| [`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 |
|
| [`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
|
## `collections` module
|
||||||
|
|
||||||
|
|||||||
@@ -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.
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
count=14
|
||||||
|
1 2 3 4 5 6 7 8 9 89 135 175 518 598
|
||||||
@@ -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)
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
compared original, loop, String, stream, log10 for 1..1000000: disagreements=0
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
log10 vs loop digit count around every power of ten: mismatches=0
|
||||||
@@ -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
|
||||||
@@ -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
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<project xmlns="http://maven.apache.org/POM/4.0.0"
|
||||||
|
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||||
|
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||||
|
<modelVersion>4.0.0</modelVersion>
|
||||||
|
|
||||||
|
<groupId>com.ankurm.interviewlab</groupId>
|
||||||
|
<artifactId>disarium-number</artifactId>
|
||||||
|
<version>1.0.0</version>
|
||||||
|
<name>disarium-number</name>
|
||||||
|
<description>Disarium number checks in Java: loop, String and stream approaches, with a dry run and counts</description>
|
||||||
|
|
||||||
|
<properties>
|
||||||
|
<maven.compiler.release>25</maven.compiler.release>
|
||||||
|
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
|
||||||
|
<junit.version>6.1.3</junit.version>
|
||||||
|
</properties>
|
||||||
|
|
||||||
|
<dependencies>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.junit.jupiter</groupId>
|
||||||
|
<artifactId>junit-jupiter</artifactId>
|
||||||
|
<version>${junit.version}</version>
|
||||||
|
<scope>test</scope>
|
||||||
|
</dependency>
|
||||||
|
</dependencies>
|
||||||
|
</project>
|
||||||
Executable
+22
@@ -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
|
||||||
Executable
+6
@@ -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 "$@"
|
||||||
@@ -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<Integer> 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 + ")");
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Integer> 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<Integer> l) {
|
||||||
|
return String.join(" ", l.stream().map(String::valueOf).toList());
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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<Integer> 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));
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user