5
\$\begingroup\$

(The entire project is here.)

Intro

I have this priority queue data structure for non-negative integer priority keys. I recall that it is called Dial's heap.

Code

Implementation

com.github.coderodde.util.IntegerMinimumPriorityQueue.java:

package com.github.coderodde.util;
/**
 * This interface defines the API for minimum priority queues with integer 
 * priority keys.
 * 
 * @param <D> the data type of the satellite datums.
 * 
 * @version 1.0.0 (May 10, 2024)
 * @since 1.0.0 (May 10, 2024)
 */
public interface IntegerMinimumPriorityQueue<D> extends Iterable<D>, Cloneable {
 
 /**
 * Inserts a new datum {@code datum} to this heap with the given priority 
 * {@code priority}.
 * 
 * @param datum the datum to insert.
 * @param priority the priority key to set.
 */
 public void insert(final D datum, final int priority);
 
 /**
 * Updates the priority of the satellite datum {@code datum} to 
 * {@code priority}. This method can handle both increasing and decreasing 
 * of the priority key.
 * 
 * @param datum the datum of which priority to update.
 * @param priority the new priority of {@code datum}. 
 */
 public void updatePriority(final D datum, final int priority);
 
 /**
 * Returns the minimal priority throughout the contents of this heap. If 
 * this heap is empty, {@code -1} is returned.
 * 
 * @return the minimal priority.
 */
 public int minimumPriority();
 
 /**
 * Returns the datum with the lowest priority key, or {@code null} if this
 * heap is empty.
 * 
 * @return the datum with the lowest priority key, or {@code null} if this
 * heap is empty.
 */
 public D minimumNode();
 /**
 * Returns {@code true} if the {@code datum} is stored in this heap.
 * 
 * @param datum the query datum.
 * 
 * @return {@code true} if the {@code datum} is stored in this heap.
 */
 public boolean containsDatum(final D datum);
 
 /**
 * Returns the current priority of the input datum.
 * 
 * @param datum the datum to query.
 * @return the current priority of {@code datum}.
 */
 public int getPriority(final D datum);
 
 /**
 * Removes and returns the datum with the lowest priority key, or 
 * {@code null} if this heap is empty.
 * 
 * @return the datum with the lowest priority key, or {@code null} if this 
 * heap is empty.
 */
 public D extractMinimum();
 
 /**
 * Removes the datum {@code datum} from this heap.
 * 
 * @param datum to remove.
 */
 public void remove(final D datum);
 
 /**
 * Clears all the data from this heap.
 */
 public void clear();
 
 /**
 * Since the heap cannot contract the collision chain table, the remedy to 
 * do that is to clone it which will return another heap with the same 
 * content, but with the table as small as is necessary to accommodate also
 * the maximum priority nodes.
 * 
 * @return the clone of this heap.
 */
 public Object clone();
 
 /**
 * Returns the number of datums stored in this heap.
 * 
 * @return the number of datums stored in this heap.
 */
 public int size();
}

com.github.coderodde.util.DialsHeap.java:

package com.github.coderodde.util;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
/**
 * This class implements a priority queue data structure called a Dial's heap.
 * 
 * @param <D> the type of the satellite data.
 * 
 * @version 1.0.1 (May 12, 2024)
 * @since 1.0.0 (May 10, 2024)
 */
public class DialsHeap<D> implements IntegerMinimumPriorityQueue<D> {
 
 /**
 * This static inner class implements the node type for this heap.
 * 
 * @param <D> the satellite data type.
 */
 private static final class DialsHeapNode<D> {
 
 /**
 * The actual satellite datum.
 */
 final D datum;
 
 /**
 * The priority key of this node. Must be at least zero (0).
 */
 int priority;
 
 /**
 * The previous node in the collision chain or {@code null} if this node
 * is the head of the collision chain.
 */
 DialsHeapNode<D> prev;
 
 /**
 * The next node in the collision chain or {@code null} if this node is
 * the tail of the collision chain.
 */
 DialsHeapNode<D> next;
 
 /**
 * Constructs a new heap node.'
 * 
 * @param datum the satellite datum.
 * @param priority the priority key.
 */
 DialsHeapNode(final D datum, final int priority) {
 this.datum = datum;
 this.priority = priority;
 }
 }
 
 /**
 * This inner class implements the iterator over all satellite data in this
 * heap in the ascending priority key order.
 */
 private final class DialsHeapIterator implements Iterator<D> {
 /**
 * Caches the number of nodes already iterated.
 */
 private int iterated = 0;
 
