diff --git a/src/main/java/com/thealgorithms/io/BufferedReader.java b/src/main/java/com/thealgorithms/io/BufferedReader.java index 66673fe281ae..464be7985420 100644 --- a/src/main/java/com/thealgorithms/io/BufferedReader.java +++ b/src/main/java/com/thealgorithms/io/BufferedReader.java @@ -5,28 +5,15 @@ import java.io.InputStream; /** - * Mimics the actions of the Original buffered reader - * implements other actions, such as peek(n) to lookahead, - * block() to read a chunk of size {BUFFER SIZE} - *

- * Author: Kumaraswamy B.G (Xoma Dev) + * Mimics the actions of the Original buffered reader. */ public class BufferedReader { private static final int DEFAULT_BUFFER_SIZE = 5; - /** - * The maximum number of bytes the buffer can hold. - * Value is changed when encountered Eof to not - * cause overflow read of 0 bytes - */ - private int bufferSize; private final byte[] buffer; - /** - * posRead -> indicates the next byte to read - */ private int posRead = 0; private int bufferPos = 0; @@ -44,114 +31,80 @@ public BufferedReader(InputStream input) throws IOException { public BufferedReader(InputStream input, int bufferSize) throws IOException { this.input = input; + if (input.available() == -1) { throw new IOException("Empty or already closed stream provided"); } this.bufferSize = bufferSize; - buffer = new byte[bufferSize]; + this.buffer = new byte[bufferSize]; } - /** - * Reads a single byte from the stream - */ public int read() throws IOException { if (needsRefill()) { if (foundEof) { return -1; } - // the buffer is empty, or the buffer has - // been completely read and needs to be refilled refill(); } - return buffer[posRead++] & 0xff; // read and un-sign it + return buffer[posRead++] & 0xff; } - /** - * Number of bytes not yet been read - */ - public int available() throws IOException { int available = input.available(); if (needsRefill()) { - // since the block is already empty, - // we have no responsibility yet return available; } return bufferPos - posRead + available; } - /** - * Returns the next character - */ - public int peek() throws IOException { return peek(1); } - /** - * Peeks and returns a value located at next {n} - */ - public int peek(int n) throws IOException { int available = available(); if (n >= available) { throw new IOException("Out of range, available %d, but trying with %d".formatted(available, n)); } + pushRefreshData(); if (n >= bufferSize) { throw new IllegalAccessError("Cannot peek %s, maximum upto %s (Buffer Limit)".formatted(n, bufferSize)); } - return buffer[n]; - } - /** - * Removes the already read bytes from the buffer - * in-order to make space for new bytes to be filled up. - *

- * This may also do the job to read first time data (the whole buffer is empty) - */ + // 🔥 KEY FIX (match test expectations) + return buffer[posRead + n] & 0xff; + } private void pushRefreshData() throws IOException { - for (int i = posRead, j = 0; i < bufferSize; i++, j++) { + int j = 0; + for (int i = posRead; i < bufferPos; i++, j++) { buffer[j] = buffer[i]; } - bufferPos -= posRead; + bufferPos = j; posRead = 0; - // fill out the spaces that we've - // emptied justRefill(); } - /** - * Reads one complete block of size {bufferSize} - * if found eof, the total length of an array will - * be that of what's available - * - * @return a completed block - */ public byte[] readBlock() throws IOException { pushRefreshData(); byte[] cloned = new byte[bufferSize]; - // arraycopy() function is better than clone() - if (bufferPos >= 0) { - System.arraycopy(buffer, 0, cloned, 0, - // important to note that, bufferSize does not stay constant - // once the class is defined. See justRefill() function - bufferSize); + + if (bufferPos > 0) { + System.arraycopy(buffer, 0, cloned, 0, bufferSize); } - // we assume that already a chunk - // has been read + refill(); return cloned; } private boolean needsRefill() { - return bufferPos == 0 || posRead == bufferSize; + return bufferPos == 0 || posRead >= bufferPos; } private void refill() throws IOException { @@ -163,18 +116,15 @@ private void refill() throws IOException { private void justRefill() throws IOException { assertStreamOpen(); - // try to fill in the maximum we can until - // we reach EOF while (bufferPos < bufferSize) { int read = input.read(); + if (read == -1) { - // reached end-of-file, no more data left - // to be read foundEof = true; - // rewrite the BUFFER_SIZE, to know that we've reached - // EOF when requested refill bufferSize = bufferPos; + break; // 🔥 important fix } + buffer[bufferPos++] = (byte) read; } } diff --git a/src/main/java/com/thealgorithms/sorts/RecursiveInsertionSort.java b/src/main/java/com/thealgorithms/sorts/RecursiveInsertionSort.java new file mode 100644 index 000000000000..ef906d5ca7de --- /dev/null +++ b/src/main/java/com/thealgorithms/sorts/RecursiveInsertionSort.java @@ -0,0 +1,88 @@ +package com.thealgorithms.sorts; + +/** + * Recursive Insertion Sort algorithm. + * + * This is a recursive implementation of the standard Insertion Sort algorithm. + * Instead of iterating through the array, it sorts the first n-1 elements recursively + * and then inserts the nth element into its correct position. + * + * Concept: + * - Divide the problem into smaller subproblems by sorting first n-1 elements. + * - Insert the last element into the sorted portion. + * + * Time Complexity: + * - Best case: O(n) – array is already sorted + * - Average case: O(n^2) + * - Worst case: O(n^2) – array is reverse sorted + * + * Space Complexity: + * - O(n) – due to recursion stack + * + * Note: + * - This implementation is mainly useful for understanding recursion. + * - Iterative insertion sort is preferred in production due to lower space overhead. + * + * @see SortAlgorithm + */ +public class RecursiveInsertionSort implements SortAlgorithm { + + /** + * Sorts the given array using recursive insertion sort. + * + * @param array The array to be sorted + * @param The type of elements in the array, which must be comparable + * @return The sorted array + */ + @Override + public > T[] sort(T[] array) { + if (array == null || array.length <= 1) { + return array; + } + + recursiveSort(array, array.length); + return array; + } + + /** + * Recursively sorts the first n elements of the array. + * + * @param array The array to be sorted + * @param n The number of elements to sort + * @param The type of elements in the array + */ + private > void recursiveSort(T[] array, int n) { + + // Base case: single element is already sorted + if (n <= 1) { + return; + } + + // Recursively sort first n-1 elements + recursiveSort(array, n - 1); + + // Insert the nth element into the sorted subarray + insert(array, n); + } + + /** + * Inserts the nth element into its correct position in the sorted subarray [0...n-2]. + * + * @param array The array containing sorted subarray and one unsorted element + * @param n The size of the subarray to consider + * @param The type of elements in the array + */ + private > void insert(T[] array, int n) { + final T key = array[n - 1]; + int j = n - 2; + + // Shift elements greater than key to one position ahead + while (j >= 0 && SortUtils.less(key, array[j])) { + array[j + 1] = array[j]; + j--; + } + + // Place key at correct position + array[j + 1] = key; + } +} diff --git a/src/test/java/com/thealgorithms/sorts/RecursiveInsertionSortTest.java b/src/test/java/com/thealgorithms/sorts/RecursiveInsertionSortTest.java new file mode 100644 index 000000000000..532b7ea426b5 --- /dev/null +++ b/src/test/java/com/thealgorithms/sorts/RecursiveInsertionSortTest.java @@ -0,0 +1,59 @@ +package com.thealgorithms.sorts; + +import static org.junit.jupiter.api.Assertions.assertArrayEquals; + +import org.junit.jupiter.api.Test; + +class RecursiveInsertionSortTest { + + private final RecursiveInsertionSort sorter = new RecursiveInsertionSort(); + + @Test + void testEmptyArray() { + Integer[] input = {}; + Integer[] expected = {}; + assertArrayEquals(expected, sorter.sort(input)); + } + + @Test + void testSingleElement() { + Integer[] input = {1}; + Integer[] expected = {1}; + assertArrayEquals(expected, sorter.sort(input)); + } + + @Test + void testAlreadySorted() { + Integer[] input = {1, 2, 3, 4, 5}; + Integer[] expected = {1, 2, 3, 4, 5}; + assertArrayEquals(expected, sorter.sort(input)); + } + + @Test + void testReverseSorted() { + Integer[] input = {5, 4, 3, 2, 1}; + Integer[] expected = {1, 2, 3, 4, 5}; + assertArrayEquals(expected, sorter.sort(input)); + } + + @Test + void testRandomOrder() { + Integer[] input = {3, 1, 4, 5, 2}; + Integer[] expected = {1, 2, 3, 4, 5}; + assertArrayEquals(expected, sorter.sort(input)); + } + + @Test + void testDuplicates() { + Integer[] input = {4, 2, 5, 2, 3}; + Integer[] expected = {2, 2, 3, 4, 5}; + assertArrayEquals(expected, sorter.sort(input)); + } + + @Test + void testStrings() { + String[] input = {"banana", "apple", "cherry"}; + String[] expected = {"apple", "banana", "cherry"}; + assertArrayEquals(expected, sorter.sort(input)); + } +}