├── .github └── workflows │ └── test.yml ├── .gitignore ├── LICENSE ├── README.md ├── index.js ├── package.json └── test.js /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Build Status 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | branches: 9 | - master 10 | jobs: 11 | build: 12 | strategy: 13 | matrix: 14 | node-version: [10.x, 12.x, 14.x] 15 | os: [ubuntu-16.04, macos-latest, windows-latest] 16 | runs-on: ${{ matrix.os }} 17 | steps: 18 | - uses: actions/checkout@v2 19 | - name: Use Node.js ${{ matrix.node-version }} 20 | uses: actions/setup-node@v1 21 | with: 22 | node-version: ${{ matrix.node-version }} 23 | - run: npm install 24 | - run: npm test 25 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | sandbox.js 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2020 Mathias Buus 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 13 | all 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 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # random-array-iterator 2 | 3 | An iterator to iterate an array in random order with controls to requeue or dequeue 4 | elements during the iteration. 5 | 6 | ``` 7 | npm install random-array-iterator 8 | ``` 9 | 10 | ## Usage 11 | 12 | ``` js 13 | const RandomArrayIterator = require('random-array-iterator') 14 | 15 | const ite = new RandomArrayIterator([1, 2, 3, 4, 5]) 16 | 17 | for (const val of ite) { 18 | console.log(val) // random value 19 | 20 | // call requeue if you want to revisit this value is the same iteration 21 | if (someCondition) ite.requeue() 22 | 23 | // call dequeue if you want to remove the value from the iteration and array entirely 24 | if (someOtherCondition) ite.dequeue() 25 | } 26 | ``` 27 | 28 | ## API 29 | 30 | #### `ite = new RandomArrayIterator(array)` 31 | 32 | Make a new iterator. Implements the JavaScript iterator interface. 33 | 34 | #### `ite.requeue()` 35 | 36 | Requeue the current value. Only valid to call during an iteration. 37 | 38 | #### `ite.dequeue()` 39 | 40 | Remove the current value from the array and iteration. Only valid to vall during an iteration. 41 | 42 | #### `ite.restart()` 43 | 44 | Restarts the iterator. 45 | 46 | ## License 47 | 48 | MIT 49 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | module.exports = class RandomArrayIterator { 2 | constructor (values) { 3 | this.values = values 4 | this.start = 0 5 | this.length = this.values.length 6 | } 7 | 8 | next () { 9 | if (this.length === 0) { 10 | if (this.start === 0) return { done: true, value: undefined } 11 | this.length = this.start 12 | this.start = 0 13 | } 14 | 15 | const i = this.start + ((Math.random() * this.length) | 0) 16 | const j = this.start + --this.length 17 | const value = this.values[i] 18 | 19 | this.values[i] = this.values[j] 20 | this.values[j] = value 21 | 22 | return { done: false, value } 23 | } 24 | 25 | dequeue () { 26 | this.values[this.start + this.length] = this.values[this.values.length - 1] 27 | this.values.pop() 28 | } 29 | 30 | requeue () { 31 | const i = this.start + this.length 32 | const value = this.values[i] 33 | this.values[i] = this.values[this.start] 34 | this.values[this.start++] = value 35 | } 36 | 37 | restart () { 38 | this.start = 0 39 | this.length = this.values.length 40 | return this 41 | } 42 | 43 | [Symbol.iterator] () { 44 | return this 45 | } 46 | } 47 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "random-array-iterator", 3 | "version": "1.0.0", 4 | "description": "An iterator to iterate an array in random order with controls to requeue or dequeue elements during the iteration", 5 | "main": "index.js", 6 | "dependencies": {}, 7 | "devDependencies": { 8 | "standard": "^16.0.3", 9 | "tape": "^5.0.1" 10 | }, 11 | "scripts": { 12 | "test": "standard && tape test.js" 13 | }, 14 | "repository": { 15 | "type": "git", 16 | "url": "https://github.com/mafintosh/random-array-iterator.git" 17 | }, 18 | "author": "Mathias Buus (@mafintosh)", 19 | "license": "MIT", 20 | "bugs": { 21 | "url": "https://github.com/mafintosh/random-array-iterator/issues" 22 | }, 23 | "homepage": "https://github.com/mafintosh/random-array-iterator" 24 | } 25 | -------------------------------------------------------------------------------- /test.js: -------------------------------------------------------------------------------- 1 | const tape = require('tape') 2 | const Iterator = require('./') 3 | 4 | tape('basic', function (t) { 5 | let diff = 0 6 | 7 | for (let n = 0; n < 5; n++) { 8 | const values = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] 9 | const old = values.join(',') 10 | 11 | const cnts = {} 12 | for (const i of new Iterator(values)) { 13 | cnts[i] = (cnts[i] || 0) + 1 14 | t.same(cnts[i], 1) 15 | } 16 | 17 | if (values.join(',') !== old) diff++ 18 | t.same(Object.keys(cnts).length, values.length) 19 | } 20 | 21 | t.ok(diff > 1) 22 | t.end() 23 | }) 24 | 25 | tape('requeue', function (t) { 26 | const values = [1, 2, 3, 4, 5] 27 | 28 | let requeues = 2 29 | 30 | const cnts = {} 31 | const ite = new Iterator(values) 32 | for (const i of ite) { 33 | if ((i === 1 || i === 3) && requeues-- > 0) { 34 | ite.requeue() 35 | } 36 | cnts[i] = (cnts[i] || 0) + 1 37 | } 38 | 39 | t.same(cnts, { 40 | 1: 2, 41 | 2: 1, 42 | 3: 2, 43 | 4: 1, 44 | 5: 1 45 | }) 46 | 47 | t.end() 48 | }) 49 | 50 | tape('dequeue', function (t) { 51 | const values = [1, 2, 3, 4, 5] 52 | 53 | const ite = new Iterator(values) 54 | for (const i of ite) { 55 | if (i === 2 || i === 5) ite.dequeue() 56 | } 57 | 58 | t.same(values.sort(), [1, 3, 4]) 59 | t.end() 60 | }) 61 | 62 | tape('dequeue all', function (t) { 63 | const values = [1, 2, 3, 4, 5] 64 | 65 | const trace = [] 66 | const ite = new Iterator(values) 67 | for (const i of ite) { 68 | ite.dequeue() 69 | trace.push(i) 70 | } 71 | 72 | t.same(trace.sort(), [1, 2, 3, 4, 5]) 73 | t.same(values, []) 74 | t.end() 75 | }) 76 | 77 | tape('dequeue and requeue', function (t) { 78 | const values = [1, 2, 3, 4, 5] 79 | 80 | const ite = new Iterator(values) 81 | const trace = [] 82 | let requeue = true 83 | 84 | for (const i of ite) { 85 | if (i === 2 || i === 5) ite.dequeue() 86 | if (requeue && i === 3) { 87 | requeue = false 88 | ite.requeue() 89 | } 90 | trace.push(i) 91 | } 92 | 93 | t.same(values.sort(), [1, 3, 4]) 94 | t.same(trace.sort(), [1, 2, 3, 3, 4, 5]) 95 | t.end() 96 | }) 97 | --------------------------------------------------------------------------------