├── .gitignore ├── index.d.ts ├── index.js ├── README.md ├── .npmignore ├── .travis.yml ├── .eslintrc ├── src ├── deque.d.ts └── deque.js ├── Gruntfile.js ├── CHANGELOG.md ├── LICENSE ├── package.json └── test └── deque.test.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .DS_Store 4 | -------------------------------------------------------------------------------- /index.d.ts: -------------------------------------------------------------------------------- 1 | import { Deque } from './src/deque'; 2 | 3 | export { Deque } 4 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const { Deque } = require('./src/deque'); 2 | 3 | exports.Deque = Deque; 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # @datastructures-js/deque 2 | 3 | ## Docs 4 | https://datastructures-js.info/docs/deque 5 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | .git* 2 | .travis.yml 3 | .gitignore 4 | .eslintrc 5 | Gruntfile.js 6 | coverage/ 7 | node_modules/ 8 | test/ 9 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - "8" 4 | - "9" 5 | - "10" 6 | - "11" 7 | - "12" 8 | - "14" 9 | install: 10 | - npm install -g grunt-cli 11 | - npm install 12 | script: 13 | - grunt build 14 | -------------------------------------------------------------------------------- /.eslintrc: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "max-len": ["error", { "code": 80, "ignoreComments": true }], 4 | "comma-dangle": ["error", { 5 | "functions": "ignore" 6 | }], 7 | "no-underscore-dangle": [ 8 | "error", 9 | { "allowAfterThis": true } 10 | ] 11 | }, 12 | "env": { 13 | "mocha": true, 14 | "node": true 15 | }, 16 | "extends": ["airbnb-base"] 17 | } 18 | -------------------------------------------------------------------------------- /src/deque.d.ts: -------------------------------------------------------------------------------- 1 | export class Deque { 2 | constructor(elements?: T[]); 3 | pushFront(element: T): Deque; 4 | pushBack(element: T): Deque; 5 | popFront(): T | null; 6 | popBack(): T | null; 7 | front(): T | null; 8 | back(): T | null; 9 | toArray(): T[]; 10 | isEmpty(): boolean; 11 | size(): number; 12 | clear(): void; 13 | clone(): Deque; 14 | static fromArray(elements: T[]): Deque; 15 | } 16 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | module.exports = (grunt) => { 2 | grunt.initConfig({ 3 | eslint: { 4 | src: ['src/*.js', 'test/*.test.js'] 5 | }, 6 | mochaTest: { 7 | files: ['test/*.test.js'] 8 | }, 9 | mocha_istanbul: { 10 | coverage: { 11 | src: 'test', 12 | options: { 13 | mask: '*.test.js' 14 | } 15 | } 16 | } 17 | }); 18 | 19 | grunt.loadNpmTasks('grunt-eslint'); 20 | grunt.loadNpmTasks('grunt-mocha-test'); 21 | grunt.loadNpmTasks('grunt-mocha-istanbul'); 22 | 23 | grunt.registerTask('lint', ['eslint']); 24 | grunt.registerTask('test', ['mochaTest']); 25 | grunt.registerTask('coverage', ['mocha_istanbul']); 26 | grunt.registerTask('build', ['lint', 'coverage']); 27 | }; 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | All notable changes to this project will be documented in this file. 3 | 4 | The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), 5 | and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). 6 | 7 | ## [Unreleased] 8 | ## [1.0.8] - 2025-09-08 9 | ### Fixed 10 | - return null in type definition for pop & back functions. 11 | 12 | ## [1.0.7] - 2025-08-26 13 | ### Fixed 14 | - README 15 | 16 | ## [1.0.6] - 2025-08-26 17 | ### Fixed 18 | - README 19 | 20 | ## [1.0.5] - 2025-02-14 21 | ### Fixed 22 | - type in .toArray() 23 | 24 | ## [1.0.4] - 2022-08-15 25 | ### Fixed 26 | - add types to package.json 27 | 28 | ## [1.0.3] - 2022-05-30 29 | ### Fixed 30 | - README 31 | 32 | ## [1.0.2] - 2022-05-15 33 | ### Fixed 34 | - README 35 | 36 | ## [1.0.1] - 2022-01-27 37 | ### Fixed 38 | - readme typos 39 | 40 | ## [1.0.0] - 2022-01-17 41 | ### Added 42 | - v1 43 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2022 Eyas Ranjous 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@datastructures-js/deque", 3 | "version": "1.0.8", 4 | "description": "A performant double-ended queue (deque) implementation in javascript.", 5 | "main": "index.js", 6 | "types": "index.d.ts", 7 | "directories": { 8 | "test": "test" 9 | }, 10 | "scripts": { 11 | "test": "grunt test" 12 | }, 13 | "repository": { 14 | "type": "git", 15 | "url": "git+https://github.com/datastructures-js/deque.git" 16 | }, 17 | "keywords": [ 18 | "double ended queue", 19 | "double-ended queue", 20 | "deque", 21 | "deque es6", 22 | "deque js" 23 | ], 24 | "author": "Eyas Ranjous ", 25 | "license": "MIT", 26 | "bugs": { 27 | "url": "https://github.com/datastructures-js/deque/issues" 28 | }, 29 | "homepage": "https://github.com/datastructures-js/deque#readme", 30 | "devDependencies": { 31 | "chai": "^4.2.0", 32 | "eslint": "^6.8.0", 33 | "eslint-config-airbnb-base": "^14.0.0", 34 | "eslint-plugin-import": "^2.19.1", 35 | "grunt": "^1.0.4", 36 | "grunt-eslint": "^22.0.0", 37 | "grunt-mocha-istanbul": "^5.0.2", 38 | "grunt-mocha-test": "^0.13.3", 39 | "istanbul": "^0.4.5", 40 | "mocha": "^6.2.2" 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /test/deque.test.js: -------------------------------------------------------------------------------- 1 | const { expect } = require('chai'); 2 | const { Deque } = require('../src/deque'); 3 | 4 | describe('Deque unit tests', () => { 5 | let deque; 6 | 7 | describe('constructor', () => { 8 | it('creates an empty deque', () => { 9 | deque = new Deque(); 10 | }); 11 | }); 12 | 13 | describe('fromArray', () => { 14 | it('creates a deque from an existing array', () => { 15 | const dq = Deque.fromArray([1, 2, 3]); 16 | expect(dq.front()).to.equal(1); 17 | expect(dq.back()).to.equal(3); 18 | expect(dq.size()).to.equal(3); 19 | }); 20 | }); 21 | 22 | describe('pushFront', () => { 23 | it('add elements at the front', () => { 24 | deque.pushFront(3); 25 | deque.pushFront(2); 26 | deque.pushFront(1); 27 | expect(deque.size()).to.equal(3); 28 | }); 29 | }); 30 | 31 | describe('pushBack', () => { 32 | it('add elements at the back', () => { 33 | deque.pushBack(4); 34 | deque.pushBack(5); 35 | deque.pushBack(6); 36 | expect(deque.size()).to.equal(6); 37 | }); 38 | }); 39 | 40 | describe('popFront', () => { 41 | it('remove elements from front', () => { 42 | expect(deque.popFront()).to.equal(1); 43 | }); 44 | }); 45 | 46 | describe('popBack', () => { 47 | it('remove elements from back', () => { 48 | expect(deque.popBack()).to.equal(6); 49 | }); 50 | }); 51 | 52 | describe('size', () => { 53 | it('get number of elements in the deque', () => { 54 | expect(deque.size()).to.equal(4); 55 | }); 56 | }); 57 | 58 | describe('front', () => { 59 | it('get front element', () => { 60 | expect(deque.front()).to.equal(2); 61 | }); 62 | }); 63 | 64 | describe('back', () => { 65 | it('should peek the back element', () => { 66 | expect(deque.back()).to.equal(5); 67 | }); 68 | }); 69 | 70 | describe('isEmpty', () => { 71 | it('should not be empty', () => { 72 | expect(deque.isEmpty()).to.equal(false); 73 | }); 74 | }); 75 | 76 | describe('clone', () => { 77 | it('clone the deque', () => { 78 | const clone = deque.clone(); 79 | clone.popFront(); 80 | expect(clone.front()).to.equal(3); 81 | expect(clone.size()).to.equal(3); 82 | expect(deque.front()).to.equal(2); 83 | expect(deque.size()).to.equal(4); 84 | }); 85 | }); 86 | 87 | describe('toArray', () => { 88 | it('should convert the deque into an array', () => { 89 | expect(deque.toArray()).to.deep.equal([2, 3, 4, 5]); 90 | }); 91 | 92 | it('should convert a deque with single array element', () => { 93 | const dq = new Deque([1]); 94 | expect(dq.toArray()).to.deep.equal([1]); 95 | }); 96 | }); 97 | 98 | describe('popFront/popBack', () => { 99 | it('should dequeue all elements', () => { 100 | expect(deque.popFront()).to.be.equal(2); 101 | expect(deque.popBack()).to.be.equal(5); 102 | expect(deque.popFront()).to.be.equal(3); 103 | expect(deque.popBack()).to.be.equal(4); 104 | expect(deque.popFront()).to.be.equal(null); 105 | }); 106 | }); 107 | 108 | describe('.clear()', () => { 109 | it('should clear the deque', () => { 110 | deque.pushFront(3); 111 | deque.pushFront(2); 112 | deque.pushFront(1); 113 | deque.pushBack(4); 114 | deque.pushBack(5); 115 | deque.pushBack(6); 116 | expect(deque.size()).to.equal(6); 117 | expect(deque.toArray()).to.deep.equal([1, 2, 3, 4, 5, 6]); 118 | deque.clear(); 119 | expect(deque.popFront()).to.be.equal(null); 120 | expect(deque.popBack()).to.be.equal(null); 121 | expect(deque.front()).to.be.equal(null); 122 | expect(deque.back()).to.be.equal(null); 123 | expect(deque.size()).to.be.equal(0); 124 | expect(deque.isEmpty()).to.be.equal(true); 125 | }); 126 | }); 127 | }); 128 | -------------------------------------------------------------------------------- /src/deque.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license MIT 3 | * @copyright 2022 Eyas Ranjous 4 | * 5 | * @class 6 | * double-ended queue 7 | */ 8 | class Deque { 9 | /** 10 | * Creates a deque 11 | * @param {array} [elements] 12 | */ 13 | constructor(elements) { 14 | this._backElements = Array.isArray(elements) ? elements : []; 15 | this._frontElements = []; 16 | this._backOffset = 0; 17 | this._frontOffset = 0; 18 | } 19 | 20 | /** 21 | * Adds an element at the front of the queue 22 | * @public 23 | * @param {number|string|object} element 24 | */ 25 | pushFront(element) { 26 | this._frontElements.push(element); 27 | return this; 28 | } 29 | 30 | /** 31 | * Adds an element at the back of the queue 32 | * @public 33 | * @param {number|string|object} element 34 | */ 35 | pushBack(element) { 36 | this._backElements.push(element); 37 | return this; 38 | } 39 | 40 | /** 41 | * Dequeues the front element in the queue 42 | * @public 43 | * @returns {number|string|object} 44 | */ 45 | popFront() { 46 | if (this.size() === 0) { 47 | return null; 48 | } 49 | 50 | if (this._frontElements.length > 0) { 51 | const front = this._frontElements.pop(); 52 | if (this._frontOffset >= this._frontElements.length) { 53 | this._frontElements = this._frontElements.slice(this._frontOffset); 54 | this._frontOffset = 0; 55 | } 56 | return front; 57 | } 58 | 59 | const front = this.front(); 60 | this._backOffset += 1; 61 | 62 | if (this._backOffset * 2 < this._backElements.length) { 63 | return front; 64 | } 65 | 66 | this._backElements = this._backElements.slice(this._backOffset); 67 | this._backOffset = 0; 68 | return front; 69 | } 70 | 71 | /** 72 | * Dequeues the back element of the queue 73 | * @public 74 | * @returns {number|string|object} 75 | */ 76 | popBack() { 77 | if (this.size() === 0) { 78 | return null; 79 | } 80 | 81 | if (this._backElements.length > 0) { 82 | const back = this._backElements.pop(); 83 | if (this._backOffset >= this._backElements.length) { 84 | this._backElements = this._backElements.slice(this._backOffset); 85 | this._backOffset = 0; 86 | } 87 | return back; 88 | } 89 | 90 | const back = this.back(); 91 | this._frontOffset += 1; 92 | if (this._frontOffset * 2 < this._frontElements.length) { 93 | return back; 94 | } 95 | 96 | this._frontElements = this._frontElements.slice(this._frontOffset); 97 | this._frontOffset = 0; 98 | return back; 99 | } 100 | 101 | /** 102 | * Returns the front element of the queue 103 | * @public 104 | * @returns {number|string|object} 105 | */ 106 | front() { 107 | if (this.size() === 0) { 108 | return null; 109 | } 110 | 111 | if (this._frontElements.length > 0) { 112 | return this._frontElements[this._frontElements.length - 1]; 113 | } 114 | 115 | return this._backElements[this._backOffset]; 116 | } 117 | 118 | /** 119 | * Returns the back element of the queue 120 | * @public 121 | * @returns {number|string|object} 122 | */ 123 | back() { 124 | if (this.size() === 0) { 125 | return null; 126 | } 127 | 128 | if (this._backElements.length > 0) { 129 | return this._backElements[this._backElements.length - 1]; 130 | } 131 | 132 | return this._frontElements[this._frontOffset]; 133 | } 134 | 135 | /** 136 | * Returns the number of elements in the deque 137 | * @public 138 | * @returns {number} 139 | */ 140 | size() { 141 | const frontSize = this._frontElements.length - this._frontOffset; 142 | const backSize = this._backElements.length - this._backOffset; 143 | return frontSize + backSize; 144 | } 145 | 146 | /** 147 | * Checks if the queue is empty 148 | * @public 149 | * @returns {boolean} 150 | */ 151 | isEmpty() { 152 | return this.size() === 0; 153 | } 154 | 155 | /** 156 | * Returns the remaining elements in the queue as an array 157 | * @public 158 | * @returns {array} 159 | */ 160 | toArray() { 161 | const backElements = this._backElements.slice(this._backOffset); 162 | const frontElements = this._frontElements.slice(this._frontOffset); 163 | return frontElements.reverse().concat(backElements); 164 | } 165 | 166 | /** 167 | * Clears the queue 168 | * @public 169 | */ 170 | clear() { 171 | this._backElements = []; 172 | this._frontElements = []; 173 | this._backOffset = 0; 174 | this._frontOffset = 0; 175 | } 176 | 177 | /** 178 | * Creates a shallow copy of the queue 179 | * @public 180 | * @return {Deque} 181 | */ 182 | clone() { 183 | return new Deque(this.toArray()); 184 | } 185 | 186 | /** 187 | * Creates a deque from an existing array 188 | * @public 189 | * @static 190 | * @param {array} elements 191 | * @return {Deque} 192 | */ 193 | static fromArray(elements) { 194 | return new Deque(elements); 195 | } 196 | } 197 | 198 | exports.Deque = Deque; 199 | --------------------------------------------------------------------------------