Skip to main content
added 2 characters in body
Source Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205

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));
        }
    }
}
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));
        }
    }
}

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));
        }
    }
}
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));
        }
    }
}
Fixed a couple of bugs before receiving any commentary.
Source Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205
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 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 < 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 == 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<>();
    
    /**
     * Caches the number of satellite datums in this heap.
     */
    private int size = 0;
    
    /**
     * 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> newTreeHeapNode =
                new DialsHeapNode<>(datum, priority);
        
        nodeMap.put(datum, newTreeHeapNode);
        linkImpl(newTreeHeapNode, priority);
        size++;
    }
    
    /**
     * {@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 (size == 0) {
            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 (size == 0) {
            return null;
        }
        
        final DialsHeapNode<D> treeNode = accessMinimumPriorityNode();
        
        unlinkImpl(treeNode);
        nodeMap.remove(treeNode.datum);
        size--;
        return treeNode.datum;
    }
    
    /**
     * {@inheritDoc} 
     */
    @Override
    public void remove(final D datum) {
        unlinkImpl(nodeMap.get(datum));
        size--;
    }
    
    /**
     * {@inheritDoc} 
     */
    @Override
    public void clear() {
        size = 0;
        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 size;
    }
    
    /**
     * 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;
            node.prev = null;
            
            if (node.next != null) {
                node.next.prev = node.prev;
                node.next = 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));
        }
    }
}

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));
        }
    }
}
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 < size;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 number of satellite datums in this heap.
     */
    private int size = 0;
    
    /**
     * 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> newTreeHeapNodenode = 
                new CachedDialsHeapNode<>(
                        datum, 
                        priority);
        
        nodeMap.put(datum, newTreeHeapNodenode);
        linkImpl(newTreeHeapNodenode, priority);
        size++;
    }
    
    /**
     * {@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 (size == 0nodeMap.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 (size == 0nodeMap.isEmpty()) {
            return null;
        }
        
        final CachedDialsHeapNode<D> treeNodenode = accessMinimumPriorityNode();
        
        unlinkImpl(treeNodenode);
        nodeMap.remove(treeNodenode.datum);
        size--;
        
        if (table[minimumPriority] == null) {
            updateMinimumPriority();
        }
        
        return treeNodenode.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();
        }
        
        size--;
    }
    
    /**
     * {@inheritDoc} 
     */
    @Override
    public void clear() {
        minimumPriority = Integer.MAX_VALUE;
        size = 0;
        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 size;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;
            node.prev = null;
            
            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;
    }
}
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 {
    
    private/**
 static final int UPPER_BOUND =* 1_000_000;The upper bound value for the randomly generated integers.
     */
    private static final int ARRAY_LENGTHUPPER_BOUND = 10_000_000;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;
    }
}
seed = 17154006725271715856367754.
insert() in 1508126 milliseconds.
updatePriority() in 23622 milliseconds.
extractMinimum() in 394424 milliseconds.
Total duration of DialsHeap: 17834572 milliseconds.

insert() in 1804195 milliseconds.
updatePriority() in 19648 milliseconds.
extractMinimum() in 465620 milliseconds.
Total duration of CachedDialsHeap: 20465863 milliseconds.

Heaps agree: true.
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 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 < 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 == 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<>();
    
    /**
     * Caches the number of satellite datums in this heap.
     */
    private int size = 0;
    
    /**
     * 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> newTreeHeapNode =
                new DialsHeapNode<>(datum, priority);
        
        nodeMap.put(datum, newTreeHeapNode);
        linkImpl(newTreeHeapNode, priority);
        size++;
    }
    
    /**
     * {@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 (size == 0) {
            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 (size == 0) {
            return null;
        }
        
        final DialsHeapNode<D> treeNode = accessMinimumPriorityNode();
        
        unlinkImpl(treeNode);
        nodeMap.remove(treeNode.datum);
        size--;
        return treeNode.datum;
    }
    
    /**
     * {@inheritDoc} 
     */
    @Override
    public void remove(final D datum) {
        unlinkImpl(nodeMap.get(datum));
        size--;
    }
    
    /**
     * {@inheritDoc} 
     */
    @Override
    public void clear() {
        size = 0;
        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 size;
    }
    
    /**
     * 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;
            node.prev = null;
            
            if (node.next != null) {
                node.next.prev = node.prev;
                node.next = 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));
        }
    }
}
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 < 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 == 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 number of satellite datums in this heap.
     */
    private int size = 0;
    
    /**
     * 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> newTreeHeapNode =
                new CachedDialsHeapNode<>(datum, priority);
        
        nodeMap.put(datum, newTreeHeapNode);
        linkImpl(newTreeHeapNode, priority);
        size++;
    }
    
    /**
     * {@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 (size == 0) {
            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 (size == 0) {
            return null;
        }
        
        final CachedDialsHeapNode<D> treeNode = accessMinimumPriorityNode();
        
        unlinkImpl(treeNode);
        nodeMap.remove(treeNode.datum);
        size--;
        
        if (table[minimumPriority] == null) {
            updateMinimumPriority();
        }
        
        return treeNode.datum;
    }
    
    /**
     * {@inheritDoc} 
     */
    @Override
    public void remove(final D datum) {
        final CachedDialsHeapNode<D> node = nodeMap.get(datum);
        unlinkImpl(node);
        
        if (table[node.priority] == null) {
            updateMinimumPriority();
        }
        
        size--;
    }
    
    /**
     * {@inheritDoc} 
     */
    @Override
    public void clear() {
        minimumPriority = Integer.MAX_VALUE;
        size = 0;
        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 size;
    }
    
    /**
     * 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;
            node.prev = null;
            
            if (node.next != null) {
                node.next.prev = node.prev;
                node.next = 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;
    }
}
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;

public final class Benchmark {
    
    private static final int UPPER_BOUND = 1_000_000;
    private static final int ARRAY_LENGTH = 10_000_000;
    
    public static void main(String[] args) {
        final long seed = System.currentTimeMillis();
        System.out.printf("seed = %d.\n", seed);
        
        warmup(seed);
        benchmark(seed);
    }
    
    private static void warmup(final long seed) {
        runBenchmark(
                new DialsHeap<>(),
                seed, 
                false);
        
        runBenchmark(
                new CachedDialsHeap<>(), 
                seed, 
                false);
    }
    
    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));
    }
    
    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;
    }
    
    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;
    }
    
    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;
    }
}
seed = 1715400672527.
insert() in 1508 milliseconds.
updatePriority() in 236 milliseconds.
extractMinimum() in 39 milliseconds.
Total duration of DialsHeap: 1783 milliseconds.

insert() in 1804 milliseconds.
updatePriority() in 196 milliseconds.
extractMinimum() in 46 milliseconds.
Total duration of CachedDialsHeap: 2046 milliseconds.

Heaps agree: true.

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));
        }
    }
}
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;
    }
}
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;
    }
}
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.
edited tags
Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205
clarify jargon with a wikipedia link
Source Link
J_H
  • 43.3k
  • 3
  • 38
  • 158
Loading
Source Link
coderodde
  • 32.3k
  • 15
  • 79
  • 205
Loading