 /**
 * The current heap node.
 */
 private DialsHeapNode<D> currentDialsHeapNode;
 
 /**
 * Constructs a new iterator over the enclosing heap.
 */
 private DialsHeapIterator() {
 // Attempt to find the head node:
 for (final DialsHeapNode<D> headNode : table) {
 if (headNode != null) {
 currentDialsHeapNode = headNode;
 return;
 }
 }
 
 // Once here, the heap is empty, return null:
 currentDialsHeapNode = null;
 }
 
 /**
 * {@inheritDoc } 
 */
 @Override
 public boolean hasNext() {
 return iterated < nodeMap.size();
 }
 /**
 * {@inheritDoc} 
 */
 @Override
 public D next() {
 if (!hasNext()) {
 throw new NoSuchElementException("Nothing to iterate left.");
 }
 
 final D returnElement = currentDialsHeapNode.datum;
 iterated++;
 currentDialsHeapNode = computeNextDialsHeapNode();
 return returnElement;
 }
 
 /**
 * Returns the next heap node.
 * 
 * @return the next heap node in the iteration order.
 */
 private DialsHeapNode<D> computeNextDialsHeapNode() {
 if (iterated == nodeMap.size()) {
 // Once here, iteration is complete.
 return null;
 }
 
 if (currentDialsHeapNode.next != null) {
 // currentDialsHeapNode has minimum priority key, move to its 
 // right sibling/neighbor in the collision chain:
 return currentDialsHeapNode.next;
 }
 
 // Search the next smallest priority node:
 for (int p = currentDialsHeapNode.priority + 1;
 p < table.length; 
 p++) {
 
 if (table[p] != null) {
 // Found!
 return table[p];
 }
 }
 
 // We should never ever get here.
 throw new IllegalStateException("Should not get here.");
 }
 }
 
 /**
 * The default table capacity.
 */
 private static final int DEFAULT_TABLE_CAPACITY = 8;
 
 /**
 * The table mapping each slot to the head of a collision chain.
 */
 private DialsHeapNode<D>[] table;
 
 /**
 * The map mapping the satellite datums to their respective heap nodes.
 */
 private final Map<D, DialsHeapNode<D>> nodeMap = new HashMap<>();
 
 /**
 * Constructs a heap with {@code tableCapacity} as the capacity of the 
 * internal collision chain table.
 * 
 * @param tableCapacity the requested collision chain capacity.
 */
 public DialsHeap(final int tableCapacity) {
 this.table = new DialsHeapNode[tableCapacity];
 }
 
 /**
 * Constructs a heap0 with default collision chain table capacity.
 */
 public DialsHeap() {
 this(DEFAULT_TABLE_CAPACITY);
 }
 
