diff --git a/.githooks/pre-commit b/.githooks/pre-commit new file mode 100755 index 0000000..45091c1 --- /dev/null +++ b/.githooks/pre-commit @@ -0,0 +1,7 @@ +#!/bin/sh +# Automatically bump patch version before each commit + +npm version patch --no-git-tag-version > /dev/null + +# stage version files +git add package.json package-lock.json diff --git a/DEVELOPMENT_PLAN.md b/DEVELOPMENT_PLAN.md index 392704b..efacc81 100644 --- a/DEVELOPMENT_PLAN.md +++ b/DEVELOPMENT_PLAN.md @@ -17,7 +17,7 @@ This document outlines possible improvements and future developments for the `TS ## New Data Structures - **Graph**: Implement a basic graph structure with common algorithms such as BFS, DFS, and shortest path algorithms. -- **PriorityQueue**: Provide a priority queue implementation for efficient task scheduling. +- **PriorityQueue**: Provide a priority queue implementation for efficient task scheduling. *(Implemented)* - **Balanced Trees**: Add self-balancing tree variants (e.g., AVL or Red-Black trees) for more efficient search and insert operations. ## Documentation diff --git a/README.md b/README.md index 1bd1051..c930e0c 100644 --- a/README.md +++ b/README.md @@ -244,6 +244,44 @@ queue.size(); // 2 ``` +## PriorityQueue + +#### push(value: T, priority: number) + +Insert an element into the queue with an associated priority. + +#### pop(): T | null + +Removes and returns the element with the highest priority, or `null` if the queue is empty. + +#### peek(): T | null + +Returns the element with the highest priority without removing it, or `null` if the queue is empty. + +#### size(): number + +Returns the number of elements in the queue. + +#### isEmpty(): boolean + +Returns `true` if the queue has no elements, otherwise `false`. + +#### toArray(): T[] + +Returns an array of the values in priority order. + +```typescript + +const pq = new PriorityQueue(); +pq.push(5, 5); +pq.push(1, 1); +pq.push(3, 3); +pq.pop(); // 1 +pq.peek(); // 3 +pq.size(); // 2 + +``` + ## BinaryTree #### getRoot(): BinaryTreeNode @@ -422,3 +460,13 @@ Traverse the tree in breadth-first order and apply the given callback function t If you have an advice, please feel free to contact with me + +## Git Hooks + +This project uses a pre-commit hook stored in `.githooks/pre-commit` to +automatically bump the package patch version each time you commit. Enable the +hooks with the following command: + +```bash +git config core.hooksPath .githooks +``` diff --git a/package-lock.json b/package-lock.json index 918245b..dcd7030 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "datastructure-ts", - "version": "0.1.20", + "version": "0.1.21", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "datastructure-ts", - "version": "0.1.20", + "version": "0.1.21", "license": "MIT", "devDependencies": { "@types/chai": "^4.3.20", diff --git a/package.json b/package.json index cb52491..cbe3296 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "datastructure-ts", - "version": "0.1.20", + "version": "0.1.21", "description": "Collection of data structures(LinkedList, DoubleLinkedList, Stack, Queue, Dictionary and etc...) for TypeScript.", "scripts": { "build": "tsc", diff --git a/src/PriorityQueue/PriorityQueue.ts b/src/PriorityQueue/PriorityQueue.ts new file mode 100644 index 0000000..141fe08 --- /dev/null +++ b/src/PriorityQueue/PriorityQueue.ts @@ -0,0 +1,93 @@ +export interface PriorityQueueItem { + value: T; + priority: number; +} + +export class PriorityQueue { + private heap: PriorityQueueItem[] = []; + + constructor(private compare: (a: number, b: number) => boolean = (a, b) => a < b) {} + + push(value: T, priority: number): void { + this.heap.push({ value, priority }); + this.bubbleUp(this.heap.length - 1); + } + + pop(): T | null { + if (this.isEmpty()) { + return null; + } + const top = this.heap[0].value; + const end = this.heap.pop()!; + if (!this.isEmpty()) { + this.heap[0] = end; + this.bubbleDown(0); + } + return top; + } + + peek(): T | null { + return this.isEmpty() ? null : this.heap[0].value; + } + + size(): number { + return this.heap.length; + } + + isEmpty(): boolean { + return this.heap.length === 0; + } + + toArray(): T[] { + return this.heap.map(item => item.value); + } + + private bubbleUp(index: number): void { + const element = this.heap[index]; + while (index > 0) { + const parentIndex = Math.floor((index - 1) / 2); + const parent = this.heap[parentIndex]; + if (this.compare(element.priority, parent.priority)) { + this.heap[parentIndex] = element; + this.heap[index] = parent; + index = parentIndex; + } else { + break; + } + } + } + + private bubbleDown(index: number): void { + const length = this.heap.length; + const element = this.heap[index]; + while (true) { + const leftChildIdx = 2 * index + 1; + const rightChildIdx = 2 * index + 2; + let swapIdx: number | null = null; + + if (leftChildIdx < length) { + const left = this.heap[leftChildIdx]; + if (this.compare(left.priority, element.priority)) { + swapIdx = leftChildIdx; + } + } + + if (rightChildIdx < length) { + const right = this.heap[rightChildIdx]; + if ( + (swapIdx === null && this.compare(right.priority, element.priority)) || + (swapIdx !== null && this.compare(right.priority, this.heap[swapIdx].priority)) + ) { + swapIdx = rightChildIdx; + } + } + + if (swapIdx === null) { + break; + } + this.heap[index] = this.heap[swapIdx]; + this.heap[swapIdx] = element; + index = swapIdx; + } + } +} diff --git a/src/index.ts b/src/index.ts index fe5bef1..68f85bb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -9,5 +9,6 @@ export * from './Nodes/DoubleLinkedNode'; export * from './Nodes/LinkedNode'; export * from './Stack/Stack'; export * from './Queue/Queue'; +export * from './PriorityQueue/PriorityQueue'; export * from './Nodes/BinaryTreeNode'; export * from './BinaryTree/BinaryTree'; diff --git a/tests/PriorityQueue/PriorityQueue.spec.ts b/tests/PriorityQueue/PriorityQueue.spec.ts new file mode 100644 index 0000000..fc43daa --- /dev/null +++ b/tests/PriorityQueue/PriorityQueue.spec.ts @@ -0,0 +1,34 @@ +import { PriorityQueue } from '../../src/PriorityQueue/PriorityQueue'; +import { expect } from 'chai'; + +describe('PriorityQueue', () => { + let queue: PriorityQueue; + + beforeEach(() => { + queue = new PriorityQueue(); + }); + + it('push/pop works according to priority', () => { + queue.push('low', 5); + queue.push('high', 1); + queue.push('medium', 3); + + expect(queue.pop()).equal('high'); + expect(queue.pop()).equal('medium'); + expect(queue.pop()).equal('low'); + }); + + it('peek returns highest priority', () => { + queue.push('a', 2); + queue.push('b', 1); + + expect(queue.peek()).equal('b'); + expect(queue.size()).equal(2); + }); + + it('handles empty queue', () => { + expect(queue.pop()).equal(null); + expect(queue.peek()).equal(null); + expect(queue.size()).equal(0); + }); +});