Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,16 @@
* methods to retrieve the key and additional information.
* </p>
*
* <p>
* Complexity: for {@code N} constructed and queried {@code HeapElement} instances, every
* constructor, {@link #getKey()}, and {@link #getInfo()} executes in {@code O(1)} worst-case
* time and space per element ({@code O(N)} total work across {@code N} elements, performing a
* constant number of field accesses per element); observed wall-clock per-element times may vary
* across {@code N} due to JVM JIT warmup and allocation/GC behavior rather than algorithmic
* work. {@link #toString()}, {@link #equals(Object)}, and {@link #hashCode()} add {@code O(1)}
* key work plus the cost of {@code additionalInfo}'s corresponding method.
* </p>
*
* @author Nicolas Renard
*/
public class HeapElement {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
package com.thealgorithms.datastructures.heaps;

import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNull;

import java.util.Arrays;
import java.util.Random;
import org.junit.jupiter.api.Test;

/**
* Correctness and scaling checks for {@link HeapElement}.
*
* <p>Source preconditions ({@code HeapElement.java}):
* <ul>
* <li>lines 37-38: immutable fields {@code final double key} and
* {@code final Object additionalInfo}</li>
* <li>lines 47-50: {@code HeapElement(double, Object)} performs 2 constant-time field
* assignments</li>
* <li>lines 100-103: {@code HeapElement(int)} initializes {@code additionalInfo = null}</li>
* <li>lines 131-142: {@code getInfo()} and {@code getKey()} are constant-time field reads</li>
* <li>lines 151-178: {@code toString()}, {@code equals(Object)}, and {@code hashCode()} compare
* {@code key} and delegate to {@code additionalInfo} when non-null</li>
* </ul>
*/
public class HeapElementScalingTest {

private static final long FIXED_SEED = 20261111L;
private static final int REPETITIONS = 5;
private static final int[] SIZES = {1_000, 10_000, 100_000};

private static void logRecord(String tag, String detail) {
System.out.println("[" + tag + "] " + detail);
}

/**
* Smallest valid input ({@code new HeapElement(0.0, null)} and {@code new HeapElement(0)})
* and ordinary input ({@code new HeapElement(5.0, "Info")}) with independently calculated
* literal expected outputs:
* {@code "Info".hashCode() = ((73*31 + 110)*31 + 102)*31 + 111 = 2283726};
* {@code 31 * 5 + 2283726 = 2283881}.
*/
@Test
public void smallestAndOrdinaryInputsMatchLiteralExpectedOutputs() {
HeapElement smallest = new HeapElement(0.0, null);
logRecord("R45-T019", "smallest-double-null | input=(0.0, null) | expected=0.0/null/0"
+ " | actual=" + smallest.getKey() + "/" + smallest.getInfo() + "/" + smallest.hashCode());
assertEquals(0.0, smallest.getKey());
assertNull(smallest.getInfo());
assertEquals("Key: 0.0 - No additional info", smallest.toString());
assertEquals(0, smallest.hashCode());
assertEquals(new HeapElement(0), smallest);

HeapElement ordinary = new HeapElement(5.0, "Info");
logRecord("R45-T019", "ordinary-double-info | input=(5.0, \"Info\") | expected=5.0/Info/2283881"
+ " | actual=" + ordinary.getKey() + "/" + ordinary.getInfo() + "/" + ordinary.hashCode());
assertEquals(5.0, ordinary.getKey());
assertEquals("Info", ordinary.getInfo());
assertEquals("Key: 5.0 - Info", ordinary.toString());
assertEquals(2283881, ordinary.hashCode());
assertEquals(new HeapElement(5, "Info"), ordinary);
assertNotEquals(new HeapElement(5.0, "Other"), ordinary);
assertNotEquals(new HeapElement(6.0, "Info"), ordinary);
}

/**
* Scaling experiment over {@code N in {1000, 10000, 100000}} with fixed seed and 5 repetitions
* per size. Fresh inputs are prepared before the timed interval, only {@code HeapElement}
* construction and {@code getKey()}/{@code getInfo()} lookups are timed, and all {@code N}
* outputs plus exact operation counts ({@code 3 * N}) are verified outside the timed interval.
*/
@Test
public void scalingAcrossThreeSizesPreservesCorrectnessAndLinearOperationCount() {
Random rng = new Random(FIXED_SEED);

// Warmup outside reported repetitions to reduce JIT compilation skew.
for (int i = 0; i < 200; i++) {
HeapElement w = new HeapElement((double) i, "w");
assertEquals((double) i, w.getKey());
}

for (int n : SIZES) {
long[] elapsedNanos = new long[REPETITIONS];
long totalOpsPerRep = 0;

for (int rep = 0; rep < REPETITIONS; rep++) {
double[] keys = new double[n];
String[] infos = new String[n];
for (int i = 0; i < n; i++) {
keys[i] = rng.nextInt(10_000);
infos[i] = "item-" + (i & 63);
}

HeapElement[] elements = new HeapElement[n];
double[] observedKeys = new double[n];
Object[] observedInfos = new Object[n];

long ops = 0;
long start = System.nanoTime();
for (int i = 0; i < n; i++) {
HeapElement el = new HeapElement(keys[i], infos[i]);
elements[i] = el;
observedKeys[i] = el.getKey();
observedInfos[i] = el.getInfo();
ops += 3; // 1 construction + 1 getKey + 1 getInfo
}
long duration = System.nanoTime() - start;
elapsedNanos[rep] = duration;
totalOpsPerRep = ops;

// Verify every constructed element outside the timed interval.
assertEquals(3L * n, ops);
for (int i = 0; i < n; i++) {
assertEquals(keys[i], observedKeys[i]);
assertEquals(infos[i], observedInfos[i]);
}
}

long[] sorted = Arrays.copyOf(elapsedNanos, elapsedNanos.length);
Arrays.sort(sorted);
long medianNs = sorted[REPETITIONS / 2];
long minNs = sorted[0];
long maxNs = sorted[REPETITIONS - 1];
double medianNsPerElement = (double) medianNs / n;

logRecord("R45-T019", "scaling-n-" + n + " | input=N=" + n + ",reps=" + REPETITIONS
+ ",seed=" + FIXED_SEED + " | expected=ops=" + (3L * n)
+ ",all_correct=true | actual=ops=" + totalOpsPerRep + ",all_correct=true");
logRecord("R45-T019-TIMING", String.format(
"N=%d | ops=%d | median_ns=%d | min_ns=%d | max_ns=%d | range_ns=%d | median_ns_per_elem=%.2f",
n, totalOpsPerRep, medianNs, minNs, maxNs, maxNs - minNs, medianNsPerElement));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,4 +53,19 @@ void testHashCode() {
assertEquals(element1.hashCode(), element2.hashCode()); // Same key and info
assertNotEquals(element1.hashCode(), element3.hashCode()); // Different info
}

@Test
void testSmallestAndOrdinaryContractAndHashCode() {
HeapElement smallest = new HeapElement(0.0, null);
assertEquals(0.0, smallest.getKey());
assertNull(smallest.getInfo());
assertEquals("Key: 0.0 - No additional info", smallest.toString());
assertEquals(0, smallest.hashCode());

HeapElement ordinary = new HeapElement(5.0, "Info");
assertEquals(5.0, ordinary.getKey());
assertEquals("Info", ordinary.getInfo());
assertEquals("Key: 5.0 - Info", ordinary.toString());
assertEquals(2283881, ordinary.hashCode());
}
}
80 changes: 80 additions & 0 deletions work/R45-T019/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
#!/usr/bin/env python3
"""Runner for task R45-T019: scaling and correctness checks for HeapElement.

Runs the focused JUnit test through Maven, requires a nonzero test count,
saves inputs, expected versus actual results, median/range timings, and tool versions
into work/R45-T019/, and exits non-zero on any failure.
"""
import glob
import subprocess
import sys
import xml.etree.ElementTree as ET
from pathlib import Path

HERE = Path(__file__).resolve().parent
REPO = HERE.parent.parent
TEST_CLASS = "HeapElementScalingTest"
CMD = ["mvn", "-B", "test", f"-Dtest={TEST_CLASS}", "-Dsurefire.failIfNoSpecifiedTests=false"]


def tool_version(cmd):
try:
out = subprocess.run(cmd, capture_output=True, text=True, cwd=REPO)
return "\n".join(l for l in (out.stdout + out.stderr).splitlines()
if not l.startswith("Picked up")).strip()
except FileNotFoundError:
return f"{cmd[0]}: not found"


def main():
(HERE / "tool_versions.txt").write_text(
"java:\n" + tool_version(["java", "-version"]) + "\n\nmvn:\n"
+ tool_version(["mvn", "-v"]) + "\n\npython:\n" + sys.version + "\n")

print("Running:", " ".join(CMD))
proc = subprocess.run(CMD, capture_output=True, text=True, cwd=REPO)
combined_output = proc.stdout + proc.stderr
(HERE / "mvn_output.txt").write_text(combined_output)

comparisons = [line.strip() for line in combined_output.splitlines()
if line.strip().startswith("[R45-T019]")]
timings = [line.strip() for line in combined_output.splitlines()
if line.strip().startswith("[R45-T019-TIMING]")]
(HERE / "comparisons.txt").write_text(
("\n".join(comparisons) + "\n") if comparisons else "No comparison records captured.\n")
(HERE / "timings.txt").write_text(
("\n".join(timings) + "\n") if timings else "No timing records captured.\n")

reports = glob.glob(str(REPO / "target/surefire-reports" / f"TEST-*{TEST_CLASS}.xml"))
if not reports:
(HERE / "result.txt").write_text(
f"command: {' '.join(CMD)}\nFAIL: no surefire report found\n")
print("FAIL: no surefire report found")
return 1

suite = ET.parse(reports[0]).getroot()
tests = int(suite.get("tests", 0))
failures = int(suite.get("failures", 0)) + int(suite.get("errors", 0))
skipped = int(suite.get("skipped", 0))
passed = (
proc.returncode == 0
and failures == 0
and tests > 0
and len(comparisons) > 0
and len(timings) == 3
)
status = "PASS" if passed else "FAIL"
summary = (
f"command: {' '.join(CMD)}\n"
f"tests={tests} failures/errors={failures} skipped={skipped} "
f"comparisons={len(comparisons)} timings={len(timings)} mvn_exit={proc.returncode}\n"
f"status={status}\n"
)
(HERE / "result.txt").write_text(summary)
print(summary)

return 0 if passed else 1


if __name__ == "__main__":
sys.exit(main())
5 changes: 5 additions & 0 deletions work/R45-T019/comparisons.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
[R45-T019] smallest-double-null | input=(0.0, null) | expected=0.0/null/0 | actual=0.0/null/0
[R45-T019] ordinary-double-info | input=(5.0, "Info") | expected=5.0/Info/2283881 | actual=5.0/Info/2283881
[R45-T019] scaling-n-1000 | input=N=1000,reps=5,seed=20261111 | expected=ops=3000,all_correct=true | actual=ops=3000,all_correct=true
[R45-T019] scaling-n-10000 | input=N=10000,reps=5,seed=20261111 | expected=ops=30000,all_correct=true | actual=ops=30000,all_correct=true
[R45-T019] scaling-n-100000 | input=N=100000,reps=5,seed=20261111 | expected=ops=300000,all_correct=true | actual=ops=300000,all_correct=true
57 changes: 57 additions & 0 deletions work/R45-T019/mvn_output.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
[INFO] Scanning for projects...
[INFO] Loaded 24447 auto-discovered prefixes for remote repository central (prefixes-central.txt)
[INFO]
[INFO] -----------------------< com.thealgorithms:Java >-----------------------
[INFO] Building Java 1.0-SNAPSHOT
[INFO] from pom.xml
[INFO] --------------------------------[ jar ]---------------------------------
[INFO]
[INFO] --- jacoco:0.8.16:prepare-agent (default) @ Java ---
[INFO] Loaded 64 auto-discovered prefixes for remote repository apache.snapshots (prefixes-apache.snapshots.txt)
[INFO] argLine set to -javaagent:/Users/shyamlibakale/.m2/repository/org/jacoco/org.jacoco.agent/0.8.16/org.jacoco.agent-0.8.16-runtime.jar=destfile=/Users/shyamlibakale/TheAlgorithms-Java/TheAlgorithms-Java/target/jacoco.exec
[INFO]
[INFO] --- resources:3.5.0:resources (default-resources) @ Java ---
[INFO] skip non existing resourceDirectory /Users/shyamlibakale/TheAlgorithms-Java/TheAlgorithms-Java/src/main/resources
[INFO]
[INFO] --- compiler:3.16.0:compile (default-compile) @ Java ---
[INFO] Recompiling the module because of changed source code.
[INFO] Compiling 848 source files with javac [debug release 21] to target/classes
[INFO]
[INFO] --- resources:3.5.0:testResources (default-testResources) @ Java ---
[INFO] skip non existing resourceDirectory /Users/shyamlibakale/TheAlgorithms-Java/TheAlgorithms-Java/src/test/resources
[INFO]
[INFO] --- compiler:3.16.0:testCompile (default-testCompile) @ Java ---
[INFO] Recompiling the module because of changed dependency.
[INFO] Compiling 797 source files with javac [debug release 21] to target/test-classes
[INFO]
[INFO] --- surefire:3.6.0:test (default-test) @ Java ---
[INFO] Using auto detected provider org.apache.maven.surefire.junitplatform.JUnitPlatformProvider
[INFO]
[INFO] -------------------------------------------------------
[INFO] T E S T S
[INFO] -------------------------------------------------------
[INFO] Running com.thealgorithms.datastructures.heaps.HeapElementScalingTest
[R45-T019] smallest-double-null | input=(0.0, null) | expected=0.0/null/0 | actual=0.0/null/0
[R45-T019] ordinary-double-info | input=(5.0, "Info") | expected=5.0/Info/2283881 | actual=5.0/Info/2283881
[R45-T019] scaling-n-1000 | input=N=1000,reps=5,seed=20261111 | expected=ops=3000,all_correct=true | actual=ops=3000,all_correct=true
[R45-T019-TIMING] N=1000 | ops=3000 | median_ns=46083 | min_ns=45583 | max_ns=86167 | range_ns=40584 | median_ns_per_elem=46.08
[R45-T019] scaling-n-10000 | input=N=10000,reps=5,seed=20261111 | expected=ops=30000,all_correct=true | actual=ops=30000,all_correct=true
[R45-T019-TIMING] N=10000 | ops=30000 | median_ns=447333 | min_ns=113541 | max_ns=479292 | range_ns=365751 | median_ns_per_elem=44.73
[R45-T019] scaling-n-100000 | input=N=100000,reps=5,seed=20261111 | expected=ops=300000,all_correct=true | actual=ops=300000,all_correct=true
[R45-T019-TIMING] N=100000 | ops=300000 | median_ns=1108875 | min_ns=1104500 | max_ns=1221667 | range_ns=117167 | median_ns_per_elem=11.09
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0, Time elapsed: 0.074 s -- in com.thealgorithms.datastructures.heaps.HeapElementScalingTest
[INFO]
[INFO] Results:
[INFO]
[INFO] Tests run: 2, Failures: 0, Errors: 0, Skipped: 0
[INFO]
[INFO]
[INFO] --- jacoco:0.8.16:report (generate-code-coverage-report) @ Java ---
[INFO] Loading execution data file /Users/shyamlibakale/TheAlgorithms-Java/TheAlgorithms-Java/target/jacoco.exec
[INFO] Analyzed bundle 'Java' with 1073 classes
[INFO] ------------------------------------------------------------------------
[INFO] BUILD SUCCESS
[INFO] ------------------------------------------------------------------------
[INFO] Total time: 5.566 s
[INFO] Finished at: 2026-11-11T19:49:04+05:30
[INFO] ------------------------------------------------------------------------
3 changes: 3 additions & 0 deletions work/R45-T019/result.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
command: mvn -B test -Dtest=HeapElementScalingTest -Dsurefire.failIfNoSpecifiedTests=false
tests=2 failures/errors=0 skipped=0 comparisons=5 timings=3 mvn_exit=0
status=PASS
3 changes: 3 additions & 0 deletions work/R45-T019/timings.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
[R45-T019-TIMING] N=1000 | ops=3000 | median_ns=46083 | min_ns=45583 | max_ns=86167 | range_ns=40584 | median_ns_per_elem=46.08
[R45-T019-TIMING] N=10000 | ops=30000 | median_ns=447333 | min_ns=113541 | max_ns=479292 | range_ns=365751 | median_ns_per_elem=44.73
[R45-T019-TIMING] N=100000 | ops=300000 | median_ns=1108875 | min_ns=1104500 | max_ns=1221667 | range_ns=117167 | median_ns_per_elem=11.09
14 changes: 14 additions & 0 deletions work/R45-T019/tool_versions.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
java:
openjdk version "21.0.13" 2026-10-20 LTS
OpenJDK Runtime Environment Temurin-21.0.13+9 (build 21.0.13+9-LTS)
OpenJDK 64-Bit Server VM Temurin-21.0.13+9 (build 21.0.13+9-LTS, mixed mode, sharing)

mvn:
Apache Maven 3.10.0 (c43a36b8d67be7e0805a411bc0898af1a51f5472)
Maven home: /opt/homebrew/Cellar/maven/3.10.0/libexec
Java version: 27.0.1.0.3, vendor: Homebrew, runtime: /opt/homebrew/Cellar/openjdk/27.0.1.0.3/libexec/openjdk.jdk/Contents/Home
Default locale: en_IN, platform encoding: UTF-8, time zone: Asia/Kolkata
OS name: "mac os x", version: "27.0", arch: "aarch64", family: "mac"

python:
3.14.7 (v3.14.7:823f0323ee6, Aug 5 2026, 07:07:01) [Clang 21.0.0 (clang-2100.1.1.101)]