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 |
- number |
+ boolean |
```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 |
- boolean |
+ cb: (value: T) => boolean |
+ number |
```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);