diff --git a/CHANGELOG.md b/CHANGELOG.md index 8036679..9d94c79 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,14 +5,25 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ## [Unreleased] -## [1.0.2] - 2021-JUN-24 + +## [2.0.0] - 2022-feb-09 +### Changed +- deleteByKey to delete item by its key +- deleteByValue to delete items by a matching criteria. +- internal cleanup. + +## [1.0.2] - 2021-jun-24 +### Fixed +- index.d.ts + +## [1.0.2] - 2021-jun-24 ### Fixed - index.d.ts -## [1.0.1] - 2021-MAY-12 +## [1.0.1] - 2021-may-12 ### Fixed - README -## [1.0.0] - 2021-MAY-12 +## [1.0.0] - 2021-may-12 ### Added - v1 diff --git a/README.md b/README.md index 64d4ba4..4a2b17a 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,8 @@ An implementation of the round robin as a data structure. Two strategies are imp * [add(item)](#additem) * [count()](#count) * [next()](#next) - * [completedRounds()](#completedrounds) - * [delete(key)](#deletekey) + * [deleteByKey(key)](#deletebykeykey) + * [deleteByValue(cb)](#deletebyvaluecb) * [reset()](#reset) * [clear()](#clear) * [Build](#build) @@ -181,61 +181,73 @@ console.log(randomTable.next()); // { key: 3, value: 25 } console.log(randomTable.next()); // { key: 1, value: 10 } ``` -### completedRounds() -returns the number of completed rounds. +### deleteByKey(key) +deletes an item by its key from the table. - +
return
numberboolean
```js -console.log(sequentialTable.completedRounds()); // 1 +sequentialTable.deleteByKey(0); +sequentialTable.deleteByKey(2); +console.log(sequentialTable.next()); // { key: 1, value: 'T2' } +console.log(sequentialTable.next()); // { key: 3, value: 'T4' } +console.log(sequentialTable.next()); // { key: 1, value: 'T2' } -console.log(randomTable.completedRounds()); // 1 +randomTable.deleteByKey(0); +randomTable.deleteByKey(2); +console.log(randomTable.next()); // { key: 3, value: 25 } +console.log(randomTable.next()); // { key: 1, value: 10 } +console.log(randomTable.next()); // { key: 3, value: 25 } ``` -### delete(key) -deletes an item from the table by its key. +### deleteByValue(cb) +deletes items with values that match a criteria from the table and returns the number of deleted items. + - + +
params return
booleancb: (value: T) => booleannumber
```js -sequentialTable.delete(0); -sequentialTable.delete(2); -console.log(sequentialTable.next()); // { key: 1, value: 'T2' } -console.log(sequentialTable.next()); // { key: 3, value: 'T4' } -console.log(sequentialTable.next()); // { key: 1, value: 'T2' } - -randomTable.delete(0); -randomTable.delete(2); -console.log(randomTable.next()); // { key: 3, value: 25 } -console.log(randomTable.next()); // { key: 1, value: 10 } -console.log(randomTable.next()); // { key: 3, value: 25 } +const seqTable = new SequentialRoundRobin([2, 3, 5, 6, 7, 10]); +const ranTable = new RandomRoundRobin<{ id: string }>([ + { id: '123' }, + { id: 'id456' }, + { id: '456' }, + { id: 'id780' } +]); + +const d1 = seqTable.deleteByValue((n) => n % 2 === 1); // 3 +console.log(seqTable.next(), seqTable.next(), seqTable.next()) +// { key: 0, value: 2 } { key: 3, value: 6 } { key: 5, value: 10 } + +const d2 = ranTable.deleteByValue((obj) => obj.id.indexOf('id') === 0); // 2 +console.log(ranTable.next(), ranTable.next()) +// { key: 2, value: { id: '456' } } { key: 0, value: { id: '123' } } ``` ### reset() -resets the table with the intial items and clears completed rounds. +resets the table with the intial values. ```js sequentialTable.reset(); console.log(sequentialTable.count()); // 3 -console.log(sequentialTable.completedRounds()); // 0 randomTable.reset(); console.log(randomTable.count()); // 3 -console.log(randomTable.completedRounds()); // 0 ``` ### clear() @@ -244,11 +256,9 @@ clears all values in the table. ```js sequentialTable.clear(); console.log(sequentialTable.count()); // 0 -console.log(sequentialTable.completedRounds()); // 0 randomTable.clear(); console.log(randomTable.count()); // 0 -console.log(randomTable.completedRounds()); // 0 ``` ## Build diff --git a/package.json b/package.json index ae852c0..10dea4b 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "round-robin-js", - "version": "1.0.2", + "version": "2.0.0", "description": "an implementation of round robin as a data structure", "main": "index.js", "scripts": { @@ -20,7 +20,7 @@ }, "homepage": "https://github.com/eyas-ranjous/round-robin-js#readme", "dependencies": { - "@datastructures-js/linked-list": "^5.0.1" + "@datastructures-js/linked-list": "^5.1.1" }, "devDependencies": { "chai": "^4.2.0", diff --git a/src/RandomRoundRobin.js b/src/RandomRoundRobin.js index 7bd9ac6..ae2e687 100644 --- a/src/RandomRoundRobin.js +++ b/src/RandomRoundRobin.js @@ -12,10 +12,10 @@ const RoundRobin = require('./RoundRobin'); class RandomRoundRobin extends RoundRobin { /** * @constructor - * @param {array} items + * @param {array} values */ - constructor(items) { - super(items); + constructor(values) { + super(values); this._init(); } @@ -24,66 +24,77 @@ class RandomRoundRobin extends RoundRobin { */ _init() { this._items = new Map(); - this._keys = new Set(); this._round = new Set(); - this._initialItems.forEach((item) => this.add(item)); + this._initialValues.forEach((value) => this.add(value)); } /** - * Adds an item and memoizes its key for random selection + * Adds a value to the table * @public - * @return {object} + * @return {any} value */ - add(item) { + add(value) { const key = this._currentkey; - this._items.set(key, { key, value: item }); - this._keys.add(key); + this._items.set(key, { key, value }); this._currentkey++; return this._items.get(key); } /** - * Deletes an item with its key from the table + * Deletes an item by its key from the table * @public * @param {number} key * @return {boolean} */ - delete(key) { - if (!this._keys.has(key)) { + deleteByKey(key) { + if (!this._items.has(key)) { return false; } if (this._currentTurn && this._currentTurn.key === key) { - this._currentTurn = this._selectNextItem(); - if (this._currentTurn === null) { - this._completedRounds += 1; - } + this._currentTurn = this._nextTurn(); } - this._keys.delete(key); this._round.delete(key); - return this._items.delete(key); } + /** + * Deletes items that their values match a criteria + * @public + * @param {function} cb + * @return {number} + */ + deleteByValue(cb) { + let deleted = 0; + this._items.forEach(({ key, value }) => { + if (cb(value)) { + this.deleteByKey(key); + deleted += 1; + } + }); + return deleted; + } + /** * Selects the next item's key from the round * @public * @return {object} */ - _selectNextItem() { + _nextTurn() { if (this._currentTurn === null) { - this._round = new Set(this._keys); + const keys = Array.from(this._items.keys()); + this._round = new Set(keys); } - const keys = Array.from(this._round); - if (keys.length === 0) { + if (this._round.size === 0) { return null; } - const randomKey = keys[Math.floor(Math.random() * keys.length)]; - this._round.delete(randomKey); - return this._items.get(randomKey); + const roundKeys = Array.from(this._round); + const selectedKey = roundKeys[Math.floor(Math.random() * roundKeys.length)]; + this._round.delete(selectedKey); + return this._items.get(selectedKey); } /** @@ -97,15 +108,11 @@ class RandomRoundRobin extends RoundRobin { } if (this._currentTurn === null) { - this._currentTurn = this._selectNextItem(); + this._currentTurn = this._nextTurn(); } const item = this._currentTurn; - this._currentTurn = this._selectNextItem(); - if (this._currentTurn === null) { - this._completedRounds += 1; - } - + this._currentTurn = this._nextTurn(); return item; } diff --git a/src/RoundRobin.d.ts b/src/RoundRobin.d.ts index e50c67a..5a4f67b 100644 --- a/src/RoundRobin.d.ts +++ b/src/RoundRobin.d.ts @@ -4,9 +4,10 @@ export interface RoundRobinItem { } export class RoundRobin { - constructor(items?: T[]); - add(item: T): RoundRobinItem; - delete(key: number): boolean; + constructor(values?: T[]); + add(value: T): RoundRobinItem; + deleteByKey(key: number): boolean; + deleteByValue(cb: (value: T) => boolean): number; next(): RoundRobinItem; count(): number; completedRounds(): number; diff --git a/src/RoundRobin.js b/src/RoundRobin.js index e90b1d2..0e9e493 100644 --- a/src/RoundRobin.js +++ b/src/RoundRobin.js @@ -12,27 +12,18 @@ class RoundRobin { /** * @constructor - * @param {array} items + * @param {array} values */ - constructor(items = []) { - if (items && !Array.isArray(items)) { - throw new Error('items must be an array'); + constructor(values = []) { + if (values && !Array.isArray(values)) { + throw new Error('RoundRobin constructor: values must be an array'); } - this._initialItems = items; + + this._initialValues = values; this._currentkey = 0; - this._completedRounds = 0; this._currentTurn = null; } - /** - * Returns number of completed round of turns - * @public - * @return {number} - */ - completedRounds() { - return this._completedRounds; - } - /** * Clears the table * @public @@ -40,7 +31,6 @@ class RoundRobin { */ clear() { this._currentkey = 0; - this._completedRounds = 0; this._currentTurn = null; return this; } diff --git a/src/SequentialRoundRobin.js b/src/SequentialRoundRobin.js index 898ea4c..6c7220c 100644 --- a/src/SequentialRoundRobin.js +++ b/src/SequentialRoundRobin.js @@ -13,10 +13,10 @@ const RoundRobin = require('./RoundRobin'); class SequentialRoundRobin extends RoundRobin { /** * @constructor - * @param {array} items + * @param {array} values */ - constructor(items) { - super(items); + constructor(values) { + super(values); this._init(); } @@ -26,45 +26,60 @@ class SequentialRoundRobin extends RoundRobin { _init() { this._items = new DoublyLinkedList(); this._itemNodes = new Map(); - this._initialItems.forEach((item) => this.add(item)); + this._initialValues.forEach((value) => this.add(value)); } /** * Adds a new item to the table * @public - * @param {any} item - * @return {object} + * @param {any} value + * @return {any} */ - add(item) { + add(value) { this._itemNodes.set( this._currentkey, - this._items.insertLast({ key: this._currentkey++, value: item }) + this._items.insertLast({ key: this._currentkey++, value }) ); return this._items.tail().getValue(); } /** - * Deletes an item from the table + * Deletes an item by its key from the table * @public * @param {number} key * @return {boolean} */ - delete(key) { + deleteByKey(key) { if (!this._itemNodes.has(key)) { return false; } if (this._currentTurn && this._currentTurn.getValue().key === key) { this._currentTurn = this._currentTurn.getNext(); - if (this._currentTurn === null) { - this._completedRounds += 1; - } } this._items.remove(this._itemNodes.get(key)); return this._itemNodes.delete(key); } + /** + * Deletes items that their values match a criteria + * @public + * @param {function} cb + * @return {number} + */ + deleteByValue(cb) { + const deletedKeys = []; + this._items.forEach((itemNode) => { + const { key, value } = itemNode.getValue(); + if (cb(value)) { + deletedKeys.push(key); + } + }); + deletedKeys.map((key) => this.deleteByKey(key)); + return deletedKeys.length; + } + /** * Selects the next item in the turn round sequentially * @public @@ -81,10 +96,6 @@ class SequentialRoundRobin extends RoundRobin { const item = this._currentTurn.getValue(); this._currentTurn = this._currentTurn.getNext(); - if (this._currentTurn === null) { - this._completedRounds += 1; - } - return item; } diff --git a/test/RandomRoundRobin.test.js b/test/RandomRoundRobin.test.js index acb1617..57f1e13 100644 --- a/test/RandomRoundRobin.test.js +++ b/test/RandomRoundRobin.test.js @@ -4,7 +4,7 @@ const { RandomRoundRobin } = require('../src/RandomRoundRobin'); describe('RandomRoundRobin tests', () => { const round = new RandomRoundRobin(['item 1', 'item 2']); - describe('.add', () => { + describe('add', () => { it('adds items to the round', () => { expect(round.add('item 3')).to.deep.equal({ key: 2, value: 'item 3' }); expect(round.add('item 4')).to.deep.equal({ key: 3, value: 'item 4' }); @@ -12,7 +12,7 @@ describe('RandomRoundRobin tests', () => { }); }); - describe('.next', () => { + describe('next', () => { it('gets the next item in the round', () => { const items = [ round.next(), @@ -25,10 +25,10 @@ describe('RandomRoundRobin tests', () => { }); }); - describe('.delete', () => { - it('removes items from the round', () => { - round.delete(0); - round.delete(2); + describe('deleteByKey', () => { + it('deletes items by key', () => { + round.deleteByKey(0); + round.deleteByKey(2); expect(round.count()).to.equal(2); const items = [ @@ -40,14 +40,27 @@ describe('RandomRoundRobin tests', () => { }); }); - describe('.reset', () => { + describe('deleteByValue', () => { + it('deletes items by value', () => { + const round2 = new RandomRoundRobin(['n1', 'val1', 'n2', 'n3']); + round2.next(); + round2.next(); + + const deletedCount = round2.deleteByValue((val) => val.includes('n')); + expect(deletedCount).to.equal(3); + expect(round2.count()).to.equal(1); + expect(round2.next().value).to.equal('val1'); + }); + }); + + describe('reset', () => { it('reset the round', () => { round.reset(); expect(round.count()).to.equal(2); }); }); - describe('.clear', () => { + describe('clear', () => { it('clears the round', () => { round.clear(); expect(round.count()).to.equal(0); diff --git a/test/SequentialRoundRobin.test.js b/test/SequentialRoundRobin.test.js index 1815990..b5159d6 100644 --- a/test/SequentialRoundRobin.test.js +++ b/test/SequentialRoundRobin.test.js @@ -4,7 +4,7 @@ const { SequentialRoundRobin } = require('../src/SequentialRoundRobin'); describe('SequentialRoundRobin tests', () => { const round = new SequentialRoundRobin(['item 1', 'item 2']); - describe('.add', () => { + describe('add', () => { it('adds items to the round', () => { expect(round.add('item 3')).to.deep.equal({ key: 2, value: 'item 3' }); expect(round.add('item 4')).to.deep.equal({ key: 3, value: 'item 4' }); @@ -12,7 +12,7 @@ describe('SequentialRoundRobin tests', () => { }); }); - describe('.next', () => { + describe('next', () => { it('gets the next item in the round', () => { const next1 = round.next(); expect(next1.key).to.equal(0); @@ -29,7 +29,6 @@ describe('SequentialRoundRobin tests', () => { const next4 = round.next(); expect(next4.key).to.equal(3); expect(next4.value).to.equal('item 4'); - expect(round.completedRounds()).to.equal(1); const next5 = round.next(); expect(next5.key).to.equal(0); @@ -37,10 +36,10 @@ describe('SequentialRoundRobin tests', () => { }); }); - describe('.delete', () => { - it('removes items from the round', () => { - round.delete(1); - round.delete(3); + describe('deleteByKey', () => { + it('delete items by key', () => { + round.deleteByKey(1); + round.deleteByKey(3); expect(round.count()).to.equal(2); const next1 = round.next(); @@ -66,7 +65,21 @@ describe('SequentialRoundRobin tests', () => { }); }); - describe('.reset', () => { + describe('deleteByValue', () => { + it('deletes items by value', () => { + const round2 = new SequentialRoundRobin(['n1', 'val1', 'n2', 'val2']); + round2.next(); + round2.next(); + + const deletedCount = round2.deleteByValue((val) => val.includes('n')); + expect(deletedCount).to.equal(2); + expect(round2.count()).to.equal(2); + expect(round2.next()).to.eql({ key: 3, value: 'val2' }); + expect(round2.next()).to.eql({ key: 1, value: 'val1' }); + }); + }); + + describe('reset', () => { it('reset the round', () => { round.reset(); expect(round.count()).to.equal(2); @@ -81,7 +94,7 @@ describe('SequentialRoundRobin tests', () => { }); }); - describe('.clear', () => { + describe('clear', () => { it('clears the round', () => { round.clear(); expect(round.count()).to.equal(0);