Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added resizable circular buffer and unit tests for buffer resizing an… #6056

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
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
@@ -0,0 +1,98 @@
package com.thealgorithms.datastructures.resizablebuffer;

import java.util.concurrent.atomic.AtomicInteger;

/**
* The {@code ResizableCircularBuffer} class implements a generic resizable circular (or ring) buffer.
* This buffer grows automatically when it becomes full, retaining a FIFO (First In, First Out) structure.
*
* @param <Item> The type of elements stored in the circular buffer.
*/
public class ResizableCircularBuffer<Item> {
private Item[] buffer;
private int putPointer = 0;
private int getPointer = 0;
private final AtomicInteger size = new AtomicInteger(0);

/**
* Constructor to initialize the circular buffer with a specified initial size.
*
* @param initialSize The initial size of the circular buffer.
* @throws IllegalArgumentException if the size is zero or negative.
*/
@SuppressWarnings("unchecked")
public ResizableCircularBuffer(int initialSize) {
if (initialSize <= 0) {
throw new IllegalArgumentException("Buffer size must be positive");
}
this.buffer = (Item[]) new Object[initialSize];
}

/**
* Checks if the circular buffer is empty.
*
* @return {@code true} if the buffer is empty, {@code false} otherwise.
*/
public boolean isEmpty() {
return size.get() == 0;
}

/**
* Checks if the circular buffer is full.
*
* @return {@code true} if the buffer is full, {@code false} otherwise.
*/
public boolean isFull() {
return size.get() == buffer.length;
}

/**
* Retrieves and removes the item at the front of the buffer (FIFO).
*
* @return The item at the front of the buffer, or {@code null} if the buffer is empty.
*/
public synchronized Item get() {
if (isEmpty()) {
return null;
}
Item item = buffer[getPointer];
buffer[getPointer] = null; // Optional: clear reference
getPointer = (getPointer + 1) % buffer.length;
size.decrementAndGet();
return item;
}

/**
* Adds an item to the end of the buffer (FIFO). If the buffer is full, it doubles in size.
*
* @param item The item to be added.
* @throws IllegalArgumentException if the item is null.
*/
public synchronized void put(Item item) {
if (item == null) {
throw new IllegalArgumentException("Null items are not allowed");
}

if (isFull()) {
resizeBuffer();
}

buffer[putPointer] = item;
putPointer = (putPointer + 1) % buffer.length;
size.incrementAndGet();
}

/**
* Resizes the buffer to twice its current size to accommodate more items.
*/
@SuppressWarnings("unchecked")
private void resizeBuffer() {
Item[] newBuffer = (Item[]) new Object[buffer.length * 2];
for (int i = 0; i < buffer.length; i++) {
newBuffer[i] = buffer[(getPointer + i) % buffer.length];
}
buffer = newBuffer;
getPointer = 0;
putPointer = size.get();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
package com.thealgorithms.datastructures.resizablebuffer;

import static org.junit.jupiter.api.Assertions.*;

import org.junit.jupiter.api.Test;

class ResizableCircularBufferTest {

@Test
void testInitialization() {
ResizableCircularBuffer<Integer> buffer = new ResizableCircularBuffer<>(5);
assertTrue(buffer.isEmpty());
assertFalse(buffer.isFull());
}

@Test
void testPutAndGet() {
ResizableCircularBuffer<String> buffer = new ResizableCircularBuffer<>(3);

buffer.put("A");
assertFalse(buffer.isEmpty());
assertFalse(buffer.isFull());

buffer.put("B");
buffer.put("C");
assertTrue(buffer.isFull());

assertEquals("A", buffer.get());
assertEquals("B", buffer.get());
assertEquals("C", buffer.get());
assertTrue(buffer.isEmpty());
}

@Test
void testResizeOnFullBuffer() {
ResizableCircularBuffer<Integer> buffer = new ResizableCircularBuffer<>(2);

buffer.put(1);
buffer.put(2);
buffer.put(3); // Should trigger resizing

assertEquals(1, buffer.get());
assertEquals(2, buffer.get());
assertEquals(3, buffer.get());
}

@Test
void testOverwrite() {
ResizableCircularBuffer<Integer> buffer = new ResizableCircularBuffer<>(2);

buffer.put(1);
buffer.put(2);
buffer.put(3); // Should resize instead of overwrite
assertEquals(1, buffer.get());
assertEquals(2, buffer.get());
assertEquals(3, buffer.get());
}

@Test
void testEmptyBuffer() {
ResizableCircularBuffer<Double> buffer = new ResizableCircularBuffer<>(2);
assertNull(buffer.get());
}

@Test
void testFullBufferAndResize() {
ResizableCircularBuffer<Character> buffer = new ResizableCircularBuffer<>(2);

buffer.put('A');
buffer.put('B');
assertTrue(buffer.isFull());

buffer.put('C'); // This should resize the buffer instead of overwriting 'A'
assertEquals('A', buffer.get());
assertEquals('B', buffer.get());
assertEquals('C', buffer.get());
}

@Test
void testIllegalArguments() {
assertThrows(IllegalArgumentException.class, () -> new ResizableCircularBuffer<>(0));
assertThrows(IllegalArgumentException.class, () -> new ResizableCircularBuffer<>(-1));

ResizableCircularBuffer<String> buffer = new ResizableCircularBuffer<>(1);
assertThrows(IllegalArgumentException.class, () -> buffer.put(null));
}

@Test
void testLargeBufferWithResize() {
ResizableCircularBuffer<Integer> buffer = new ResizableCircularBuffer<>(10);

// Fill buffer to trigger multiple resizes
for (int i = 0; i < 100; i++) {
buffer.put(i);
}

assertEquals(10, buffer.get()); // Check that resizing kept order
}
}
Loading