 /**
 * {@inheritDoc }
 */
 @Override
 public Iterator<D> iterator() {
 return new DialsHeapIterator();
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public void insert(final D datum, final int priority) {
 
 if (nodeMap.containsKey(datum)) {
 // Once here, the input datum is already in this heap. Use 
 // updatePriority for adjusting the priority key.
 return;
 }
 
 checkPriority(priority);
 
 if (mustExpand(priority)) {
 expand(priority);
 }
 
 final DialsHeapNode<D> node = new DialsHeapNode<>(datum, priority);
 
 nodeMap.put(datum, node);
 linkImpl(node, priority);
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public void updatePriority(final D datum, final int priority) {
 
 if (mustExpand(priority)) {
 expand(priority);
 }
 
 final DialsHeapNode<D> node = nodeMap.get(datum);
 
 unlinkImpl(node);
 linkImpl(node, priority);
 node.priority = priority;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public int minimumPriority() {
 return accessMinimumPriorityNode().priority;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public D minimumNode() {
 if (nodeMap.isEmpty()) {
 return null;
 }
 
 return accessMinimumPriorityNode().datum;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public boolean containsDatum(final D datum) {
 return nodeMap.containsKey(datum);
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public int getPriority(final D datum) {
 return nodeMap.get(datum).priority;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public D extractMinimum() {
 if (nodeMap.isEmpty()) {
 return null;
 }
 
 final DialsHeapNode<D> node = accessMinimumPriorityNode();
 
 unlinkImpl(node);
 nodeMap.remove(node.datum);
 return node.datum;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public void remove(final D datum) {
 unlinkImpl(nodeMap.get(datum));
 nodeMap.remove(datum);
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public void clear() {
 nodeMap.clear();
 Arrays.fill(table, null);
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public Object clone() {
 final int maximumPriorityKey = getMaximumPriority();
 final int cloneCapacity = getNextCapacity(maximumPriorityKey);
 final DialsHeap<D> copy = new DialsHeap<>(cloneCapacity);
 
 for (final Map.Entry<D, DialsHeapNode<D>> entry : nodeMap.entrySet()) {
 copy.insert(entry.getValue().datum, entry.getValue().priority);
 }
 
 return copy;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public int size() {
 return nodeMap.size();
 }
 
 /**
 * {@inheritDoc } 
 */
 @Override
 public boolean isEmpty() {
 return nodeMap.isEmpty();
 }
 
 /**
 * Returns the head of the collision chain with the lowest priority key.
 * 
 * @return the head of the collision chain with the lowest priority key.
 */
 private DialsHeapNode<D> accessMinimumPriorityNode() {
 for (int p = 0; p != table.length; p++) {
 if (table[p] != null) {
 return table[p];
 }
 }
 
 throw new IllegalStateException("Should not get here.");
 }
 
 /**
 * Links the node {@code node} to the head of the collision chain with 
 * priority key {@code priority}.
 * 
 * @param node the node to link.
 * @param priority the priority key to link with.
 */
 private void linkImpl(final DialsHeapNode<D> node, final int priority) {
 final DialsHeapNode<D> currentBucketHead = table[priority];
 
 if (currentBucketHead != null) {
 node.next = currentBucketHead;
 currentBucketHead.prev = node;
 } 
 
 table[priority] = node;
 }
 
 /**
 * Unlinks the node {@code node} from this heap.
 * 
 * @param node the node to unlink.
 */
 private void unlinkImpl(final DialsHeapNode<D> node) {
 if (node.prev != null) {
 node.prev.next = node.next;
 
 if (node.next != null) {
 node.next.prev = node.prev;
 node.next = null;
 }
 
 node.prev = null;
 } else {
 // Once here, node.prev == null!
 if (node.next != null) {
 node.next.prev = null;
 table[node.priority] = node.next;
 node.next = null;
 } else {
 // Remove the last node in the collision chain:
 table[node.priority] = null;
 }
 }
 }
 
 /**
 * Returns {@code true} if this heap's table cannot accommodate a node with
 * priority {@code priority}.
 * 
 * @param priority the priority to query.
 * 
 * @return {@code true} if the table must be expanded.
 */
 private boolean mustExpand(final int priority) {
 return priority >= table.length;
 }
 
 /**
 * Expands the internal table {@code table} such that it can accommodate the
 * priority key {@code priority}, while being smallest such table.
 * 
 * @param priority the requested priority key.
 */
 private void expand(final int priority) {
 final int nextCapacity = getNextCapacity(priority);
 this.table = Arrays.copyOf(table, nextCapacity);
 }
 
 /**
 * Returns the capacity that is sufficiently large in order to accommodate
 * the heap nodes with priority {@code priority}.
 * 
 * @param priority the requested priority.
 * 
 * @return the next capacity to expand with. 
 */
 private int getNextCapacity(final int priority) {
 int nextCapacity = table.length;
 
 while (nextCapacity <= priority) {
 nextCapacity *= 2;
 }
 
 return nextCapacity;
 }
 
 /**
 * Returns the maximum priority key in this heap.
 * 
 * @return the maximum priority key in this heap.
 */
 private int getMaximumPriority() {
 for (int priority = table.length - 1; priority >= 0; priority--) {
 if (table[priority] != null) {
 return priority;
 }
 }
 
 return -1;
 }
 
 /**
 * Makes sure that the input priority is non-negative.
 * 
 * @param priority the priority to check.
 * 
 * @throws IllegalArgumentException if the input priority is negative.
 */
 private void checkPriority(final int priority) {
 if (priority < 0) {
 throw new IllegalArgumentException(
 String.format(
 "The input priority is negtive (%d).\n",
 priority));
 }
 }
}

com.github.coderodde.util.CachedDialsHeap.java:

package com.github.coderodde.util;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.NoSuchElementException;
/**
 * This class implements a priority queue data structure called a Dial's heap.
 * 
 * @param <D> the type of the satellite data.
 * 
 * @version 1.0.0 (May 10, 2024)
 * @since 1.0.0
 */
public class CachedDialsHeap<D> implements IntegerMinimumPriorityQueue<D> {
 
 /**
 * This static inner class implements the node type for this heap.
 * 
 * @param <D> the satellite data type.
 */
 private static final class CachedDialsHeapNode<D> {
 
 /**
 * The actual satellite datum.
 */
 final D datum;
 
 /**
 * The priority key of this node. Must be at least zero (0).
 */
 int priority;
 
 /**
 * The previous node in the collision chain or {@code null} if this node
 * is the head of the collision chain.
 */
 CachedDialsHeapNode<D> prev;
 
 /**
 * The next node in the collision chain or {@code null} if this node is
 * the tail of the collision chain.
 */
 CachedDialsHeapNode<D> next;
 
 /**
 * Constructs a new heap node.'
 * 
 * @param datum the satellite datum.
 * @param priority the priority key.
 */
 CachedDialsHeapNode(final D datum, final int priority) {
 this.datum = datum;
 this.priority = priority;
 }
 }
 
 /**
 * This inner class implements the iterator over all satellite data in this
 * heap in the ascending priority key order.
 */
 private final class CachedDialsHeapIterator implements Iterator<D> {
 /**
 * Caches the number of nodes already iterated.
 */
 private int iterated = 0;
 
 /**
 * The current heap node.
 */
 private CachedDialsHeapNode<D> currentDialsHeapNode;
 
 /**
 * Constructs a new iterator over the enclosing heap.
 */
 private CachedDialsHeapIterator() {
 // Attempt to find the head node:
 for (final CachedDialsHeapNode<D> headNode : table) {
 if (headNode != null) {
 currentDialsHeapNode = headNode;
 return;
 }
 }
 
 // Once here, the heap is empty, return null:
 currentDialsHeapNode = null;
 }
 
 /**
 * {@inheritDoc } 
 */
 @Override
 public boolean hasNext() {
 return iterated < nodeMap.size();
 }
 /**
 * {@inheritDoc} 
 */
 @Override
 public D next() {
 if (!hasNext()) {
 throw new NoSuchElementException("Nothing to iterate left.");
 }
 
 final D returnElement = currentDialsHeapNode.datum;
 iterated++;
 currentDialsHeapNode = computeNextDialsHeapNode();
 return returnElement;
 }
 
 /**
 * Returns the next heap node.
 * 
 * @return the next heap node in the iteration order.
 */
 private CachedDialsHeapNode<D> computeNextDialsHeapNode() {
 if (iterated == nodeMap.size()) {
 // Once here, iteration is complete.
 return null;
 }
 
 if (currentDialsHeapNode.next != null) {
 // currentDialsHeapNode has minimum priority key, move to its 
 // right sibling/neighbor in the collision chain:
 return currentDialsHeapNode.next;
 }
 
 // Search the next smallest priority node:
 for (int p = currentDialsHeapNode.priority + 1;
 p < table.length; 
 p++) {
 
 if (table[p] != null) {
 // Found!
 return table[p];
 }
 }
 
 // We should never ever get here.
 throw new IllegalStateException("Should not get here.");
 }
 }
 
 /**
 * The default table capacity.
 */
 private static final int DEFAULT_TABLE_CAPACITY = 8;
 
 /**
 * The table mapping each slot to the head of a collision chain.
 */
 private CachedDialsHeapNode<D>[] table;
 
 /**
 * The map mapping the satellite datums to their respective heap nodes.
 */
 private final Map<D, CachedDialsHeapNode<D>> nodeMap = new HashMap<>();
 
 /**
 * Caches the minimum priority so that {@link #extractMinimum()} and
 * {@link #minimumNode()} and {@link #minimumPriority()} run in constant 
 * time.
 */
 private int minimumPriority = Integer.MAX_VALUE;
 
 /**
 * Constructs a heap with {@code tableCapacity} as the capacity of the 
 * internal collision chain table.
 * 
 * @param tableCapacity the requested collision chain capacity.
 */
 public CachedDialsHeap(final int tableCapacity) {
 this.table = new CachedDialsHeapNode[tableCapacity];
 }
 
 /**
 * Constructs a heap0 with default collision chain table capacity.
 */
 public CachedDialsHeap() {
 this(DEFAULT_TABLE_CAPACITY);
 }
 
 /**
 * {@inheritDoc }
 */
 @Override
 public Iterator<D> iterator() {
 return new CachedDialsHeapIterator();
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public void insert(final D datum, final int priority) {
 if (nodeMap.containsKey(datum)) {
 // Once here, the input datum is already in this heap. Use 
 // updatePriority for adjusting the priority key.
 return;
 }
 
 checkPriority(priority);
 
 minimumPriority = Math.min(minimumPriority, priority);
 
 if (mustExpand(priority)) {
 expand(priority);
 }
 
 final CachedDialsHeapNode<D> node = 
 new CachedDialsHeapNode<>(
 datum, 
 priority);
 
 nodeMap.put(datum, node);
 linkImpl(node, priority);
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public void updatePriority(final D datum, final int priority) {
 checkPriority(priority);
 
 minimumPriority = Math.min(minimumPriority, priority);
 
 if (mustExpand(priority)) {
 expand(priority);
 }
 
 final CachedDialsHeapNode<D> node = nodeMap.get(datum);
 
 unlinkImpl(node);
 linkImpl(node, priority);
 node.priority = priority;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public int minimumPriority() {
 return minimumPriority;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public D minimumNode() {
 if (nodeMap.isEmpty()) {
 return null;
 }
 
 return table[minimumPriority].datum;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public boolean containsDatum(final D datum) {
 return nodeMap.containsKey(datum);
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public int getPriority(final D datum) {
 return nodeMap.get(datum).priority;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public D extractMinimum() {
 if (nodeMap.isEmpty()) {
 return null;
 }
 
 final CachedDialsHeapNode<D> node = accessMinimumPriorityNode();
 
 unlinkImpl(node);
 nodeMap.remove(node.datum);
 
 if (table[minimumPriority] == null) {
 updateMinimumPriority();
 }
 
 return node.datum;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public void remove(final D datum) {
 final CachedDialsHeapNode<D> node = nodeMap.get(datum);
 unlinkImpl(node);
 nodeMap.remove(datum);
 
 if (table[node.priority] == null) {
 updateMinimumPriority();
 }
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public void clear() {
 minimumPriority = Integer.MAX_VALUE;
 nodeMap.clear();
 Arrays.fill(table, null);
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public Object clone() {
 final int maximumPriorityKey = getMaximumPriority();
 final int cloneCapacity = getNextCapacity(maximumPriorityKey);
 final CachedDialsHeap<D> copy = new CachedDialsHeap<>(cloneCapacity);
 
 for (final Map.Entry<D, CachedDialsHeapNode<D>> entry : nodeMap.entrySet()) {
 copy.insert(entry.getValue().datum, entry.getValue().priority);
 }
 
 return copy;
 }
 
 /**
 * {@inheritDoc} 
 */
 @Override
 public int size() {
 return nodeMap.size();
 }
 
 /**
 * {@inheritDoc } 
 */
 @Override
 public boolean isEmpty() {
 return nodeMap.isEmpty();
 }
 
 /**
 * Returns the head of the collision chain with the lowest priority key.
 * 
 * @return the head of the collision chain with the lowest priority key.
 */
 private CachedDialsHeapNode<D> accessMinimumPriorityNode() {
 for (int p = 0; p != table.length; p++) {
 if (table[p] != null) {
 return table[p];
 }
 }
 
 throw new IllegalStateException("Should not get here.");
 }
 
 /**
 * Links the node {@code node} to the head of the collision chain with 
 * priority key {@code priority}.
 * 
 * @param node the node to link.
 * @param priority the priority key to link with.
 */
 private void linkImpl(final CachedDialsHeapNode<D> node, final int priority) {
 final CachedDialsHeapNode<D> currentBucketHead = table[priority];
 
 if (currentBucketHead != null) {
 node.next = currentBucketHead;
 currentBucketHead.prev = node;
 } 
 
 table[priority] = node;
 }
 
 /**
 * Unlinks the node {@code node} from this heap.
 * 
 * @param node the node to unlink.
 */
 private void unlinkImpl(final CachedDialsHeapNode<D> node) {
 if (node.prev != null) {
 node.prev.next = node.next;
 
 if (node.next != null) {
 node.next.prev = node.prev;
 node.next = null;
 }
 
 node.prev = null;
 } else {
 // Once here, node.prev == null!
 if (node.next != null) {
 node.next.prev = null;
 table[node.priority] = node.next;
 node.next = null;
 } else {
 // Remove the last node in the collision chain:
 table[node.priority] = null;
 }
 }
 }
 
 /**
 * Returns {@code true} if this heap's table cannot accommodate a node with
 * priority {@code priority}.
 * 
 * @param priority the priority to query.
 * 
 * @return {@code true} if the table must be expanded.
 */
 private boolean mustExpand(final int priority) {
 return priority >= table.length;
 }
 
 /**
 * Expands the internal table {@code table} such that it can accommodate the
 * priority key {@code priority}, while being smallest such table.
 * 
 * @param priority the requested priority key.
 */
 private void expand(final int priority) {
 final int nextCapacity = getNextCapacity(priority);
 this.table = Arrays.copyOf(table, nextCapacity);
 }
 
 /**
 * Returns the capacity that is sufficiently large in order to accommodate
 * the heap nodes with priority {@code priority}.
 * 
 * @param priority the requested priority.
 * 
 * @return the next capacity to expand with. 
 */
 private int getNextCapacity(final int priority) {
 int nextCapacity = table.length;
 
 while (nextCapacity <= priority) {
 nextCapacity *= 2;
 }
 
 return nextCapacity;
 }
 
 /**
 * Returns the maximum priority key in this heap.
 * 
 * @return the maximum priority key in this heap.
 */
 private int getMaximumPriority() {
 for (int priority = table.length - 1; priority >= 0; priority--) {
 if (table[priority] != null) {
 return priority;
 }
 }
 
 return -1;
 }
 
 /**
 * Makes sure that the input priority is non-negative.
 * 
 * @param priority the priority to check.
 * 
 * @throws IllegalArgumentException if the input priority is negative.
 */
 private void checkPriority(final int priority) {
 if (priority < 0) {
 throw new IllegalArgumentException(
 String.format(
 "The input priority is negtive (%d).\n",
 priority));
 }
 }
 
 /**
 * Updates the minimum priority.
 */
 private void updateMinimumPriority() {
 for (int p = minimumPriority + 1; p < table.length; p++) {
 if (table[p] != null) {
 minimumPriority = p;
 return;
 }
 }
 
 minimumPriority = -1;
 }
}

Benchmark

com.github.coderodde.util.benchmark.Benchmark.java:

package com.github.coderodde.util.benchmark;
import com.github.coderodde.util.CachedDialsHeap;
import com.github.coderodde.util.DialsHeap;
import com.github.coderodde.util.IntegerMinimumPriorityQueue;
import java.util.Arrays;
import java.util.Random;
/**
 * This class implements the benchmark program for the priority queues.
 * 
 * @version 1.0.1 (May 12, 2024)
 * @since 1.0.0 (May 11, 2024)
 */
public final class Benchmark {
 
 /**
 * The upper bound value for the randomly generated integers.
 */
 private static final int UPPER_BOUND = 150_000;
 
 /**
 * The benchmarking array lengths.
 */
 private static final int ARRAY_LENGTH = 1_000_000;
 
 /**
 * The entry point of the benchmark.
 * 
 * @param args the command line arguments.
 */
 public static void main(String[] args) {
 final long seed = System.currentTimeMillis();
 System.out.printf("seed = %d.\n", seed);
 
 warmup(seed);
 benchmark(seed);
 }
 
 /**
 * Warms up the benchmark.
 * 
 * @param seed the random number generator seed. 
 */
 private static void warmup(final long seed) {
 runBenchmark(
 new DialsHeap<>(),
 seed, 
 false);
 
 runBenchmark(
 new CachedDialsHeap<>(), 
 seed, 
 false);
 }
 
 /**
 * Benchmarks the heaps.
 * 
 * @param seed the random number generator seed.
 */
 private static void benchmark(final long seed) {
 final Integer[] output1 = 
 runBenchmark(
 new DialsHeap<>(), 
 seed, 
 true);
 
 System.out.println();
 
 final Integer[] output2 = 
 runBenchmark(
 new CachedDialsHeap<>(), 
 seed, 
 true);
 
 System.out.println();
 System.out.printf(
 "Heaps agree: %b.\n", 
 Arrays.equals(output1, 
 output2));
 }
 
 /**
 * Implements the benchmark/warmup runner.
 * 
 * @param heap the heap to benchmark.
 * @param seed the random number generator seed.
 * @param print the flag specifying whether to print the intermediate 
 * results.
 * 
 * @return the processed data.
 */
 private static Integer[] runBenchmark(
 final IntegerMinimumPriorityQueue<Integer> heap,
 final long seed,
 final boolean print) {
 
 final Random random = new Random(seed);
 
 final Integer[] array =
 getRandomIntegerArray(
 random, 
 ARRAY_LENGTH, 
 UPPER_BOUND);
 
 long totalDuration = 0L;
 
 long start = System.currentTimeMillis();
 
 for (final Integer i : array) {
 heap.insert(i, i);
 }
 
 long end = System.currentTimeMillis();
 
 totalDuration += end - start;
 
 if (print) {
 System.out.printf("insert() in %d milliseconds.\n", end - start);
 }
 
 final int[] priorities = 
 getRandomIntArray(
 random, 
 ARRAY_LENGTH, 
 UPPER_BOUND);
 
 start = System.currentTimeMillis();
 
 for (int i = 0; i < heap.size(); i++) {
 heap.updatePriority(array[i], priorities[i]);
 }
 
 end = System.currentTimeMillis();
 
 totalDuration += end - start;
 
 if (print) {
 System.out.printf(
 "updatePriority() in %d milliseconds.\n", 
 end - start);
 }
 
 final Integer[] output = new Integer[heap.size()];
 int index = 0;
 
 start = System.currentTimeMillis();
 
 while (heap.size() != 0) {
 output[index++] = heap.extractMinimum();
 }
 
 end = System.currentTimeMillis();
 
 totalDuration += end - start;
 
 if (print) {
 System.out.printf(
 "extractMinimum() in %d milliseconds.\n", 
 end - start);
 
 System.out.printf(
 "Total duration of %s: %d milliseconds.\n", 
 heap.getClass().getSimpleName(), 
 totalDuration);
 }
 
 return output;
 }
 
 /**
 * Returns the random array of {@code Integer}s. Each integer is drawn from 
 * the range {@code [0, upperBound - 1]}.
 * 
 * @param random the random number generator.
 * @param length the length of the generated array.
 * @param upperBound the upper bound of the integer values.
 * 
 * @return the random integer array.
 */
 private static Integer[] 
 getRandomIntegerArray(
 final Random random,
 final int length, 
 final int upperBound) {
 
 final Integer[] array = new Integer[length];
 
 for (int i = 0; i < length; i++) {
 array[i] = random.nextInt(upperBound);
 }
 
 return array;
 }
 
 /**
 * Returns the random array of {@code int}s. Each integer is drawn from 
 * the range {@code [0, upperBound - 1]}.
 * 
 * @param random the random number generator.
 * @param length the length of the generated array.
 * @param upperBound the upper bound of the integer values.
 * 
 * @return the random integer array.
 */
 private static int[] 
 getRandomIntArray(
 final Random random,
 final int length, 
 final int upperBound) {
 
 final int[] array = new int[length];
 
 for (int i = 0; i < length; i++) {
 array[i] = random.nextInt(upperBound);
 }
 
 return array;
 }
}

Unit testing

com.github.coderodde.util.AbstractDialsHeapTest.java:

package com.github.coderodde.util;
import java.util.Iterator;
import java.util.NoSuchElementException;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
import org.junit.Before;
import org.junit.Test;
public abstract class AbstractDialsHeapTest {
 
 protected IntegerMinimumPriorityQueue<String> heap; 
 private Iterator<String> iterator;
 
 public AbstractDialsHeapTest(
 final IntegerMinimumPriorityQueue<String> heap) {
 this.heap = heap;
 }
 
 @Before 
 public void before() {
 heap = new DialsHeap<String>();
 iterator = null;
 }
 
 @Test
 public void testIterator() {
 iterator = heap.iterator();
 
 assertFalse(iterator.hasNext());
 
 try {
 iterator.next();
 fail("Should throw on empty iterator.");
 } catch (final NoSuchElementException ex) {
 
 }
 
 heap.insert("A1", 1);
 heap.insert("A2", 1);
 heap.insert("A3", 1);
 
 heap.insert("C1", 3);
 
 heap.insert("B1", 2);
 heap.insert("B2", 2);
 
 iterator = heap.iterator();
 
 assertEquals("A3", iterator.next());
 assertEquals("A2", iterator.next());
 assertEquals("A1", iterator.next());
 assertEquals("B2", iterator.next());
 assertEquals("B1", iterator.next());
 assertEquals("C1", iterator.next());
 
 assertFalse(iterator.hasNext());
 }
 
 @Test
 public void testInsert() {
 heap.insert("A", 1);
 heap.insert("C", 3);
 heap.insert("B", 2);
 
 assertEquals("A", heap.minimumNode());
 assertEquals(1, heap.minimumPriority());
 assertEquals("A", heap.extractMinimum());
 
 assertEquals("B", heap.minimumNode());
 assertEquals(2, heap.minimumPriority());
 assertEquals("B", heap.extractMinimum());
 
 assertEquals("C", heap.minimumNode());
 assertEquals(3, heap.minimumPriority());
 assertEquals("C", heap.extractMinimum());
 
 assertEquals(0, heap.size());
 }
 @Test
 public void testUpdatePriority() {
 heap.insert("B", 2);
 heap.insert("A", 3);
 heap.insert("C", 1);
 
 heap.updatePriority("A", 1);
 heap.updatePriority("B", 2);
 heap.updatePriority("C", 3);
 
 assertEquals("A", heap.extractMinimum());
 assertEquals("B", heap.extractMinimum());
 assertEquals("C", heap.extractMinimum());
 }
 @Test
 public void testMinimumPriority() {
 heap.insert("A", 1);
 heap.insert("B", 2);
 heap.insert("C", 3);
 
 assertEquals(1, heap.minimumPriority());
 assertEquals("A", heap.extractMinimum());
 
 assertEquals(2, heap.minimumPriority());
 assertEquals("B", heap.extractMinimum());
 
 assertEquals(3, heap.minimumPriority());
 assertEquals("C", heap.extractMinimum());
 }
 @Test
 public void testMinimumNode() {
 heap.insert("A", 1);
 heap.insert("B", 2);
 heap.insert("C", 3);
 
 assertEquals("A", heap.minimumNode());
 assertEquals("A", heap.extractMinimum());
 
 assertEquals("B", heap.minimumNode());
 assertEquals("B", heap.extractMinimum());
 
 assertEquals("C", heap.minimumNode());
 assertEquals("C", heap.extractMinimum());
 }
 @Test
 public void testGetPriority() {
 heap.insert("X", 10);
 
 assertEquals(10, heap.getPriority("X"));
 
 heap.updatePriority("X", 9);
 
 assertEquals(9, heap.getPriority("X"));
 
 heap.updatePriority("X", 26);
 
 assertEquals(26, heap.getPriority("X"));
 }
 @Test
 public void testExtractMinimum() {
 for (int i = 10; i >= 0; i--) {
 heap.insert(Integer.toString(i), i);
 }
 
 for (int i = 0; i <= 10; i++) {
 assertEquals(Integer.toString(i), heap.extractMinimum());
 }
 }
 
 @Test
 public void testRemove() {
 for (int i = 10; i >= 0; i--) {
 heap.insert(Integer.toString(i), i);
 }
 
 // Remove odd numbers:
 for (int i = 1; i <= 10; i += 2) {
 heap.remove(Integer.toString(i));
 }
 
 // Make sure all the even numbers are still in the heap:
 for (int i = 0; i <= 10; i += 2) {
 heap.containsDatum(Integer.toString(i));
 }
 }
 @Test
 public void testClone() {
 for (int i = 2; i < 10; i++) {
 heap.insert(Integer.toString(i), i);
 }
 
 final DialsHeap<String> copy = (DialsHeap<String>) heap.clone();
 
 assertEquals(heap.size(), copy.size());
 
 for (int i = 2; i < 10; i++) {
 assertTrue(copy.containsDatum(Integer.toString(i)));
 }
 }
 @Test
 public void testSize() {
 for (int i = 0; i < 10; i++) {
 assertEquals(i, heap.size());
 heap.insert(Integer.toString(i), i);
 assertEquals(i + 1, heap.size());
 }
 }
 
 @Test
 public void testCanExpand() {
 heap.insert("A", 1000);
 }
}

com.github.coderodde.util.DialsHeapTest.java:

package com.github.coderodde.util;
public final class DialsHeapTest extends AbstractDialsHeapTest {
 
 public DialsHeapTest() {
 super(new DialsHeap<>());
 }
}

com.github.coderodde.util.CachedDialsHeapTest.java:

package com.github.coderodde.util;
public final class CachedDialsHeapTest extends AbstractDialsHeapTest {
 
 public CachedDialsHeapTest() {
 super(new CachedDialsHeap<>());
 }
}

Typical benchmark output

The typical benchmark output is:

seed = 1715856367754.
insert() in 126 milliseconds.
updatePriority() in 22 milliseconds.
extractMinimum() in 4424 milliseconds.
Total duration of DialsHeap: 4572 milliseconds.
insert() in 195 milliseconds.
updatePriority() in 48 milliseconds.
extractMinimum() in 5620 milliseconds.
Total duration of CachedDialsHeap: 5863 milliseconds.
Heaps agree: true.

Critique request

As always, I would like to receive any commentary on my work.

asked May 11, 2024 at 4:13
\$\endgroup\$

0

Know someone who can answer? Share a link to this question via email, Twitter, or Facebook.

Your Answer

Draft saved
Draft discarded

Sign up or log in

Sign up using Google
Sign up using Email and Password

Post as a guest

Required, but never shown

Post as a guest

Required, but never shown

By clicking "Post Your Answer", you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.