mirror of
https://github.com/TheAlgorithms/Java.git
synced 2025-12-19 07:00:35 +08:00
feat: add IndexedPriorityQueue implementation and tests (#7062)
* feat: add IndexedPriorityQueue implementation and tests * mod : clang-format * Fix Checkstyle naming for IndexedPriorityQueue tests * Align IndexedPriorityQueue tests with Checkstyle and clang-format
This commit is contained in:
@@ -0,0 +1,327 @@
|
||||
package com.thealgorithms.datastructures.heaps;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.Comparator;
|
||||
import java.util.IdentityHashMap;
|
||||
import java.util.Objects;
|
||||
import java.util.function.Consumer;
|
||||
|
||||
/**
|
||||
* An addressable (indexed) min-priority queue with O(log n) updates.
|
||||
*
|
||||
* <p>Key features:
|
||||
* <ul>
|
||||
* <li>Each element E is tracked by a handle (its current heap index) via a map,
|
||||
* enabling O(log n) {@code remove(e)} and O(log n) key updates
|
||||
* ({@code changeKey/decreaseKey/increaseKey}).</li>
|
||||
* <li>The queue order is determined by the provided {@link Comparator}. If the
|
||||
* comparator is {@code null}, elements must implement {@link Comparable}
|
||||
* (same contract as {@link java.util.PriorityQueue}).</li>
|
||||
* <li>By default this implementation uses {@link IdentityHashMap} for the index
|
||||
* mapping to avoid issues with duplicate-equals elements or mutable equals/hashCode.
|
||||
* If you need value-based equality, switch to {@code HashMap} and read the caveats
|
||||
* in the class-level Javadoc carefully.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <h2>IMPORTANT contracts</h2>
|
||||
* <ul>
|
||||
* <li><b>Do not mutate comparator-relevant fields of an element directly</b> while it is
|
||||
* inside the queue. Always use {@code changeKey}/{@code decreaseKey}/{@code increaseKey}
|
||||
* so the heap can be restored accordingly.</li>
|
||||
* <li>If you replace {@link IdentityHashMap} with {@link HashMap}, you must ensure:
|
||||
* (a) no two distinct elements are {@code equals()}-equal at the same time in the queue, and
|
||||
* (b) {@code equals/hashCode} of elements remain stable while enqueued.</li>
|
||||
* <li>{@code peek()} returns {@code null} when empty (matching {@link java.util.PriorityQueue}).</li>
|
||||
* <li>Not thread-safe.</li>
|
||||
* </ul>
|
||||
*
|
||||
* <p>Complexities:
|
||||
* {@code offer, poll, remove(e), changeKey, decreaseKey, increaseKey} are O(log n);
|
||||
* {@code peek, isEmpty, size, contains} are O(1).
|
||||
*/
|
||||
public class IndexedPriorityQueue<E> {
|
||||
|
||||
/** Binary heap storage (min-heap). */
|
||||
private Object[] heap;
|
||||
|
||||
/** Number of elements in the heap. */
|
||||
private int size;
|
||||
|
||||
/** Comparator used for ordering; if null, elements must be Comparable. */
|
||||
private final Comparator<? super E> cmp;
|
||||
|
||||
/**
|
||||
* Index map: element -> current heap index.
|
||||
* <p>We use IdentityHashMap by default to:
|
||||
* <ul>
|
||||
* <li>allow duplicate-equals elements;</li>
|
||||
* <li>avoid corruption when equals/hashCode are mutable or not ID-based.</li>
|
||||
* </ul>
|
||||
* If you prefer value-based semantics, replace with HashMap<E,Integer> and
|
||||
* respect the warnings in the class Javadoc.
|
||||
*/
|
||||
private final IdentityHashMap<E, Integer> index;
|
||||
|
||||
private static final int DEFAULT_INITIAL_CAPACITY = 11;
|
||||
|
||||
public IndexedPriorityQueue() {
|
||||
this(DEFAULT_INITIAL_CAPACITY, null);
|
||||
}
|
||||
|
||||
public IndexedPriorityQueue(Comparator<? super E> cmp) {
|
||||
this(DEFAULT_INITIAL_CAPACITY, cmp);
|
||||
}
|
||||
|
||||
public IndexedPriorityQueue(int initialCapacity, Comparator<? super E> cmp) {
|
||||
if (initialCapacity < 1) {
|
||||
throw new IllegalArgumentException("initialCapacity < 1");
|
||||
}
|
||||
this.heap = new Object[initialCapacity];
|
||||
this.cmp = cmp;
|
||||
this.index = new IdentityHashMap<>();
|
||||
}
|
||||
|
||||
/** Returns current number of elements. */
|
||||
public int size() {
|
||||
return size;
|
||||
}
|
||||
|
||||
/** Returns {@code true} if empty. */
|
||||
public boolean isEmpty() {
|
||||
return size == 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the minimum element without removing it, or {@code null} if empty.
|
||||
* Matches {@link java.util.PriorityQueue#peek()} behavior.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public E peek() {
|
||||
return size == 0 ? null : (E) heap[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts the specified element (O(log n)).
|
||||
* @throws NullPointerException if {@code e} is null
|
||||
* @throws ClassCastException if {@code cmp == null} and {@code e} is not Comparable,
|
||||
* or if incompatible with other elements
|
||||
*/
|
||||
public boolean offer(E e) {
|
||||
Objects.requireNonNull(e, "element is null");
|
||||
if (size >= heap.length) {
|
||||
grow(size + 1);
|
||||
}
|
||||
// Insert at the end and bubble up. siftUp will maintain 'index' for all touched nodes.
|
||||
siftUp(size, e);
|
||||
size++;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes and returns the minimum element (O(log n)), or {@code null} if empty.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public E poll() {
|
||||
if (size == 0) {
|
||||
return null;
|
||||
}
|
||||
E min = (E) heap[0];
|
||||
removeAt(0); // updates map and heap structure
|
||||
return min;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes one occurrence of the specified element e (O(log n)) if present.
|
||||
* Uses the index map for O(1) lookup.
|
||||
*/
|
||||
public boolean remove(Object o) {
|
||||
Integer i = index.get(o);
|
||||
if (i == null) {
|
||||
return false;
|
||||
}
|
||||
removeAt(i);
|
||||
return true;
|
||||
}
|
||||
|
||||
/** O(1): returns whether the queue currently contains the given element reference. */
|
||||
public boolean contains(Object o) {
|
||||
return index.containsKey(o);
|
||||
}
|
||||
|
||||
/** Clears the heap and the index map. */
|
||||
public void clear() {
|
||||
Arrays.fill(heap, 0, size, null);
|
||||
index.clear();
|
||||
size = 0;
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Key update API
|
||||
// ------------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Changes comparator-relevant fields of {@code e} via the provided {@code mutator},
|
||||
* then restores the heap in O(log n) by bubbling in the correct direction.
|
||||
*
|
||||
* <p><b>IMPORTANT:</b> The mutator must not change {@code equals/hashCode} of {@code e}
|
||||
* if you migrate this implementation to value-based indexing (HashMap).
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code e} is not in the queue
|
||||
*/
|
||||
public void changeKey(E e, Consumer<E> mutator) {
|
||||
Integer i = index.get(e);
|
||||
if (i == null) {
|
||||
throw new IllegalArgumentException("Element not in queue");
|
||||
}
|
||||
// Mutate fields used by comparator (do NOT mutate equality/hash if using value-based map)
|
||||
mutator.accept(e);
|
||||
// Try bubbling up; if no movement occurred, bubble down.
|
||||
if (!siftUp(i)) {
|
||||
siftDown(i);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Faster variant if the new key is strictly smaller (higher priority).
|
||||
* Performs a single sift-up (O(log n)).
|
||||
*/
|
||||
public void decreaseKey(E e, Consumer<E> mutator) {
|
||||
Integer i = index.get(e);
|
||||
if (i == null) {
|
||||
throw new IllegalArgumentException("Element not in queue");
|
||||
}
|
||||
mutator.accept(e);
|
||||
siftUp(i);
|
||||
}
|
||||
|
||||
/**
|
||||
* Faster variant if the new key is strictly larger (lower priority).
|
||||
* Performs a single sift-down (O(log n)).
|
||||
*/
|
||||
public void increaseKey(E e, Consumer<E> mutator) {
|
||||
Integer i = index.get(e);
|
||||
if (i == null) {
|
||||
throw new IllegalArgumentException("Element not in queue");
|
||||
}
|
||||
mutator.accept(e);
|
||||
siftDown(i);
|
||||
}
|
||||
|
||||
// ------------------------------------------------------------------------------------
|
||||
// Internal utilities
|
||||
// ------------------------------------------------------------------------------------
|
||||
|
||||
/** Grows the internal array to accommodate at least {@code minCapacity}. */
|
||||
private void grow(int minCapacity) {
|
||||
int old = heap.length;
|
||||
int pref = (old < 64) ? old + 2 : old + (old >> 1); // +2 if small, else +50%
|
||||
int newCap = Math.max(minCapacity, pref);
|
||||
heap = Arrays.copyOf(heap, newCap);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private int compare(E a, E b) {
|
||||
if (cmp != null) {
|
||||
return cmp.compare(a, b);
|
||||
}
|
||||
return ((Comparable<? super E>) a).compareTo(b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Inserts item {@code x} at position {@code k}, bubbling up while maintaining the heap.
|
||||
* Also maintains the index map for all moved elements.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void siftUp(int k, E x) {
|
||||
while (k > 0) {
|
||||
int p = (k - 1) >>> 1;
|
||||
E e = (E) heap[p];
|
||||
if (compare(x, e) >= 0) {
|
||||
break;
|
||||
}
|
||||
heap[k] = e;
|
||||
index.put(e, k);
|
||||
k = p;
|
||||
}
|
||||
heap[k] = x;
|
||||
index.put(x, k);
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to bubble up the element currently at {@code k}.
|
||||
* @return true if it moved; false otherwise.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private boolean siftUp(int k) {
|
||||
int orig = k;
|
||||
E x = (E) heap[k];
|
||||
while (k > 0) {
|
||||
int p = (k - 1) >>> 1;
|
||||
E e = (E) heap[p];
|
||||
if (compare(x, e) >= 0) {
|
||||
break;
|
||||
}
|
||||
heap[k] = e;
|
||||
index.put(e, k);
|
||||
k = p;
|
||||
}
|
||||
if (k != orig) {
|
||||
heap[k] = x;
|
||||
index.put(x, k);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/** Bubbles down the element currently at {@code k}. */
|
||||
@SuppressWarnings("unchecked")
|
||||
private void siftDown(int k) {
|
||||
int n = size;
|
||||
E x = (E) heap[k];
|
||||
int half = n >>> 1; // loop while k has at least one child
|
||||
while (k < half) {
|
||||
int child = (k << 1) + 1; // assume left is smaller
|
||||
E c = (E) heap[child];
|
||||
int r = child + 1;
|
||||
if (r < n && compare(c, (E) heap[r]) > 0) {
|
||||
child = r;
|
||||
c = (E) heap[child];
|
||||
}
|
||||
if (compare(x, c) <= 0) {
|
||||
break;
|
||||
}
|
||||
heap[k] = c;
|
||||
index.put(c, k);
|
||||
k = child;
|
||||
}
|
||||
heap[k] = x;
|
||||
index.put(x, k);
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the element at heap index {@code i}, restoring the heap afterwards.
|
||||
* <p>Returns nothing; the standard {@code PriorityQueue} returns a displaced
|
||||
* element in a rare case to help its iterator. We don't need that here, so
|
||||
* we keep the API simple.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private void removeAt(int i) {
|
||||
int n = --size; // last index after removal
|
||||
E moved = (E) heap[n];
|
||||
E removed = (E) heap[i];
|
||||
heap[n] = null; // help GC
|
||||
index.remove(removed); // drop mapping for removed element
|
||||
|
||||
if (i == n) {
|
||||
return; // removed last element; done
|
||||
}
|
||||
|
||||
heap[i] = moved;
|
||||
index.put(moved, i);
|
||||
|
||||
// Try sift-up first (cheap if key decreased); if no movement, sift-down.
|
||||
if (!siftUp(i)) {
|
||||
siftDown(i);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,350 @@
|
||||
package com.thealgorithms.datastructures.heaps;
|
||||
|
||||
import java.util.Comparator;
|
||||
import org.junit.jupiter.api.Assertions;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
/**
|
||||
* Tests for {@link IndexedPriorityQueue}.
|
||||
*
|
||||
* Notes:
|
||||
* - We mainly use a Node class with a mutable "prio" field to test changeKey/decreaseKey/increaseKey.
|
||||
* - The queue is a min-heap, so smaller "prio" means higher priority.
|
||||
* - By default the implementation uses IdentityHashMap so duplicate-equals objects are allowed.
|
||||
*/
|
||||
public class IndexedPriorityQueueTest {
|
||||
|
||||
// ------------------------
|
||||
// Helpers
|
||||
// ------------------------
|
||||
|
||||
/** Simple payload with mutable priority. */
|
||||
static class Node {
|
||||
final String id;
|
||||
int prio; // lower is better (min-heap)
|
||||
|
||||
Node(String id, int prio) {
|
||||
this.id = id;
|
||||
this.prio = prio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id + "(" + prio + ")";
|
||||
}
|
||||
}
|
||||
|
||||
/** Same as Node but overrides equals/hashCode to simulate "duplicate-equals" scenario. */
|
||||
static class NodeWithEquals {
|
||||
final String id;
|
||||
int prio;
|
||||
|
||||
NodeWithEquals(String id, int prio) {
|
||||
this.id = id;
|
||||
this.prio = prio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof NodeWithEquals)) {
|
||||
return false;
|
||||
}
|
||||
NodeWithEquals other = (NodeWithEquals) o;
|
||||
// Intentionally naive equality: equal if priority is equal
|
||||
return this.prio == other.prio;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Integer.hashCode(prio);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return id + "(" + prio + ")";
|
||||
}
|
||||
}
|
||||
|
||||
private static IndexedPriorityQueue<Node> newNodePQ() {
|
||||
return new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
|
||||
}
|
||||
|
||||
// ------------------------
|
||||
// Basic operations
|
||||
// ------------------------
|
||||
|
||||
@Test
|
||||
void testOfferPollWithIntegersComparableMode() {
|
||||
// cmp == null -> elements must be Comparable
|
||||
IndexedPriorityQueue<Integer> pq = new IndexedPriorityQueue<>();
|
||||
Assertions.assertTrue(pq.isEmpty());
|
||||
|
||||
pq.offer(5);
|
||||
pq.offer(1);
|
||||
pq.offer(3);
|
||||
|
||||
Assertions.assertEquals(3, pq.size());
|
||||
Assertions.assertEquals(1, pq.peek());
|
||||
Assertions.assertEquals(1, pq.poll());
|
||||
Assertions.assertEquals(3, pq.poll());
|
||||
Assertions.assertEquals(5, pq.poll());
|
||||
Assertions.assertNull(pq.poll()); // empty -> null
|
||||
Assertions.assertTrue(pq.isEmpty());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testPeekAndIsEmpty() {
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Assertions.assertTrue(pq.isEmpty());
|
||||
Assertions.assertNull(pq.peek());
|
||||
|
||||
pq.offer(new Node("A", 10));
|
||||
pq.offer(new Node("B", 5));
|
||||
pq.offer(new Node("C", 7));
|
||||
|
||||
Assertions.assertFalse(pq.isEmpty());
|
||||
Assertions.assertEquals("B(5)", pq.peek().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testRemoveSpecificElement() {
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Node a = new Node("A", 10);
|
||||
Node b = new Node("B", 5);
|
||||
Node c = new Node("C", 7);
|
||||
|
||||
pq.offer(a);
|
||||
pq.offer(b);
|
||||
pq.offer(c);
|
||||
|
||||
// remove by reference (O(log n))
|
||||
Assertions.assertTrue(pq.remove(b));
|
||||
Assertions.assertEquals(2, pq.size());
|
||||
// now min should be C(7)
|
||||
Assertions.assertEquals("C(7)", pq.peek().toString());
|
||||
// removing an element not present -> false
|
||||
Assertions.assertFalse(pq.remove(b));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testContainsAndClear() {
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Node a = new Node("A", 2);
|
||||
Node b = new Node("B", 3);
|
||||
|
||||
pq.offer(a);
|
||||
pq.offer(b);
|
||||
|
||||
Assertions.assertTrue(pq.contains(a));
|
||||
Assertions.assertTrue(pq.contains(b));
|
||||
|
||||
pq.clear();
|
||||
Assertions.assertTrue(pq.isEmpty());
|
||||
Assertions.assertFalse(pq.contains(a));
|
||||
Assertions.assertNull(pq.peek());
|
||||
}
|
||||
|
||||
// ------------------------
|
||||
// Key updates
|
||||
// ------------------------
|
||||
|
||||
@Test
|
||||
void testDecreaseKeyMovesUp() {
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Node a = new Node("A", 10);
|
||||
Node b = new Node("B", 5);
|
||||
Node c = new Node("C", 7);
|
||||
|
||||
pq.offer(a);
|
||||
pq.offer(b);
|
||||
pq.offer(c);
|
||||
|
||||
// current min is B(5)
|
||||
Assertions.assertEquals("B(5)", pq.peek().toString());
|
||||
|
||||
// Make A more important: 10 -> 1 (smaller is better)
|
||||
pq.decreaseKey(a, n -> n.prio = 1);
|
||||
|
||||
// Now A should be at the top
|
||||
Assertions.assertEquals("A(1)", pq.peek().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIncreaseKeyMovesDown() {
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Node a = new Node("A", 1);
|
||||
Node b = new Node("B", 2);
|
||||
Node c = new Node("C", 3);
|
||||
|
||||
pq.offer(a);
|
||||
pq.offer(b);
|
||||
pq.offer(c);
|
||||
|
||||
// min is A(1)
|
||||
Assertions.assertEquals("A(1)", pq.peek().toString());
|
||||
|
||||
// Make A worse: 1 -> 100
|
||||
pq.increaseKey(a, n -> n.prio = 100);
|
||||
|
||||
// Now min should be B(2)
|
||||
Assertions.assertEquals("B(2)", pq.peek().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangeKeyChoosesDirectionAutomatically() {
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Node a = new Node("A", 10);
|
||||
Node b = new Node("B", 20);
|
||||
Node c = new Node("C", 30);
|
||||
|
||||
pq.offer(a);
|
||||
pq.offer(b);
|
||||
pq.offer(c);
|
||||
|
||||
// Decrease B to 0 -> should move up
|
||||
pq.changeKey(b, n -> n.prio = 0);
|
||||
Assertions.assertEquals("B(0)", pq.peek().toString());
|
||||
|
||||
// Increase B to 100 -> should move down
|
||||
pq.changeKey(b, n -> n.prio = 100);
|
||||
Assertions.assertEquals("A(10)", pq.peek().toString());
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDirectMutationWithoutChangeKeyDoesNotReheapByDesign() {
|
||||
// Demonstrates the contract: do NOT mutate comparator fields directly.
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Node a = new Node("A", 5);
|
||||
Node b = new Node("B", 10);
|
||||
|
||||
pq.offer(a);
|
||||
pq.offer(b);
|
||||
|
||||
// Illegally mutate priority directly
|
||||
a.prio = 100; // worse than b now, but heap wasn't notified
|
||||
|
||||
// The heap structure is unchanged; peek still returns A(100) (was A(5) before)
|
||||
// This test documents the behavior/contract rather than relying on it.
|
||||
Assertions.assertEquals("A(100)", pq.peek().toString());
|
||||
|
||||
// Now fix properly via changeKey (no change in value, but triggers reheap)
|
||||
pq.changeKey(a, n -> n.prio = n.prio);
|
||||
Assertions.assertEquals("B(10)", pq.peek().toString());
|
||||
}
|
||||
|
||||
// ------------------------
|
||||
// Identity semantics & duplicates
|
||||
// ------------------------
|
||||
|
||||
@Test
|
||||
void testDuplicateEqualsElementsAreSupportedIdentityMap() {
|
||||
IndexedPriorityQueue<NodeWithEquals> pq = new IndexedPriorityQueue<>(Comparator.comparingInt(n -> n.prio));
|
||||
|
||||
NodeWithEquals x1 = new NodeWithEquals("X1", 7);
|
||||
NodeWithEquals x2 = new NodeWithEquals("X2", 7); // equals to X1 by prio, but different instance
|
||||
|
||||
// With IdentityHashMap internally, both can coexist
|
||||
pq.offer(x1);
|
||||
pq.offer(x2);
|
||||
|
||||
Assertions.assertEquals(2, pq.size());
|
||||
// Poll twice; both 7s should be returned (order between x1/x2 is unspecified)
|
||||
Assertions.assertEquals(7, pq.poll().prio);
|
||||
Assertions.assertEquals(7, pq.poll().prio);
|
||||
Assertions.assertTrue(pq.isEmpty());
|
||||
}
|
||||
|
||||
// ------------------------
|
||||
// Capacity growth
|
||||
// ------------------------
|
||||
|
||||
@Test
|
||||
void testGrowByManyInserts() {
|
||||
IndexedPriorityQueue<Integer> pq = new IndexedPriorityQueue<>();
|
||||
int n = 100; // beyond default capacity (11)
|
||||
|
||||
for (int i = n; i >= 1; i--) {
|
||||
pq.offer(i);
|
||||
}
|
||||
|
||||
Assertions.assertEquals(n, pq.size());
|
||||
// Ensure min-to-max order when polling
|
||||
for (int expected = 1; expected <= n; expected++) {
|
||||
Integer v = pq.poll();
|
||||
Assertions.assertEquals(expected, v);
|
||||
}
|
||||
Assertions.assertTrue(pq.isEmpty());
|
||||
Assertions.assertNull(pq.poll());
|
||||
}
|
||||
|
||||
// ------------------------
|
||||
// remove/contains edge cases
|
||||
// ------------------------
|
||||
|
||||
@Test
|
||||
void testRemoveHeadAndMiddleAndTail() {
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Node a = new Node("A", 1);
|
||||
Node b = new Node("B", 2);
|
||||
Node c = new Node("C", 3);
|
||||
Node d = new Node("D", 4);
|
||||
|
||||
pq.offer(a);
|
||||
pq.offer(b);
|
||||
pq.offer(c);
|
||||
pq.offer(d);
|
||||
|
||||
// remove head
|
||||
Assertions.assertTrue(pq.remove(a));
|
||||
Assertions.assertFalse(pq.contains(a));
|
||||
Assertions.assertEquals("B(2)", pq.peek().toString());
|
||||
|
||||
// remove middle
|
||||
Assertions.assertTrue(pq.remove(c));
|
||||
Assertions.assertFalse(pq.contains(c));
|
||||
Assertions.assertEquals("B(2)", pq.peek().toString());
|
||||
|
||||
// remove tail (last)
|
||||
Assertions.assertTrue(pq.remove(d));
|
||||
Assertions.assertFalse(pq.contains(d));
|
||||
Assertions.assertEquals("B(2)", pq.peek().toString());
|
||||
|
||||
// remove last remaining
|
||||
Assertions.assertTrue(pq.remove(b));
|
||||
Assertions.assertTrue(pq.isEmpty());
|
||||
Assertions.assertNull(pq.peek());
|
||||
}
|
||||
|
||||
// ------------------------
|
||||
// Error / edge cases for coverage
|
||||
// ------------------------
|
||||
|
||||
@Test
|
||||
void testInvalidInitialCapacityThrows() {
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> new IndexedPriorityQueue<Integer>(0, Comparator.naturalOrder()));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testChangeKeyOnMissingElementThrows() {
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Node a = new Node("A", 10);
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> pq.changeKey(a, n -> n.prio = 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testDecreaseKeyOnMissingElementThrows() {
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Node a = new Node("A", 10);
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> pq.decreaseKey(a, n -> n.prio = 5));
|
||||
}
|
||||
|
||||
@Test
|
||||
void testIncreaseKeyOnMissingElementThrows() {
|
||||
IndexedPriorityQueue<Node> pq = newNodePQ();
|
||||
Node a = new Node("A", 10);
|
||||
|
||||
Assertions.assertThrows(IllegalArgumentException.class, () -> pq.increaseKey(a, n -> n.prio = 15));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user