Repository: TheAlgorithms/Java
Pull request #6349 (2025-07-06 19:30:34)

refactor: improve median calculator class design and readability
Refactored the abstract class `MedianOfRunningArray` to improve clarity, extensibility, and maintainability.

Changes made:
- Renamed `median()` → `getMedian()` for clearer naming convention.
- Improved JavaDoc comments for class and methods to enhance code documentation and readability.
- Extracted heap balancing logic into a dedicated private method `balanceHeapsIfNeeded()` to reduce code duplication.
- Changed method access modifiers:
  - Made `insert()` and `getMedian()` final to prevent override and preserve consistency.
  - Changed heap fields to `final`.
- Added detailed exception message and `@throws` tag when median is requested on an empty data set.
- General formatting and naming improvements for consistency and readability.

<!--
Thank you for your contribution!
In order to reduce the number of notifications sent to the maintainers, please:
- create your PR as draft, cf. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/about-pull-requests#draft-pull-requests,
- make sure that all of the CI checks pass,
- mark your PR as ready for review, cf. https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/changing-the-stage-of-a-pull-request#marking-a-pull-request-as-ready-for-review
-->

<!-- For completed items, change [ ] to [x] -->

- [x] I have read [CONTRIBUTING.md](https://github.com/TheAlgorithms/Java/blob/master/CONTRIBUTING.md).
- [x] This pull request is all my own work -- I have not plagiarized it.
- [x] All filenames are in PascalCase.
- [x] All functions and variable names follow Java naming conventions.
- [x] All new algorithms have a URL in their comments that points to Wikipedia or other similar explanations.
- [x] All new code is formatted with `clang-format -i --style=file path/to/your/file.java`

codecov-commenter: ## [Codecov](https://app.codecov.io/gh/TheAlgorithms/Java/pull/6349?dropdown=coverage&src=pr&el=h1&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) Report
All modified and coverable lines are covered by tests :white_check_mark:
> Project coverage is 74.56%. Comparing base [(`350f149`)](https://app.codecov.io/gh/TheAlgorithms/Java/commit/350f1496573c218f1d3796ed80811d4f46060125?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms) to head [(`a80d431`)](https://app.codecov.io/gh/TheAlgorithms/Java/commit/a80d431c2d72cc4842dd69c2017138bb4affeae0?dropdown=coverage&el=desc&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).

<details><summary>Additional details and impacted files</summary>


```diff
@@             Coverage Diff              @@
##             master    #6349      +/-   ##
============================================
- Coverage     74.56%   74.56%   -0.01%     
- Complexity     5451     5453       +2     
============================================
  Files           682      682              
  Lines         19036    19037       +1     
  Branches       3690     3689       -1     
============================================
  Hits          14194    14194              
- Misses         4277     4278       +1     
  Partials        565      565              
```

</details>

[:umbrella: View full report in Codecov by Sentry](https://app.codecov.io/gh/TheAlgorithms/Java/pull/6349?dropdown=coverage&src=pr&el=continue&utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).   
:loudspeaker: Have feedback on the report? [Share it here](https://about.codecov.io/codecov-pr-comment-feedback/?utm_medium=referral&utm_source=github&utm_content=comment&utm_campaign=pr+comments&utm_term=TheAlgorithms).

<details><summary> :rocket: New features to boost your workflow: </summary>

- :snowflake: [Test Analytics](https://docs.codecov.com/docs/test-analytics): Detect flaky tests, report on failures, and find test suite problems.
</details>

Patch:
diff --git a/src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java b/src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java
index 62013ee3..95f86f63 100644
--- a/src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java
+++ b/src/main/java/com/thealgorithms/misc/MedianOfRunningArray.java
@@ -4,50 +4,74 @@ import java.util.Collections;
 import java.util.PriorityQueue;
 
 /**
- * @author shrutisheoran
+ * A generic abstract class to compute the median of a dynamically growing stream of numbers.
+ *
+ * @param <T> the number type, must extend Number and be Comparable
+ *
+ * Usage:
+ * Extend this class and implement {@code calculateAverage(T a, T b)} to define how averaging is done.
  */
 public abstract class MedianOfRunningArray<T extends Number & Comparable<T>> {
 
-    private PriorityQueue<T> maxHeap;
-    private PriorityQueue<T> minHeap;
+    private final PriorityQueue<T> maxHeap; // Lower half (max-heap)
+    private final PriorityQueue<T> minHeap; // Upper half (min-heap)
 
-    // Constructor
     public MedianOfRunningArray() {
-        this.maxHeap = new PriorityQueue<>(Collections.reverseOrder()); // Max Heap
-        this.minHeap = new PriorityQueue<>(); // Min Heap
+        this.maxHeap = new PriorityQueue<>(Collections.reverseOrder());
+        this.minHeap = new PriorityQueue<>();
     }
 
-    /*
-      Inserting lower half of array to max Heap
-      and upper half to min heap
+    /**
+     * Inserts a new number into the data structure.
+     *
+     * @param element the number to insert
      */
-    public void insert(final T e) {
-        if (!minHeap.isEmpty() && e.compareTo(minHeap.peek()) < 0) {
-            maxHeap.offer(e);
-            if (maxHeap.size() > minHeap.size() + 1) {
-                minHeap.offer(maxHeap.poll());
-            }
+    public final void insert(final T element) {
+        if (!minHeap.isEmpty() && element.compareTo(minHeap.peek()) < 0) {
+            maxHeap.offer(element);
+            balanceHeapsIfNeeded();
         } else {
-            minHeap.offer(e);
-            if (minHeap.size() > maxHeap.size() + 1) {
-                maxHeap.offer(minHeap.poll());
-            }
+            minHeap.offer(element);
+            balanceHeapsIfNeeded();
         }
     }
 
-    /*
-      Returns median at any given point
+    /**
+     * Returns the median of the current elements.
+     *
+     * @return the median value
+     * @throws IllegalArgumentException if no elements have been inserted
      */
-    public T median() {
+    public final T getMedian() {
         if (maxHeap.isEmpty() && minHeap.isEmpty()) {
-            throw new IllegalArgumentException("Enter at least 1 element, Median of empty list is not defined!");
-        } else if (maxHeap.size() == minHeap.size()) {
-            T maxHeapTop = maxHeap.peek();
-            T minHeapTop = minHeap.peek();
-            return calculateAverage(maxHeapTop, minHeapTop);
+            throw new IllegalArgumentException("Median is undefined for an empty data set.");
         }
-        return maxHeap.size() > minHeap.size() ? maxHeap.peek() : minHeap.peek();
+
+        if (maxHeap.size() == minHeap.size()) {
+            return calculateAverage(maxHeap.peek(), minHeap.peek());
+        }
+
+        return (maxHeap.size() > minHeap.size()) ? maxHeap.peek() : minHeap.peek();
     }
 
-    public abstract T calculateAverage(T a, T b);
+    /**
+     * Calculates the average between two values.
+     * Concrete subclasses must define how averaging works (e.g., for Integer, Double, etc.).
+     *
+     * @param a first number
+     * @param b second number
+     * @return the average of a and b
+     */
+    protected abstract T calculateAverage(T a, T b);
+
+    /**
+     * Balances the two heaps so that their sizes differ by at most 1.
+     */
+    private void balanceHeapsIfNeeded() {
+        if (maxHeap.size() > minHeap.size() + 1) {
+            minHeap.offer(maxHeap.poll());
+        } else if (minHeap.size() > maxHeap.size() + 1) {
+            maxHeap.offer(minHeap.poll());
+        }
+    }
 }


Test patch:
diff --git a/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java b/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java
index e64ae1b7..f4195303 100644
--- a/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java
+++ b/src/test/java/com/thealgorithms/misc/MedianOfRunningArrayTest.java
@@ -11,12 +11,12 @@ import org.junit.jupiter.api.Test;
  */
 
 public class MedianOfRunningArrayTest {
-    private static final String EXCEPTION_MESSAGE = "Enter at least 1 element, Median of empty list is not defined!";
+    private static final String EXCEPTION_MESSAGE = "Median is undefined for an empty data set.";
 
     @Test
     public void testWhenInvalidInoutProvidedShouldThrowException() {
         var stream = new MedianOfRunningArrayInteger();
-        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, () -> stream.median());
+        IllegalArgumentException exception = assertThrows(IllegalArgumentException.class, stream::getMedian);
         assertEquals(exception.getMessage(), EXCEPTION_MESSAGE);
     }
 
@@ -24,68 +24,68 @@ public class MedianOfRunningArrayTest {
     public void testWithNegativeValues() {
         var stream = new MedianOfRunningArrayInteger();
         stream.insert(-1);
-        assertEquals(-1, stream.median());
+        assertEquals(-1, stream.getMedian());
         stream.insert(-2);
-        assertEquals(-1, stream.median());
+        assertEquals(-1, stream.getMedian());
         stream.insert(-3);
-        assertEquals(-2, stream.median());
+        assertEquals(-2, stream.getMedian());
     }
 
     @Test
     public void testWithSingleValues() {
         var stream = new MedianOfRunningArrayInteger();
         stream.insert(-1);
-        assertEquals(-1, stream.median());
+        assertEquals(-1, stream.getMedian());
     }
 
     @Test
     public void testWithRandomValues() {
         var stream = new MedianOfRunningArrayInteger();
         stream.insert(10);
-        assertEquals(10, stream.median());
+        assertEquals(10, stream.getMedian());
 
         stream.insert(5);
-        assertEquals(7, stream.median());
+        assertEquals(7, stream.getMedian());
 
         stream.insert(20);
-        assertEquals(10, stream.median());
+        assertEquals(10, stream.getMedian());
 
         stream.insert(15);
-        assertEquals(12, stream.median());
+        assertEquals(12, stream.getMedian());
 
         stream.insert(25);
-        assertEquals(15, stream.median());
+        assertEquals(15, stream.getMedian());
 
         stream.insert(30);
-        assertEquals(17, stream.median());
+        assertEquals(17, stream.getMedian());
 
         stream.insert(35);
-        assertEquals(20, stream.median());
+        assertEquals(20, stream.getMedian());
 
         stream.insert(1);
-        assertEquals(17, stream.median());
+        assertEquals(17, stream.getMedian());
     }
 
     @Test
     public void testWithNegativeAndPositiveValues() {
         var stream = new MedianOfRunningArrayInteger();
         stream.insert(-1);
-        assertEquals(-1, stream.median());
+        assertEquals(-1, stream.getMedian());
         stream.insert(2);
-        assertEquals(0, stream.median());
+        assertEquals(0, stream.getMedian());
         stream.insert(-3);
-        assertEquals(-1, stream.median());
+        assertEquals(-1, stream.getMedian());
     }
 
     @Test
     public void testWithDuplicateValues() {
         var stream = new MedianOfRunningArrayInteger();
         stream.insert(-1);
-        assertEquals(-1, stream.median());
+        assertEquals(-1, stream.getMedian());
         stream.insert(-1);
-        assertEquals(-1, stream.median());
+        assertEquals(-1, stream.getMedian());
         stream.insert(-1);
-        assertEquals(-1, stream.median());
+        assertEquals(-1, stream.getMedian());
     }
 
     @Test
@@ -98,20 +98,20 @@ public class MedianOfRunningArrayTest {
         stream.insert(20);
         stream.insert(0);
         stream.insert(50);
-        assertEquals(10, stream.median());
+        assertEquals(10, stream.getMedian());
     }
 
     @Test
     public void testWithLargeValues() {
         var stream = new MedianOfRunningArrayInteger();
         stream.insert(1000000);
-        assertEquals(1000000, stream.median());
+        assertEquals(1000000, stream.getMedian());
         stream.insert(12000);
-        assertEquals(506000, stream.median());
+        assertEquals(506000, stream.getMedian());
         stream.insert(15000000);
-        assertEquals(1000000, stream.median());
+        assertEquals(1000000, stream.getMedian());
         stream.insert(2300000);
-        assertEquals(1650000, stream.median());
+        assertEquals(1650000, stream.getMedian());
     }
 
     @Test
@@ -120,7 +120,7 @@ public class MedianOfRunningArrayTest {
         for (int i = 1; i <= 1000; i++) {
             stream.insert(i);
         }
-        assertEquals(500, stream.median());
+        assertEquals(500, stream.getMedian());
     }
 
     @Test
@@ -129,7 +129,7 @@ public class MedianOfRunningArrayTest {
         stream.insert(30);
         stream.insert(20);
         stream.insert(10);
-        assertEquals(20, stream.median());
+        assertEquals(20, stream.getMedian());
     }
 
     @Test
@@ -138,7 +138,7 @@ public class MedianOfRunningArrayTest {
         stream.insert(10);
         stream.insert(20);
         stream.insert(30);
-        assertEquals(20, stream.median());
+        assertEquals(20, stream.getMedian());
     }
 
     @Test
@@ -147,7 +147,7 @@ public class MedianOfRunningArrayTest {
         stream.insert(30);
         stream.insert(10);
         stream.insert(20);
-        assertEquals(20, stream.median());
+        assertEquals(20, stream.getMedian());
     }
 
     @Test
@@ -156,46 +156,46 @@ public class MedianOfRunningArrayTest {
         stream.insert(20);
         stream.insert(10);
         stream.insert(30);
-        assertEquals(20, stream.median());
+        assertEquals(20, stream.getMedian());
     }
 
     @Test
     public void testWithFloatValues() {
         var stream = new MedianOfRunningArrayFloat();
         stream.insert(20.0f);
-        assertEquals(20.0f, stream.median());
+        assertEquals(20.0f, stream.getMedian());
         stream.insert(10.5f);
-        assertEquals(15.25f, stream.median());
+        assertEquals(15.25f, stream.getMedian());
         stream.insert(30.0f);
-        assertEquals(20.0f, stream.median());
+        assertEquals(20.0f, stream.getMedian());
     }
 
     @Test
     public void testWithByteValues() {
         var stream = new MedianOfRunningArrayByte();
         stream.insert((byte) 120);
-        assertEquals((byte) 120, stream.median());
+        assertEquals((byte) 120, stream.getMedian());
         stream.insert((byte) -120);
-        assertEquals((byte) 0, stream.median());
+        assertEquals((byte) 0, stream.getMedian());
         stream.insert((byte) 127);
-        assertEquals((byte) 120, stream.median());
+        assertEquals((byte) 120, stream.getMedian());
     }
 
     @Test
     public void testWithLongValues() {
         var stream = new MedianOfRunningArrayLong();
         stream.insert(120000000L);
-        assertEquals(120000000L, stream.median());
+        assertEquals(120000000L, stream.getMedian());
         stream.insert(92233720368547757L);
-        assertEquals(46116860244273878L, stream.median());
+        assertEquals(46116860244273878L, stream.getMedian());
     }
 
     @Test
     public void testWithDoubleValues() {
         var stream = new MedianOfRunningArrayDouble();
         stream.insert(12345.67891);
-        assertEquals(12345.67891, stream.median());
+        assertEquals(12345.67891, stream.getMedian());
         stream.insert(23456789.98);
-        assertEquals(11734567.83, stream.median(), .01);
+        assertEquals(11734567.83, stream.getMedian(), .01);
     }
 }