├── docs ├── .nojekyll ├── docs │ ├── .nojekyll │ ├── assets │ │ ├── .nojekyll │ │ ├── images │ │ │ ├── icons.png │ │ │ ├── icons@2x.png │ │ │ ├── widgets.png │ │ │ └── widgets@2x.png │ │ └── js │ │ │ └── search.js │ ├── classes │ │ └── .nojekyll │ ├── modules │ │ ├── .nojekyll │ │ ├── generated_package_version.html │ │ └── circular_buffer.html │ ├── modules.html │ └── index.html └── index.html ├── src ├── html │ ├── .nojekyll │ └── index.html ├── ts │ ├── types.ts │ ├── generated │ │ └── package_version.ts │ ├── tests │ │ ├── rig.spec.ts │ │ ├── circular_buffer.spec.ts │ │ └── circular_buffer.stoch.ts │ └── circular_buffer.ts └── build_js │ ├── archive_docs_by_version.js │ ├── release.js │ └── write_version_file.js ├── archive_docs_by_version.js ├── dist ├── generated │ └── package_version.d.ts ├── circular_buffer.d.ts ├── circular_buffer.esm.min.js ├── circular_buffer.cjs.min.js ├── circular_buffer.iife.min.js └── circular_buffer.iife.js ├── .eslintignore ├── jest-stoch.config.js ├── .eslintrc.js ├── jest.config.js ├── rollup.config.js ├── LICENSE ├── .github └── workflows │ └── node.js.yml ├── .gitignore ├── .npmignore ├── tsconfig.json ├── package.json ├── README.md └── CHANGELOG.md /docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/html/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /src/ts/types.ts: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/docs/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/docs/assets/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/docs/classes/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /docs/docs/modules/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /archive_docs_by_version.js: -------------------------------------------------------------------------------- 1 | 2 | // The file you're looking for is in the root directory of the branch gh-pages 3 | -------------------------------------------------------------------------------- /src/build_js/archive_docs_by_version.js: -------------------------------------------------------------------------------- 1 | 2 | // The file you're looking for is in the root directory of the branch gh-pages 3 | -------------------------------------------------------------------------------- /docs/docs/assets/images/icons.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StoneCypher/circular_buffer_js/HEAD/docs/docs/assets/images/icons.png -------------------------------------------------------------------------------- /docs/docs/assets/images/icons@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StoneCypher/circular_buffer_js/HEAD/docs/docs/assets/images/icons@2x.png -------------------------------------------------------------------------------- /docs/docs/assets/images/widgets.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StoneCypher/circular_buffer_js/HEAD/docs/docs/assets/images/widgets.png -------------------------------------------------------------------------------- /docs/docs/assets/images/widgets@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/StoneCypher/circular_buffer_js/HEAD/docs/docs/assets/images/widgets@2x.png -------------------------------------------------------------------------------- /dist/generated/package_version.d.ts: -------------------------------------------------------------------------------- 1 | declare const version = "1.10.0", packagename = "circular_buffer_js"; 2 | export { version, packagename }; 3 | -------------------------------------------------------------------------------- /.eslintignore: -------------------------------------------------------------------------------- 1 | 2 | node_modules 3 | dist 4 | build 5 | .github 6 | coverage 7 | docs 8 | src/html 9 | src/ts/generated 10 | rollup.config.js 11 | -------------------------------------------------------------------------------- /src/ts/generated/package_version.ts: -------------------------------------------------------------------------------- 1 | 2 | // Generated by %proj%/src/build_js/write_version_file.js 3 | 4 | const version = '1.10.0', 5 | packagename = 'circular_buffer_js'; 6 | 7 | export { version, packagename }; 8 | -------------------------------------------------------------------------------- /src/build_js/release.js: -------------------------------------------------------------------------------- 1 | 2 | const fs = require('fs'), 3 | { execSync } = require('child_process'), 4 | package = JSON.parse(fs.readFileSync('./package.json')), 5 | version = `v${package.version}`, 6 | command = `gh release create ${version} -F ./CHANGELOG.md`; 7 | 8 | console.log( `${execSync(command)}` ); 9 | -------------------------------------------------------------------------------- /src/ts/tests/rig.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as fc from 'fast-check'; 3 | 4 | 5 | 6 | 7 | 8 | test( 9 | 'Tests are running (arithmetic isn\'t wrong)', 10 | () => expect(1 + 2).toBe(3) 11 | ); 12 | 13 | 14 | 15 | 16 | 17 | test('fast-check is running (stoch arithmetic isn\'t wrong)', () => { 18 | 19 | fc.assert( 20 | 21 | fc.property( 22 | fc.nat(), fc.nat(), 23 | (a, b) => expect(a + b).toBe(b + a) 24 | ) 25 | 26 | ); 27 | 28 | }); 29 | -------------------------------------------------------------------------------- /jest-stoch.config.js: -------------------------------------------------------------------------------- 1 | 2 | const baseConfig = require('./jest.config.js'); // eslint-disable-line no-undef,@typescript-eslint/no-var-requires 3 | 4 | module.exports = { // eslint-disable-line no-undef 5 | 6 | ... baseConfig, 7 | 8 | testMatch : ['**/*.stoch.ts'], 9 | coverageDirectory : "coverage/stoch/", 10 | 11 | coverageThreshold : { 12 | global : { 13 | branches : 90, 14 | functions : 90, 15 | lines : 90, 16 | statements : 90, 17 | }, 18 | }, 19 | 20 | }; 21 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { // eslint-disable-line no-undef 3 | 4 | root: true, 5 | 6 | parser: '@typescript-eslint/parser', 7 | 8 | plugins: [ 9 | '@typescript-eslint', 10 | ], 11 | 12 | extends: [ 13 | 'eslint:recommended', 14 | 'plugin:@typescript-eslint/recommended', 15 | ], 16 | 17 | rules: { 18 | "no-unused-vars" : [ "warn", { argsIgnorePattern : "^_", varsIgnorePattern : "^_", caughtErrors : "all", caughtErrorsIgnorePattern : "^_" } ], 19 | "@typescript-eslint/no-unused-vars" : [ "warn", { argsIgnorePattern : "^_", varsIgnorePattern : "^_", caughtErrors : "all", caughtErrorsIgnorePattern : "^_" } ], 20 | } 21 | 22 | }; 23 | -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | 2 | module.exports = { // eslint-disable-line no-undef 3 | 4 | testEnvironment : 'node', 5 | 6 | moduleFileExtensions : ['js', 'ts'], 7 | coveragePathIgnorePatterns : ["/node_modules/", "/src/ts/tests/"], 8 | testMatch : ['**/*.spec.ts'], 9 | 10 | transform : { '^.+\\.ts$': 'ts-jest' }, 11 | 12 | verbose : false, 13 | collectCoverage : true, 14 | coverageDirectory : "coverage/spec/", 15 | 16 | coverageThreshold : { 17 | global : { 18 | branches : 90, 19 | functions : 90, 20 | lines : 90, 21 | statements : 90, 22 | }, 23 | }, 24 | 25 | collectCoverageFrom: ["src/ts/**/*.{js,ts}"] 26 | 27 | }; 28 | -------------------------------------------------------------------------------- /src/build_js/write_version_file.js: -------------------------------------------------------------------------------- 1 | 2 | /* eslint-disable no-undef */ 3 | /* eslint-disable @typescript-eslint/no-var-requires */ 4 | 5 | const fs = require('fs'), 6 | package = JSON.parse(`${fs.readFileSync('./package.json')}`); 7 | 8 | const filename = './src/ts/generated/package_version.ts'; 9 | 10 | /* eslint-enable @typescript-eslint/no-var-requires */ 11 | /* eslint-enable no-undef */ 12 | 13 | 14 | const data_template = ` 15 | // Generated by %proj%/src/build_js/write_version_file.js 16 | 17 | const version = '${package.version}', 18 | packagename = '${package.name}'; 19 | 20 | export { version, packagename }; 21 | `; 22 | 23 | fs.writeFileSync(filename, data_template, { flag: 'w', encoding: 'utf8'}); 24 | 25 | console.log(`# Wrote version file as ${package.name} ${package.version}; finished`); // eslint-disable-line no-undef 26 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | 2 | const es6_config = { 3 | 4 | input : 'build/es6/circular_buffer.js', 5 | 6 | output : { 7 | file : 'build/circular_buffer.esm.js', 8 | format : 'es', 9 | name : 'circular_buffer' 10 | } 11 | 12 | }; 13 | 14 | 15 | 16 | 17 | 18 | const cjs_config = { 19 | 20 | input : 'build/es6/circular_buffer.js', 21 | 22 | output : { 23 | file : 'build/circular_buffer.cjs.js', 24 | format : 'cjs', 25 | name : 'circular_buffer' 26 | } 27 | 28 | }; 29 | 30 | 31 | 32 | 33 | 34 | const iife_config = { 35 | 36 | input : 'build/es6/circular_buffer.js', 37 | 38 | output : { 39 | file : 'build/circular_buffer.iife.js', 40 | format : 'iife', 41 | name : 'circular_buffer' 42 | } 43 | 44 | }; 45 | 46 | 47 | 48 | 49 | 50 | export default [ es6_config, cjs_config, iife_config ]; 51 | -------------------------------------------------------------------------------- /docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | circular_buffer_js 11 | 12 | 13 | 14 | 15 |

circular_buffer_js

16 | 17 |
    18 |
  1. Main page (here)
  2. 19 |
  3. Github
  4. 20 |
  5. Documentation
  6. 21 |
  7. NPM
  8. 22 |
  9. Issue tracker
  10. 23 |
  11. Example code (todo)
  12. 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /src/html/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 8 | 9 | 10 | circular_buffer_js 11 | 12 | 13 | 14 | 15 |

circular_buffer_js

16 | 17 |
    18 |
  1. Main page (here)
  2. 19 |
  3. Github
  4. 20 |
  5. Documentation
  6. 21 |
  7. NPM
  8. 22 |
  9. Issue tracker
  10. 23 |
  11. Example code (todo)
  12. 24 |
25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 John Haugeland 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 | -------------------------------------------------------------------------------- /dist/circular_buffer.d.ts: -------------------------------------------------------------------------------- 1 | import { version } from './generated/package_version'; 2 | declare type TraversalFunctor = (_element: T, _index?: number, _array?: T[]) => unknown; 3 | declare class circular_buffer { 4 | private _values; 5 | private _cursor; 6 | private _offset; 7 | private _length; 8 | private _capacity; 9 | constructor(uCapacity: number); 10 | get capacity(): number; 11 | set capacity(newSize: number); 12 | get length(): number; 13 | set length(newLength: number); 14 | get available(): number; 15 | get isEmpty(): boolean; 16 | get isFull(): boolean; 17 | get first(): T; 18 | get last(): T; 19 | static from(i: Iterable | ArrayLike, map_fn?: (_k: T, _i: number) => T, t?: unknown): circular_buffer; 20 | push(v: T): T; 21 | shove(v: T): T | undefined; 22 | fill(x: T): T[]; 23 | indexOf(searchElement: T, fromIndex?: number): number; 24 | find(predicate: TraversalFunctor, thisArg?: unknown): T | unknown; 25 | every(functor: TraversalFunctor, thisArg?: unknown): boolean; 26 | some(functor: TraversalFunctor, thisArg?: unknown): boolean; 27 | reverse(): circular_buffer; 28 | clear(): T[]; 29 | pop(): T | undefined; 30 | at(i: number): T; 31 | pos(i: number): T; 32 | offset(): number; 33 | resize(newSize: number, preferEnd?: boolean): void; 34 | toArray(): T[]; 35 | } 36 | export { version, circular_buffer, TraversalFunctor }; 37 | -------------------------------------------------------------------------------- /.github/workflows/node.js.yml: -------------------------------------------------------------------------------- 1 | # This workflow will do a clean install of node dependencies, build the source code and run tests across different versions of node 2 | # For more information see: https://help.github.com/actions/language-and-framework-guides/using-nodejs-with-github-actions 3 | 4 | name: Node.js CI 5 | 6 | on: 7 | push: 8 | branches: [ main ] 9 | pull_request: 10 | branches: [ main ] 11 | 12 | jobs: 13 | build: 14 | 15 | strategy: 16 | matrix: 17 | include: 18 | - node-version: 16.x # fastest, so run first, to error fast 19 | os: ubuntu-latest 20 | - node-version: 16.x # slowest, so run next. sort by slowest from here to get earliest end through parallelism 21 | os: macos-latest 22 | - node-version: 16.x # finish check big-3 on latest current 23 | os: windows-latest 24 | - node-version: 10.x # lastly check just ubuntu on historic node versions because speed, oldest (slowest) first 25 | os: ubuntu-latest 26 | - node-version: 11.x 27 | os: ubuntu-latest 28 | - node-version: 12.x 29 | os: ubuntu-latest 30 | - node-version: 13.x 31 | os: ubuntu-latest 32 | - node-version: 14.x 33 | os: ubuntu-latest 34 | - node-version: 15.x 35 | os: ubuntu-latest 36 | 37 | runs-on: ${{ matrix.os }} 38 | 39 | steps: 40 | - uses: actions/checkout@v2 41 | - name: Use Node.js ${{ matrix.node-version }} 42 | uses: actions/setup-node@v1 43 | with: 44 | node-version: ${{ matrix.node-version }} 45 | - run: npm install && npm run build 46 | - name: Coveralls 47 | uses: coverallsapp/github-action@master 48 | with: 49 | github-token: ${{ secrets.GITHUB_TOKEN }} 50 | path-to-lcov: ./coverage/spec/lcov.info 51 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | # dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | build/ 107 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | 9 | # Diagnostic reports (https://nodejs.org/api/report.html) 10 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 11 | 12 | # Runtime data 13 | pids 14 | *.pid 15 | *.seed 16 | *.pid.lock 17 | 18 | # Directory for instrumented libs generated by jscoverage/JSCover 19 | lib-cov 20 | 21 | # Coverage directory used by tools like istanbul 22 | coverage 23 | *.lcov 24 | 25 | # nyc test coverage 26 | .nyc_output 27 | 28 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 29 | .grunt 30 | 31 | # Bower dependency directory (https://bower.io/) 32 | bower_components 33 | 34 | # node-waf configuration 35 | .lock-wscript 36 | 37 | # Compiled binary addons (https://nodejs.org/api/addons.html) 38 | build/Release 39 | 40 | # Dependency directories 41 | node_modules/ 42 | jspm_packages/ 43 | 44 | # TypeScript v1 declaration files 45 | typings/ 46 | 47 | # TypeScript cache 48 | *.tsbuildinfo 49 | 50 | # Optional npm cache directory 51 | .npm 52 | 53 | # Optional eslint cache 54 | .eslintcache 55 | 56 | # Microbundle cache 57 | .rpt2_cache/ 58 | .rts2_cache_cjs/ 59 | .rts2_cache_es/ 60 | .rts2_cache_umd/ 61 | 62 | # Optional REPL history 63 | .node_repl_history 64 | 65 | # Output of 'npm pack' 66 | *.tgz 67 | 68 | # Yarn Integrity file 69 | .yarn-integrity 70 | 71 | # dotenv environment variables file 72 | .env 73 | .env.test 74 | 75 | # parcel-bundler cache (https://parceljs.org/) 76 | .cache 77 | 78 | # Next.js build output 79 | .next 80 | 81 | # Nuxt.js build / generate output 82 | .nuxt 83 | # dist 84 | 85 | # Gatsby files 86 | .cache/ 87 | # Comment in the public line in if your project uses Gatsby and *not* Next.js 88 | # https://nextjs.org/blog/next-9-1#public-directory-support 89 | # public 90 | 91 | # vuepress build output 92 | .vuepress/dist 93 | 94 | # Serverless directories 95 | .serverless/ 96 | 97 | # FuseBox cache 98 | .fusebox/ 99 | 100 | # DynamoDB Local files 101 | .dynamodb/ 102 | 103 | # TernJS port file 104 | .tern-port 105 | 106 | build/ 107 | docs/ 108 | 109 | .github 110 | archive_docs_by_version.js 111 | jest* 112 | rollup* 113 | src/ 114 | tsconfig.json 115 | 116 | .eslintignore 117 | .eslintrc 118 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "es2015", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019', 'ES2020', or 'ESNEXT'. */ 4 | "module": "es2015", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', 'es2020', or 'ESNext'. */ 5 | "lib": ["es2015"], /* Specify library files to be included in the compilation. */ 6 | "declaration": true, /* Generates corresponding '.d.ts' file. */ 7 | "outDir": "./build/es6/", /* Redirect output structure to the directory. */ 8 | "rootDir": "./src/ts/", /* Specify the root directory of input files. Use to control the output directory structure with --outDir. */ 9 | "removeComments": true, /* Do not emit comments to output. */ 10 | "strict": true, /* Enable all strict type-checking options. */ 11 | "noImplicitAny": true, /* Raise error on expressions and declarations with an implied 'any' type. */ 12 | "strictNullChecks": true, /* Enable strict null checks. */ 13 | "strictFunctionTypes": true, /* Enable strict checking of function types. */ 14 | "strictBindCallApply": true, /* Enable strict 'bind', 'call', and 'apply' methods on functions. */ 15 | "strictPropertyInitialization": true, /* Enable strict checking of property initialization in classes. */ 16 | "noImplicitThis": true, /* Raise error on 'this' expressions with an implied 'any' type. */ 17 | "alwaysStrict": true, /* Parse in strict mode and emit "use strict" for each source file. */ 18 | "noUnusedLocals": true, /* Report errors on unused locals. */ 19 | "noUnusedParameters": true, /* Report errors on unused parameters. */ 20 | "noImplicitReturns": true, /* Report error when not all code paths in function return a value. */ 21 | "noFallthroughCasesInSwitch": true, /* Report errors for fallthrough cases in switch statement. */ 22 | "noUncheckedIndexedAccess": true, /* Include 'undefined' in index signature results */ 23 | "moduleResolution": "node", /* Specify module resolution strategy: 'node' (Node.js) or 'classic' (TypeScript pre-1.6). */ 24 | "esModuleInterop": true, /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 25 | "skipLibCheck": true, /* Skip type checking of declaration files. */ 26 | "forceConsistentCasingInFileNames": true /* Disallow inconsistently-cased references to the same file. */ 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "circular_buffer_js", 3 | "version": "1.10.0", 4 | "description": "Fast TS/JS implementation of a circular buffer (aka ring queue, cyclic list, etc.) Extremely well tested.", 5 | "homepage": "https://stonecypher.github.io/circular_buffer_js/", 6 | "main": "dist/circular_buffer.cjs.min.js", 7 | "module": "dist/circular_buffer.esm.min.js", 8 | "types": "dist/circular_buffer.d.ts", 9 | "scripts": { 10 | "clean": "rimraf -f dist && mkdir dist && cd dist && mkdir generated && cd ..", 11 | "dist": "cp build/circular_buffer.iife.js dist/ && cp build/es6/circular_buffer.d.ts dist/ && cp build/es6/generated/package_version.d.ts dist/generated/ ", 12 | "docs": "typedoc --out docs/docs src/ts && cp src/html/.nojekyll docs && cp src/html/.nojekyll docs/docs && cp src/html/.nojekyll docs/docs/assets && cp src/html/.nojekyll docs/docs/modules && cp src/html/.nojekyll docs/docs/classes", 13 | "site": "cp -r src/html/* docs/", 14 | "version": "node ./src/build_js/write_version_file.js", 15 | "bundle": "rollup -c", 16 | "typescript": "tsc --build tsconfig.json", 17 | "minify-cjs": "terser build/circular_buffer.cjs.js > dist/circular_buffer.cjs.min.js", 18 | "minify-esm": "terser build/circular_buffer.esm.js > dist/circular_buffer.esm.min.js", 19 | "minify-iife": "terser build/circular_buffer.iife.js > dist/circular_buffer.iife.min.js", 20 | "minify": "npm run minify-esm && npm run minify-iife && npm run minify-cjs", 21 | "prep": "npm run clean && npm run version", 22 | "make": "npm run typescript && npm run bundle && npm run minify && npm run dist", 23 | "changelog": "auto-changelog --hide-credit --commit-limit false --unreleased", 24 | "finish": "npm run docs && npm run changelog && npm run site", 25 | "test": "jest --color --coverage --verbose && jest -c jest-stoch.config.js --color --verbose", 26 | "lint": "eslint . --ext .ts", 27 | "build": "npm run prep && npm run make && npm run lint && npm run test && npm run finish", 28 | "release": "node ./src/build_js/release.js" 29 | }, 30 | "repository": { 31 | "type": "git", 32 | "url": "git+https://github.com/StoneCypher/circular_buffer_js.git" 33 | }, 34 | "keywords": [ 35 | "circular", 36 | "buffer", 37 | "circular-buffer", 38 | "ring", 39 | "buffer", 40 | "ring-buffer", 41 | "queue", 42 | "cyclic", 43 | "circular-queue", 44 | "circular-list", 45 | "cyclic-list", 46 | "list", 47 | "ring-queue", 48 | "datastructre", 49 | "data-structure", 50 | "stochastic-tested", 51 | "property-tested", 52 | "stonecypher" 53 | ], 54 | "author": "John Haugeland ", 55 | "license": "MIT", 56 | "bugs": { 57 | "url": "https://github.com/StoneCypher/circular_buffer_js/issues" 58 | }, 59 | "homepage": "https://github.com/StoneCypher/circular_buffer_js#readme", 60 | "devDependencies": { 61 | "@types/node": "^14.14.14", 62 | "@typescript-eslint/eslint-plugin": "^4.28.4", 63 | "@typescript-eslint/parser": "^4.28.4", 64 | "auto-changelog": "^2.3.0", 65 | "eslint": "^7.31.0", 66 | "fast-check": "^2.10.0", 67 | "jest": "^26.6.3", 68 | "rimraf": "^3.0.2", 69 | "rollup": "^2.35.1", 70 | "terser": "^5.5.1", 71 | "ts-jest": "^26.4.4", 72 | "typedoc": "^0.21.4", 73 | "typescript": "^4.3.5" 74 | } 75 | } 76 | -------------------------------------------------------------------------------- /dist/circular_buffer.esm.min.js: -------------------------------------------------------------------------------- 1 | const version="1.10.0";class circular_buffer{constructor(uCapacity){if(!Number.isInteger(uCapacity)){throw new RangeError(`Capacity must be an integer, received ${uCapacity}`)}if(uCapacity<0){throw new RangeError(`Capacity must be a non-negative integer, received ${uCapacity}`)}this._values=new Array(uCapacity);this._capacity=uCapacity;this._cursor=0;this._offset=0;this._length=0}get capacity(){return this._capacity}set capacity(newSize){this.resize(newSize)}get length(){return this._length}set length(newLength){if(newLength>this._capacity){throw new RangeError(`Requested new length [${newLength}] exceeds container capacity [${this._capacity}]`)}if(newLength<0){throw new RangeError(`Requested new length [${newLength}] cannot be negative`)}if(!Number.isInteger(newLength)){throw new RangeError(`Requested new length [${newLength}] must be an integer`)}if(this._length<=newLength){return}this._length=newLength}get available(){return this._capacity-this._length}get isEmpty(){return this._length===0}get isFull(){return this._length===this._capacity}get first(){if(this.isEmpty){throw new RangeError("Cannot return first element of an empty container")}return this.at(0)}get last(){if(this.isEmpty){throw new RangeError("Cannot return last element of an empty container")}return this.at(this.length-1)}static from(i,map_fn,t){const new_array=map_fn?Array.from(i,map_fn,t):Array.from(i);const target_length=new_array.length;const ncb=new circular_buffer(target_length);ncb._values=new_array;ncb._length=target_length;return ncb}push(v){if(this.isFull){throw new RangeError(`Cannot push, structure is full to capacity`)}this._values[(this._cursor+this._length++)%this._capacity]=v;return v}shove(v){let shoved;if(this._capacity===0){throw new RangeError(`Cannot shove, structure is zero-capacity`)}if(this.isFull){shoved=this.pop()}this.push(v);return shoved}fill(x){for(let i=0;i=this._capacity){this._cursor-=this._capacity}return cache}at(i){if(i<0){throw new RangeError(`circular_buffer does not support negative traversals; called at(${i})`)}if(!Number.isInteger(i)){throw new RangeError(`Accessors must be non-negative integers; called at(${i})`)}if(i>=this._capacity){throw new RangeError(`Requested cell ${i} exceeds container permanent capacity`)}if(i>=this._length){throw new RangeError(`Requested cell ${i} exceeds container current length`)}return this._values[(this._cursor+i)%this._capacity]}pos(i){return this.at(i-this.offset())}offset(){return this._offset}resize(newSize,preferEnd=false){this._values=this.toArray();this._cursor=0;const oldSize=this._length;this._length=Math.min(this._length,newSize);this._capacity=newSize;if(newSize>=oldSize){this._values.length=newSize}else{if(preferEnd){const tmp=this._values.slice(oldSize-newSize);this._values=tmp}else{this._values.length=newSize}}}toArray(){const startPoint=this._cursor%this._capacity;if(this._capacity>startPoint+this._length){return this._values.slice(startPoint,startPoint+this._length)}else{const base=this._values.slice(startPoint,this._capacity);base.push(...this._values.slice(0,this.length-(this._capacity-startPoint)));return base}}}export{circular_buffer,version}; 2 | -------------------------------------------------------------------------------- /dist/circular_buffer.cjs.min.js: -------------------------------------------------------------------------------- 1 | "use strict";Object.defineProperty(exports,"__esModule",{value:true});const version="1.10.0";class circular_buffer{constructor(uCapacity){if(!Number.isInteger(uCapacity)){throw new RangeError(`Capacity must be an integer, received ${uCapacity}`)}if(uCapacity<0){throw new RangeError(`Capacity must be a non-negative integer, received ${uCapacity}`)}this._values=new Array(uCapacity);this._capacity=uCapacity;this._cursor=0;this._offset=0;this._length=0}get capacity(){return this._capacity}set capacity(newSize){this.resize(newSize)}get length(){return this._length}set length(newLength){if(newLength>this._capacity){throw new RangeError(`Requested new length [${newLength}] exceeds container capacity [${this._capacity}]`)}if(newLength<0){throw new RangeError(`Requested new length [${newLength}] cannot be negative`)}if(!Number.isInteger(newLength)){throw new RangeError(`Requested new length [${newLength}] must be an integer`)}if(this._length<=newLength){return}this._length=newLength}get available(){return this._capacity-this._length}get isEmpty(){return this._length===0}get isFull(){return this._length===this._capacity}get first(){if(this.isEmpty){throw new RangeError("Cannot return first element of an empty container")}return this.at(0)}get last(){if(this.isEmpty){throw new RangeError("Cannot return last element of an empty container")}return this.at(this.length-1)}static from(i,map_fn,t){const new_array=map_fn?Array.from(i,map_fn,t):Array.from(i);const target_length=new_array.length;const ncb=new circular_buffer(target_length);ncb._values=new_array;ncb._length=target_length;return ncb}push(v){if(this.isFull){throw new RangeError(`Cannot push, structure is full to capacity`)}this._values[(this._cursor+this._length++)%this._capacity]=v;return v}shove(v){let shoved;if(this._capacity===0){throw new RangeError(`Cannot shove, structure is zero-capacity`)}if(this.isFull){shoved=this.pop()}this.push(v);return shoved}fill(x){for(let i=0;i=this._capacity){this._cursor-=this._capacity}return cache}at(i){if(i<0){throw new RangeError(`circular_buffer does not support negative traversals; called at(${i})`)}if(!Number.isInteger(i)){throw new RangeError(`Accessors must be non-negative integers; called at(${i})`)}if(i>=this._capacity){throw new RangeError(`Requested cell ${i} exceeds container permanent capacity`)}if(i>=this._length){throw new RangeError(`Requested cell ${i} exceeds container current length`)}return this._values[(this._cursor+i)%this._capacity]}pos(i){return this.at(i-this.offset())}offset(){return this._offset}resize(newSize,preferEnd=false){this._values=this.toArray();this._cursor=0;const oldSize=this._length;this._length=Math.min(this._length,newSize);this._capacity=newSize;if(newSize>=oldSize){this._values.length=newSize}else{if(preferEnd){const tmp=this._values.slice(oldSize-newSize);this._values=tmp}else{this._values.length=newSize}}}toArray(){const startPoint=this._cursor%this._capacity;if(this._capacity>startPoint+this._length){return this._values.slice(startPoint,startPoint+this._length)}else{const base=this._values.slice(startPoint,this._capacity);base.push(...this._values.slice(0,this.length-(this._capacity-startPoint)));return base}}}exports.circular_buffer=circular_buffer;exports.version=version; 2 | -------------------------------------------------------------------------------- /dist/circular_buffer.iife.min.js: -------------------------------------------------------------------------------- 1 | var circular_buffer=function(exports){"use strict";const version="1.10.0";class circular_buffer{constructor(uCapacity){if(!Number.isInteger(uCapacity)){throw new RangeError(`Capacity must be an integer, received ${uCapacity}`)}if(uCapacity<0){throw new RangeError(`Capacity must be a non-negative integer, received ${uCapacity}`)}this._values=new Array(uCapacity);this._capacity=uCapacity;this._cursor=0;this._offset=0;this._length=0}get capacity(){return this._capacity}set capacity(newSize){this.resize(newSize)}get length(){return this._length}set length(newLength){if(newLength>this._capacity){throw new RangeError(`Requested new length [${newLength}] exceeds container capacity [${this._capacity}]`)}if(newLength<0){throw new RangeError(`Requested new length [${newLength}] cannot be negative`)}if(!Number.isInteger(newLength)){throw new RangeError(`Requested new length [${newLength}] must be an integer`)}if(this._length<=newLength){return}this._length=newLength}get available(){return this._capacity-this._length}get isEmpty(){return this._length===0}get isFull(){return this._length===this._capacity}get first(){if(this.isEmpty){throw new RangeError("Cannot return first element of an empty container")}return this.at(0)}get last(){if(this.isEmpty){throw new RangeError("Cannot return last element of an empty container")}return this.at(this.length-1)}static from(i,map_fn,t){const new_array=map_fn?Array.from(i,map_fn,t):Array.from(i);const target_length=new_array.length;const ncb=new circular_buffer(target_length);ncb._values=new_array;ncb._length=target_length;return ncb}push(v){if(this.isFull){throw new RangeError(`Cannot push, structure is full to capacity`)}this._values[(this._cursor+this._length++)%this._capacity]=v;return v}shove(v){let shoved;if(this._capacity===0){throw new RangeError(`Cannot shove, structure is zero-capacity`)}if(this.isFull){shoved=this.pop()}this.push(v);return shoved}fill(x){for(let i=0;i=this._capacity){this._cursor-=this._capacity}return cache}at(i){if(i<0){throw new RangeError(`circular_buffer does not support negative traversals; called at(${i})`)}if(!Number.isInteger(i)){throw new RangeError(`Accessors must be non-negative integers; called at(${i})`)}if(i>=this._capacity){throw new RangeError(`Requested cell ${i} exceeds container permanent capacity`)}if(i>=this._length){throw new RangeError(`Requested cell ${i} exceeds container current length`)}return this._values[(this._cursor+i)%this._capacity]}pos(i){return this.at(i-this.offset())}offset(){return this._offset}resize(newSize,preferEnd=false){this._values=this.toArray();this._cursor=0;const oldSize=this._length;this._length=Math.min(this._length,newSize);this._capacity=newSize;if(newSize>=oldSize){this._values.length=newSize}else{if(preferEnd){const tmp=this._values.slice(oldSize-newSize);this._values=tmp}else{this._values.length=newSize}}}toArray(){const startPoint=this._cursor%this._capacity;if(this._capacity>startPoint+this._length){return this._values.slice(startPoint,startPoint+this._length)}else{const base=this._values.slice(startPoint,this._capacity);base.push(...this._values.slice(0,this.length-(this._capacity-startPoint)));return base}}}exports.circular_buffer=circular_buffer;exports.version=version;Object.defineProperty(exports,"__esModule",{value:true});return exports}({}); 2 | -------------------------------------------------------------------------------- /docs/docs/modules.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | circular_buffer_js 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 |

circular_buffer_js

54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 |

Index

62 |
63 |
64 |
65 |

Modules

66 | 70 |
71 |
72 |
73 |
74 |
75 | 94 |
95 |
96 |
97 |
98 |

Legend

99 |
100 |
    101 |
  • Variable
  • 102 |
  • Type alias with type parameter
  • 103 |
104 |
    105 |
  • Class with type parameter
  • 106 |
107 |
108 |
109 |
110 |
111 |

Generated using TypeDoc

112 |
113 |
114 | 115 | 116 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # circular_buffer_js 2 | 3 | [![MIT License](https://img.shields.io/badge/license-MIT-blue.svg)](https://github.com/StoneCypher/circular_buffer_js/blob/master/LICENSEs) 4 | [![NPM Version](https://img.shields.io/npm/v/circular_buffer_js.svg?style=flat)]() 5 | [![Coveralls](https://img.shields.io/coveralls/StoneCypher/circular_buffer_js.svg?style=flat)]() 6 | [![Issues](https://img.shields.io/github/issues-raw/StoneCypher/circular_buffer_js.svg?maxAge=25000)](https://github.com/StoneCypher/circular_buffer_js/issues) 7 | [![GitHub contributors](https://img.shields.io/github/contributors/StoneCypher/circular_buffer_js.svg?style=flat)]() 8 | 9 | ``` 10 | npm install --save-dev circular_buffer_js 11 | ``` 12 | 13 | * [Documentation](https://stonecypher.github.io/circular_buffer_js/docs/) 14 | * [Main functional documentation](https://stonecypher.github.io/circular_buffer_js/docs/classes/circular_buffer.circular_buffer-1.html) 15 | * [Build history](https://github.com/StoneCypher/circular_buffer_js/actions) 16 | 17 | `Typescript` implementation of a circular buffer, and JS compiles to a es6 18 | module minified, es6 commonjs minified, es6 iife minified, and es6 iife full. 19 | 20 | 1. Well tested. 21 | * 100% coverage, 100% property coverage. 22 | 1. Tiny. 23 | * The `es6` minified module build is currently 1.4k. 24 | 1. Dependency-free. 25 | * Only dev-time deps like `typescript` are involved. 26 | 27 | 28 | 29 | 30 | 31 |

32 | 33 | ## API 34 | 35 | You should consider viewing the 36 | [real documentation](https://stonecypher.github.io/circular_buffer_js/docs/classes/circular_buffer.circular_buffer-1.html), 37 | but: 38 | 39 | ```typescript 40 | // yields a buffer of fixed size `size` 41 | const cb = new circular_buffer(size), 42 | cb2 = circular_buffer.from([1,2,3]); 43 | 44 | cb.push(item); // inserts `item` at end of `cb`, then returns `item` 45 | cb.shove(item); // inserts `item` at end of `cb`; remove if needed, returns removed 46 | cb.pop(); // removes and returns first element 47 | cb.at(location); // shows the element at 0-indexed offset `location` 48 | cb.pos(location); // shows the element at run-indexed offset `location` 49 | cb.offset(); // shows the delta from 0-indexed to head 50 | cb.indexOf(item); // returns the index of the first `item`, or -1 51 | cb.find(pred); // return the the first match or undefined 52 | cb.every(pred); // tests if every queue element satisfies the predicate 53 | cb.some(pred); // tests if at least one element satisfies the predicate 54 | cb.fill(item); // maxes `length` and sets every element to `item` 55 | cb.clear(); // empties the container 56 | cb.reverse(); // reverses the container 57 | cb.resize(size,pE); // change to new size, truncating if required; pE to prefer end 58 | cb.toArray(); // return an array of the current contents of the queue 59 | 60 | cb.first; // returns the first value in the queue; throws when empty 61 | cb.last; // returns the last value in the queue; throws when empty 62 | cb.isFull; // returns `true` if no space left; `false` otherwise 63 | cb.isEmpty; // returns `true` if no space remains; `false` otherwise 64 | cb.available; // returns the number of spaces remaining currently 65 | cb.capacity; // returns the total `size` allocated 66 | cb.length; // returns the amount of space currently used 67 | ``` 68 | 69 | 70 | 71 | 72 | 73 |

74 | 75 | ## What is this? 76 | 77 | This is a circular buffer (or cycle buffer, ring queue, etc.) It was written 78 | because a library I wanted to use had a native buggy implementation, so I 79 | provided something more trustworthy. 80 | 81 | A circular buffer is a fixed size buffer that allows you to push and pop 82 | forever, as a first in first out queue-like structure. Circular buffers are 83 | more efficient than queues, but can overflow. 84 | 85 | 86 | 87 |
88 | 89 | ### Basic usage 90 | 91 | ```javascript 92 | import { circular_buffer } from 'circular_buffer_js'; 93 | 94 | const cb = new circular_buffer(3); // [ , , ] 95 | 96 | cb.push(1); // ok: [1, , ] 97 | cb.push(2); // ok: [1,2, ] 98 | cb.push(3); // ok: [1,2,3] 99 | 100 | cb.at(0); // 1 101 | cb.first; // 1 102 | cb.last; // 3 103 | 104 | cb.push(4); // throws - full! ; [1,2,3] 105 | 106 | cb.pop(); // 1: [2,3, ] 107 | cb.at(0); // 2: [2,3, ] 108 | 109 | cb.push(4); // ok: [2,3,4] 110 | cb.push(5); // throws - full! ; [2,3,4] 111 | 112 | cb.pop(); // 2: [3,4, ] 113 | cb.pop(); // 3: [4, , ] 114 | cb.pop(); // 4: [ , , ] 115 | 116 | cb.pop(); // throws - empty! ; [ , , ] 117 | ``` 118 | 119 | 120 | 121 |
122 | 123 | ### Typescript 124 | 125 | It's typescript, so you can also 126 | 127 | ```typescript 128 | import { circular_buffer } from 'circular_buffer_js'; 129 | const cb = new circular_buffer(3); 130 | ``` 131 | 132 | 133 | 134 |
135 | 136 | ### Node CommonJS 137 | 138 | And there's a CommonJS build, so you can 139 | 140 | ```javascript 141 | const cbuf = require('circular_buffer_js'), 142 | circular_buffer = new cbuf.circular_buffer; 143 | ``` 144 | 145 | 146 | 147 |
148 | 149 | ### Browser <script> 150 | 151 | There're also two `iife` builds - both regular and minified - so that you can 152 | use this in older browsers, or from CDN. 153 | 154 | ```html 155 | 156 | 170 | ``` 171 | 172 | 173 | 174 |

175 | 176 | ## Alternatives 177 | 178 | If this doesn't meet your needs, please try: 179 | 180 | * [ring-buffer-ts](https://www.npmjs.com/package/ring-buffer-ts) 181 | * [CBuffer](https://www.npmjs.com/package/CBuffer) 182 | * [ringbuffer.js](https://www.npmjs.com/package/ringbufferjs) 183 | * [qlist](https://www.npmjs.com/package/qlist) 184 | * [fixed-size-list](https://www.npmjs.com/package/fixed-size-list) 185 | * [circular-buffer](https://www.npmjs.com/package/circular-buffer) 186 | * [cyclist](https://www.npmjs.com/package/cyclist) 187 | * [cyclic-buffer](https://www.npmjs.com/package/cyclic-buffer) 188 | * [bsh-circular-buffer](https://www.npmjs.com/package/bsh-circular-buffer) 189 | * [cbarrick-circular-buffer](https://www.npmjs.com/package/cbarrick-circular-buffer) 190 | * [limited-cache](https://www.npmjs.com/package/limited-cache) 191 | -------------------------------------------------------------------------------- /docs/docs/modules/generated_package_version.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | generated/package_version | circular_buffer_js 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Module generated/package_version

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Index

70 |
71 |
72 |
73 |

Variables

74 | 78 |
79 |
80 |
81 |
82 |
83 |

Variables

84 |
85 | 86 |

Const packagename

87 |
packagename: "circular_buffer_js" = 'circular_buffer_js'
88 | 93 |
94 |
95 | 96 |

Const version

97 |
version: "1.10.0" = '1.10.0'
98 | 103 |
104 |
105 |
106 | 131 |
132 |
133 |
134 |
135 |

Legend

136 |
137 |
    138 |
  • Variable
  • 139 |
  • Type alias with type parameter
  • 140 |
141 |
    142 |
  • Class with type parameter
  • 143 |
144 |
145 |
146 |
147 |
148 |

Generated using TypeDoc

149 |
150 |
151 | 152 | 153 | -------------------------------------------------------------------------------- /dist/circular_buffer.iife.js: -------------------------------------------------------------------------------- 1 | var circular_buffer = (function (exports) { 2 | 'use strict'; 3 | 4 | const version = '1.10.0'; 5 | 6 | class circular_buffer { 7 | constructor(uCapacity) { 8 | if (!(Number.isInteger(uCapacity))) { 9 | throw new RangeError(`Capacity must be an integer, received ${uCapacity}`); 10 | } 11 | if (uCapacity < 0) { 12 | throw new RangeError(`Capacity must be a non-negative integer, received ${uCapacity}`); 13 | } 14 | this._values = new Array(uCapacity); 15 | this._capacity = uCapacity; 16 | this._cursor = 0; 17 | this._offset = 0; 18 | this._length = 0; 19 | } 20 | get capacity() { 21 | return this._capacity; 22 | } 23 | set capacity(newSize) { 24 | this.resize(newSize); 25 | } 26 | get length() { 27 | return this._length; 28 | } 29 | set length(newLength) { 30 | if (newLength > this._capacity) { 31 | throw new RangeError(`Requested new length [${newLength}] exceeds container capacity [${this._capacity}]`); 32 | } 33 | if (newLength < 0) { 34 | throw new RangeError(`Requested new length [${newLength}] cannot be negative`); 35 | } 36 | if (!(Number.isInteger(newLength))) { 37 | throw new RangeError(`Requested new length [${newLength}] must be an integer`); 38 | } 39 | if (this._length <= newLength) { 40 | return; 41 | } 42 | this._length = newLength; 43 | } 44 | get available() { 45 | return this._capacity - this._length; 46 | } 47 | get isEmpty() { 48 | return this._length === 0; 49 | } 50 | get isFull() { 51 | return this._length === this._capacity; 52 | } 53 | get first() { 54 | if (this.isEmpty) { 55 | throw new RangeError('Cannot return first element of an empty container'); 56 | } 57 | return this.at(0); 58 | } 59 | get last() { 60 | if (this.isEmpty) { 61 | throw new RangeError('Cannot return last element of an empty container'); 62 | } 63 | return this.at(this.length - 1); 64 | } 65 | static from(i, map_fn, t) { 66 | const new_array = map_fn 67 | ? Array.from(i, map_fn, t) 68 | : Array.from(i); 69 | const target_length = new_array.length; 70 | const ncb = new circular_buffer(target_length); 71 | ncb._values = new_array; 72 | ncb._length = target_length; 73 | return ncb; 74 | } 75 | push(v) { 76 | if (this.isFull) { 77 | throw new RangeError(`Cannot push, structure is full to capacity`); 78 | } 79 | this._values[(this._cursor + this._length++) % this._capacity] = v; 80 | return v; 81 | } 82 | shove(v) { 83 | let shoved; 84 | if (this._capacity === 0) { 85 | throw new RangeError(`Cannot shove, structure is zero-capacity`); 86 | } 87 | if (this.isFull) { 88 | shoved = this.pop(); 89 | } 90 | this.push(v); 91 | return shoved; 92 | } 93 | fill(x) { 94 | for (let i = 0; i < this._capacity; i++) { 95 | this._values[i] = x; 96 | } 97 | this._length = this._capacity; 98 | return this._values; 99 | } 100 | indexOf(searchElement, fromIndex) { 101 | const normalized = this.toArray(); 102 | return normalized.indexOf(searchElement, fromIndex); 103 | } 104 | find(predicate, thisArg) { 105 | return this.toArray().find(predicate, thisArg); 106 | } 107 | every(functor, thisArg) { 108 | const normalized = this.toArray(), res = normalized.every(functor, thisArg); 109 | this._values = normalized; 110 | this._values.length = this._capacity; 111 | this._cursor = 0; 112 | return res; 113 | } 114 | some(functor, thisArg) { 115 | const normalized = this.toArray(), res = normalized.some(functor, thisArg); 116 | this._values = normalized; 117 | this._values.length = this._capacity; 118 | this._cursor = 0; 119 | return res; 120 | } 121 | reverse() { 122 | const normalized = this.toArray(); 123 | this._values = normalized.reverse(); 124 | this._values.length = this._capacity; 125 | this._cursor = 0; 126 | return this; 127 | } 128 | clear() { 129 | const old = this.toArray(); 130 | this._length = 0; 131 | return old; 132 | } 133 | pop() { 134 | if (this._length <= 0) { 135 | throw new RangeError(`Cannot pop, structure is empty`); 136 | } 137 | const cache = this.at(0); 138 | --this._length; 139 | ++this._offset; 140 | ++this._cursor; 141 | if (this._cursor >= this._capacity) { 142 | this._cursor -= this._capacity; 143 | } 144 | return cache; 145 | } 146 | at(i) { 147 | if (i < 0) { 148 | throw new RangeError(`circular_buffer does not support negative traversals; called at(${i})`); 149 | } 150 | if (!(Number.isInteger(i))) { 151 | throw new RangeError(`Accessors must be non-negative integers; called at(${i})`); 152 | } 153 | if (i >= this._capacity) { 154 | throw new RangeError(`Requested cell ${i} exceeds container permanent capacity`); 155 | } 156 | if (i >= this._length) { 157 | throw new RangeError(`Requested cell ${i} exceeds container current length`); 158 | } 159 | return this._values[(this._cursor + i) % this._capacity]; 160 | } 161 | pos(i) { 162 | return this.at(i - this.offset()); 163 | } 164 | offset() { 165 | return this._offset; 166 | } 167 | resize(newSize, preferEnd = false) { 168 | this._values = this.toArray(); 169 | this._cursor = 0; 170 | const oldSize = this._length; 171 | this._length = Math.min(this._length, newSize); 172 | this._capacity = newSize; 173 | if (newSize >= oldSize) { 174 | this._values.length = newSize; 175 | } 176 | else { 177 | if (preferEnd) { 178 | const tmp = this._values.slice(oldSize - newSize); 179 | this._values = tmp; 180 | } 181 | else { 182 | this._values.length = newSize; 183 | } 184 | } 185 | } 186 | toArray() { 187 | const startPoint = this._cursor % this._capacity; 188 | if (this._capacity > (startPoint + this._length)) { 189 | return this._values.slice(startPoint, startPoint + this._length); 190 | } 191 | else { 192 | const base = this._values.slice(startPoint, this._capacity); 193 | base.push(...this._values.slice(0, this.length - (this._capacity - startPoint))); 194 | return base; 195 | } 196 | } 197 | } 198 | 199 | exports.circular_buffer = circular_buffer; 200 | exports.version = version; 201 | 202 | Object.defineProperty(exports, '__esModule', { value: true }); 203 | 204 | return exports; 205 | 206 | }({})); 207 | -------------------------------------------------------------------------------- /docs/docs/modules/circular_buffer.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | circular_buffer | circular_buffer_js 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 | 61 |

Module circular_buffer

62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |

Index

70 |
71 |
72 |
73 |

References

74 | 77 |
78 |
79 |

Classes

80 | 83 |
84 |
85 |

Type aliases

86 | 89 |
90 |
91 |
92 |
93 |
94 |

References

95 |
96 | 97 |

version

98 | Re-exports version 99 |
100 |
101 |
102 |

Type aliases

103 |
104 | 105 |

TraversalFunctor

106 |
TraversalFunctor<T>: (_element: T, _index?: number, _array?: T[]) => unknown
107 | 112 |

Type parameters

113 |
    114 |
  • 115 |

    T

    116 |
  • 117 |
118 |
119 |

Type declaration

120 |
    121 |
  • 122 |
      123 |
    • (_element: T, _index?: number, _array?: T[]): unknown
    • 124 |
    125 |
      126 |
    • 127 |

      Parameters

      128 |
        129 |
      • 130 |
        _element: T
        131 |
      • 132 |
      • 133 |
        Optional _index: number
        134 |
      • 135 |
      • 136 |
        Optional _array: T[]
        137 |
      • 138 |
      139 |

      Returns unknown

      140 |
    • 141 |
    142 |
  • 143 |
144 |
145 |
146 |
147 |
148 | 176 |
177 |
178 |
179 |
180 |

Legend

181 |
182 |
    183 |
  • Variable
  • 184 |
  • Type alias with type parameter
  • 185 |
186 |
    187 |
  • Class with type parameter
  • 188 |
189 |
190 |
191 |
192 |
193 |

Generated using TypeDoc

194 |
195 |
196 | 197 | 198 | -------------------------------------------------------------------------------- /docs/docs/assets/js/search.js: -------------------------------------------------------------------------------- 1 | window.searchData = {"kinds":{"1":"Module","32":"Variable","128":"Class","512":"Constructor","1024":"Property","2048":"Method","65536":"Type literal","262144":"Accessor","4194304":"Type alias","16777216":"Reference"},"rows":[{"id":0,"kind":1,"name":"circular_buffer","url":"modules/circular_buffer.html","classes":"tsd-kind-module"},{"id":1,"kind":128,"name":"circular_buffer","url":"classes/circular_buffer.circular_buffer-1.html","classes":"tsd-kind-class tsd-parent-kind-module tsd-has-type-parameter","parent":"circular_buffer"},{"id":2,"kind":2048,"name":"from","url":"classes/circular_buffer.circular_buffer-1.html#from","classes":"tsd-kind-method tsd-parent-kind-class tsd-has-type-parameter tsd-is-static","parent":"circular_buffer.circular_buffer"},{"id":3,"kind":512,"name":"constructor","url":"classes/circular_buffer.circular_buffer-1.html#constructor","classes":"tsd-kind-constructor tsd-parent-kind-class tsd-has-type-parameter","parent":"circular_buffer.circular_buffer"},{"id":4,"kind":1024,"name":"_values","url":"classes/circular_buffer.circular_buffer-1.html#_values","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"circular_buffer.circular_buffer"},{"id":5,"kind":1024,"name":"_cursor","url":"classes/circular_buffer.circular_buffer-1.html#_cursor","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"circular_buffer.circular_buffer"},{"id":6,"kind":1024,"name":"_offset","url":"classes/circular_buffer.circular_buffer-1.html#_offset","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"circular_buffer.circular_buffer"},{"id":7,"kind":1024,"name":"_length","url":"classes/circular_buffer.circular_buffer-1.html#_length","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"circular_buffer.circular_buffer"},{"id":8,"kind":1024,"name":"_capacity","url":"classes/circular_buffer.circular_buffer-1.html#_capacity","classes":"tsd-kind-property tsd-parent-kind-class tsd-is-private","parent":"circular_buffer.circular_buffer"},{"id":9,"kind":262144,"name":"capacity","url":"classes/circular_buffer.circular_buffer-1.html#capacity","classes":"tsd-kind-accessor tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":10,"kind":262144,"name":"length","url":"classes/circular_buffer.circular_buffer-1.html#length","classes":"tsd-kind-accessor tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":11,"kind":262144,"name":"available","url":"classes/circular_buffer.circular_buffer-1.html#available","classes":"tsd-kind-get-signature tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":12,"kind":262144,"name":"isEmpty","url":"classes/circular_buffer.circular_buffer-1.html#isEmpty","classes":"tsd-kind-get-signature tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":13,"kind":262144,"name":"isFull","url":"classes/circular_buffer.circular_buffer-1.html#isFull","classes":"tsd-kind-get-signature tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":14,"kind":262144,"name":"first","url":"classes/circular_buffer.circular_buffer-1.html#first","classes":"tsd-kind-get-signature tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":15,"kind":262144,"name":"last","url":"classes/circular_buffer.circular_buffer-1.html#last","classes":"tsd-kind-get-signature tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":16,"kind":2048,"name":"push","url":"classes/circular_buffer.circular_buffer-1.html#push","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":17,"kind":2048,"name":"shove","url":"classes/circular_buffer.circular_buffer-1.html#shove","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":18,"kind":2048,"name":"fill","url":"classes/circular_buffer.circular_buffer-1.html#fill","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":19,"kind":2048,"name":"indexOf","url":"classes/circular_buffer.circular_buffer-1.html#indexOf","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":20,"kind":2048,"name":"find","url":"classes/circular_buffer.circular_buffer-1.html#find","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":21,"kind":2048,"name":"every","url":"classes/circular_buffer.circular_buffer-1.html#every","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":22,"kind":2048,"name":"some","url":"classes/circular_buffer.circular_buffer-1.html#some","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":23,"kind":2048,"name":"reverse","url":"classes/circular_buffer.circular_buffer-1.html#reverse","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":24,"kind":2048,"name":"clear","url":"classes/circular_buffer.circular_buffer-1.html#clear","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":25,"kind":2048,"name":"pop","url":"classes/circular_buffer.circular_buffer-1.html#pop","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":26,"kind":2048,"name":"at","url":"classes/circular_buffer.circular_buffer-1.html#at","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":27,"kind":2048,"name":"pos","url":"classes/circular_buffer.circular_buffer-1.html#pos","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":28,"kind":2048,"name":"offset","url":"classes/circular_buffer.circular_buffer-1.html#offset","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":29,"kind":2048,"name":"resize","url":"classes/circular_buffer.circular_buffer-1.html#resize","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":30,"kind":2048,"name":"toArray","url":"classes/circular_buffer.circular_buffer-1.html#toArray","classes":"tsd-kind-method tsd-parent-kind-class","parent":"circular_buffer.circular_buffer"},{"id":31,"kind":4194304,"name":"TraversalFunctor","url":"modules/circular_buffer.html#TraversalFunctor","classes":"tsd-kind-type-alias tsd-parent-kind-module tsd-has-type-parameter","parent":"circular_buffer"},{"id":32,"kind":65536,"name":"__type","url":"modules/circular_buffer.html#TraversalFunctor.__type","classes":"tsd-kind-type-literal tsd-parent-kind-type-alias","parent":"circular_buffer.TraversalFunctor"},{"id":33,"kind":1,"name":"generated/package_version","url":"modules/generated_package_version.html","classes":"tsd-kind-module"},{"id":34,"kind":32,"name":"version","url":"modules/generated_package_version.html#version","classes":"tsd-kind-variable tsd-parent-kind-module","parent":"generated/package_version"},{"id":35,"kind":32,"name":"packagename","url":"modules/generated_package_version.html#packagename","classes":"tsd-kind-variable tsd-parent-kind-module","parent":"generated/package_version"},{"id":36,"kind":16777216,"name":"version","url":"modules/circular_buffer.html#version","classes":"tsd-kind-reference tsd-parent-kind-module","parent":"circular_buffer"}],"index":{"version":"2.3.9","fields":["name","parent"],"fieldVectors":[["name/0",[0,19.328]],["parent/0",[]],["name/1",[0,19.328]],["parent/1",[0,1.889]],["name/2",[1,32.321]],["parent/2",[2,0.247]],["name/3",[3,32.321]],["parent/3",[2,0.247]],["name/4",[4,32.321]],["parent/4",[2,0.247]],["name/5",[5,32.321]],["parent/5",[2,0.247]],["name/6",[6,32.321]],["parent/6",[2,0.247]],["name/7",[7,32.321]],["parent/7",[2,0.247]],["name/8",[8,32.321]],["parent/8",[2,0.247]],["name/9",[9,32.321]],["parent/9",[2,0.247]],["name/10",[10,32.321]],["parent/10",[2,0.247]],["name/11",[11,32.321]],["parent/11",[2,0.247]],["name/12",[12,32.321]],["parent/12",[2,0.247]],["name/13",[13,32.321]],["parent/13",[2,0.247]],["name/14",[14,32.321]],["parent/14",[2,0.247]],["name/15",[15,32.321]],["parent/15",[2,0.247]],["name/16",[16,32.321]],["parent/16",[2,0.247]],["name/17",[17,32.321]],["parent/17",[2,0.247]],["name/18",[18,32.321]],["parent/18",[2,0.247]],["name/19",[19,32.321]],["parent/19",[2,0.247]],["name/20",[20,32.321]],["parent/20",[2,0.247]],["name/21",[21,32.321]],["parent/21",[2,0.247]],["name/22",[22,32.321]],["parent/22",[2,0.247]],["name/23",[23,32.321]],["parent/23",[2,0.247]],["name/24",[24,32.321]],["parent/24",[2,0.247]],["name/25",[25,32.321]],["parent/25",[2,0.247]],["name/26",[26,32.321]],["parent/26",[2,0.247]],["name/27",[27,32.321]],["parent/27",[2,0.247]],["name/28",[28,32.321]],["parent/28",[2,0.247]],["name/29",[29,32.321]],["parent/29",[2,0.247]],["name/30",[30,32.321]],["parent/30",[2,0.247]],["name/31",[31,32.321]],["parent/31",[0,1.889]],["name/32",[32,32.321]],["parent/32",[33,3.158]],["name/33",[34,23.848]],["parent/33",[]],["name/34",[35,27.213]],["parent/34",[34,2.33]],["name/35",[36,32.321]],["parent/35",[34,2.33]],["name/36",[35,27.213]],["parent/36",[0,1.889]]],"invertedIndex":[["__type",{"_index":32,"name":{"32":{}},"parent":{}}],["_capacity",{"_index":8,"name":{"8":{}},"parent":{}}],["_cursor",{"_index":5,"name":{"5":{}},"parent":{}}],["_length",{"_index":7,"name":{"7":{}},"parent":{}}],["_offset",{"_index":6,"name":{"6":{}},"parent":{}}],["_values",{"_index":4,"name":{"4":{}},"parent":{}}],["at",{"_index":26,"name":{"26":{}},"parent":{}}],["available",{"_index":11,"name":{"11":{}},"parent":{}}],["capacity",{"_index":9,"name":{"9":{}},"parent":{}}],["circular_buffer",{"_index":0,"name":{"0":{},"1":{}},"parent":{"1":{},"31":{},"36":{}}}],["circular_buffer.circular_buffer",{"_index":2,"name":{},"parent":{"2":{},"3":{},"4":{},"5":{},"6":{},"7":{},"8":{},"9":{},"10":{},"11":{},"12":{},"13":{},"14":{},"15":{},"16":{},"17":{},"18":{},"19":{},"20":{},"21":{},"22":{},"23":{},"24":{},"25":{},"26":{},"27":{},"28":{},"29":{},"30":{}}}],["circular_buffer.traversalfunctor",{"_index":33,"name":{},"parent":{"32":{}}}],["clear",{"_index":24,"name":{"24":{}},"parent":{}}],["constructor",{"_index":3,"name":{"3":{}},"parent":{}}],["every",{"_index":21,"name":{"21":{}},"parent":{}}],["fill",{"_index":18,"name":{"18":{}},"parent":{}}],["find",{"_index":20,"name":{"20":{}},"parent":{}}],["first",{"_index":14,"name":{"14":{}},"parent":{}}],["from",{"_index":1,"name":{"2":{}},"parent":{}}],["generated/package_version",{"_index":34,"name":{"33":{}},"parent":{"34":{},"35":{}}}],["indexof",{"_index":19,"name":{"19":{}},"parent":{}}],["isempty",{"_index":12,"name":{"12":{}},"parent":{}}],["isfull",{"_index":13,"name":{"13":{}},"parent":{}}],["last",{"_index":15,"name":{"15":{}},"parent":{}}],["length",{"_index":10,"name":{"10":{}},"parent":{}}],["offset",{"_index":28,"name":{"28":{}},"parent":{}}],["packagename",{"_index":36,"name":{"35":{}},"parent":{}}],["pop",{"_index":25,"name":{"25":{}},"parent":{}}],["pos",{"_index":27,"name":{"27":{}},"parent":{}}],["push",{"_index":16,"name":{"16":{}},"parent":{}}],["resize",{"_index":29,"name":{"29":{}},"parent":{}}],["reverse",{"_index":23,"name":{"23":{}},"parent":{}}],["shove",{"_index":17,"name":{"17":{}},"parent":{}}],["some",{"_index":22,"name":{"22":{}},"parent":{}}],["toarray",{"_index":30,"name":{"30":{}},"parent":{}}],["traversalfunctor",{"_index":31,"name":{"31":{}},"parent":{}}],["version",{"_index":35,"name":{"34":{},"36":{}},"parent":{}}]],"pipeline":[]}} -------------------------------------------------------------------------------- /src/ts/tests/circular_buffer.spec.ts: -------------------------------------------------------------------------------- 1 | 2 | import { version, circular_buffer, TraversalFunctor } from '../circular_buffer'; 3 | 4 | 5 | 6 | 7 | 8 | describe('[UNIT] version', () => { 9 | test('Version is present', () => expect(typeof version).toBe('string')); 10 | test('Version is non-empty', () => expect(version.length > 0).toBe(true)); 11 | }); 12 | 13 | 14 | 15 | 16 | 17 | describe('[UNIT] Zero size', () => { 18 | 19 | const cb = new circular_buffer(0); 20 | 21 | expect( cb.length ).toBe(0); 22 | expect( cb.available ).toBe(0); 23 | expect( cb.capacity ).toBe(0); 24 | 25 | expect( () => cb.first ).toThrow(); 26 | expect( () => cb.last ).toThrow(); 27 | 28 | expect( () => cb.push(1) ).toThrow(); 29 | expect( () => cb.pop() ).toThrow(); 30 | 31 | }); 32 | 33 | 34 | 35 | 36 | 37 | describe('[UNIT] capacity setter', () => { 38 | 39 | // start with an empty 3-cap 40 | 41 | const cb = new circular_buffer(3); 42 | 43 | expect( cb.length ).toBe(0); 44 | expect( cb.available ).toBe(3); 45 | expect( cb.capacity ).toBe(3); 46 | 47 | // up cap to 5; still empty 48 | 49 | cb.capacity = 5; 50 | 51 | expect( cb.length ).toBe(0); 52 | expect( cb.available ).toBe(5); 53 | expect( cb.capacity ).toBe(5); 54 | 55 | // fill it 56 | 57 | cb.push(1); 58 | cb.push(2); 59 | cb.push(3); 60 | cb.push(4); 61 | cb.push(5); 62 | 63 | expect( cb.toArray() ).toEqual( [1,2,3,4,5] ); 64 | 65 | // truncate by two; assert common sense 66 | 67 | cb.capacity = 3; 68 | 69 | expect( cb.toArray() ).toEqual( [1,2,3] ); 70 | 71 | expect( cb.length ).toBe(3); 72 | expect( cb.available ).toBe(0); 73 | expect( cb.capacity ).toBe(3); 74 | 75 | }); 76 | 77 | 78 | 79 | 80 | 81 | describe('[UNIT] length setter', () => { 82 | 83 | // start with an empty 3-cap 84 | 85 | const cb = new circular_buffer(3); 86 | 87 | expect( cb.length ).toBe(0); 88 | expect( cb.available ).toBe(3); 89 | expect( cb.capacity ).toBe(3); 90 | 91 | // up cap to 5; still empty 92 | 93 | cb.capacity = 5; 94 | 95 | expect( cb.length ).toBe(0); 96 | expect( cb.available ).toBe(5); 97 | expect( cb.capacity ).toBe(5); 98 | 99 | // fill it 100 | 101 | cb.push(1); 102 | cb.push(2); 103 | cb.push(3); 104 | cb.push(4); 105 | cb.push(5); 106 | 107 | expect( cb.toArray() ).toEqual( [1,2,3,4,5] ); 108 | 109 | // truncate by two using length; assert common sense 110 | 111 | cb.length = 3; 112 | 113 | expect( cb.toArray() ).toEqual( [1,2,3] ); 114 | 115 | expect( cb.length ).toBe(3); 116 | expect( cb.available ).toBe(2); 117 | expect( cb.capacity ).toBe(5); 118 | 119 | // test that the no-op catch for resize under capacity is reached 120 | cb.length = 4; 121 | expect( cb.toArray() ).toEqual( [1,2,3] ); 122 | 123 | // cannot re-length to over capacity 124 | expect( () => cb.length = 6 ).toThrow(); 125 | 126 | // cannot re-length to negative 127 | expect( () => cb.length = -1 ).toThrow(); 128 | 129 | // cannot re-length to fraction 130 | expect( () => cb.length = 2.5 ).toThrow(); 131 | 132 | }); 133 | 134 | 135 | 136 | 137 | 138 | describe('[UNIT] Circular buffer', () => { 139 | 140 | const unit = (size: number) => { 141 | 142 | describe(`at capacity ${size.toLocaleString()}`, () => { 143 | 144 | test('constructor', () => { 145 | expect(() => new circular_buffer(size)).not.toThrow(); 146 | }); 147 | 148 | test('lifecycle', () => { 149 | 150 | const cb = new circular_buffer(size); 151 | 152 | expect(cb.capacity).toBe(size); 153 | expect(cb.length).toBe(0); 154 | expect(cb.available).toBe(size); 155 | expect(() => cb.first).toThrow(); 156 | expect(() => cb.last).toThrow(); 157 | 158 | expect(cb.push(1)).toBe(1); 159 | expect(cb.capacity).toBe(size); 160 | expect(cb.length).toBe(1); 161 | expect(cb.available).toBe(size-1); 162 | expect(cb.first).toBe(1); 163 | expect(cb.last).toBe(1); 164 | 165 | expect(cb.pop()).toBe(1); 166 | expect(cb.capacity).toBe(size); 167 | expect(cb.length).toBe(0); 168 | expect(cb.available).toBe(size); 169 | expect(() => cb.first).toThrow(); 170 | expect(() => cb.last).toThrow(); 171 | 172 | }); 173 | 174 | test(`* push fill ${size.toLocaleString()}`, () => { 175 | 176 | const pf = new circular_buffer(size); 177 | for (let i=0; i { 186 | 187 | const pf = new circular_buffer(size); 188 | for (let k=0; k { 215 | 216 | const cb = circular_buffer.from([1,2,3]); 217 | 218 | expect( cb.pop() ).toBe(1); 219 | expect( cb.pop() ).toBe(2); 220 | expect( cb.pop() ).toBe(3); 221 | 222 | expect( () => cb.pop() ).toThrow(); 223 | 224 | }); 225 | 226 | 227 | 228 | test('[UNIT] functor from/2', () => { 229 | 230 | const cb = circular_buffer.from([1,2,3], i => i*10); 231 | 232 | expect( cb.pop() ).toBe(10); 233 | expect( cb.pop() ).toBe(20); 234 | expect( cb.pop() ).toBe(30); 235 | 236 | expect( () => cb.pop() ).toThrow(); 237 | 238 | }); 239 | 240 | 241 | 242 | test('[UNIT] first and last', () => { 243 | 244 | const cb = new circular_buffer(5); 245 | 246 | expect(cb.capacity).toBe(5); 247 | expect(cb.length).toBe(0); 248 | expect(cb.available).toBe(5); 249 | expect(() => cb.first).toThrow(); 250 | expect(() => cb.last).toThrow(); 251 | 252 | expect(cb.push(1)).toBe(1); 253 | expect(cb.capacity).toBe(5); 254 | expect(cb.length).toBe(1); 255 | expect(cb.available).toBe(5-1); 256 | expect(cb.first).toBe(1); 257 | expect(cb.last).toBe(1); 258 | expect(cb.push(2)).toBe(2); 259 | expect(cb.first).toBe(1); 260 | expect(cb.last).toBe(2); 261 | 262 | expect(cb.pop()).toBe(1); 263 | expect(cb.first).toBe(2); 264 | expect(cb.last).toBe(2); 265 | 266 | expect(cb.pop()).toBe(2); 267 | expect(cb.capacity).toBe(5); 268 | expect(cb.length).toBe(0); 269 | expect(cb.available).toBe(5); 270 | expect(() => cb.first).toThrow(); 271 | expect(() => cb.last).toThrow(); 272 | 273 | }); 274 | 275 | 276 | 277 | test('[UNIT] push/1', () => { 278 | 279 | // declare a three item cb 280 | const pu_ir = new circular_buffer(3); 281 | 282 | // can accept three items 283 | pu_ir.push(1); 284 | pu_ir.push(2); 285 | pu_ir.push(3); 286 | 287 | // cannot accept a fourth, out of space 288 | expect( () => pu_ir.push(4) ).toThrow('Cannot push, structure is full to capacity'); 289 | 290 | // pop and get 1 291 | expect( pu_ir.pop() ).toBe(1); 292 | 293 | // now can accept a fourth; also pop and get 2, etc. 294 | pu_ir.push(4); 295 | expect( pu_ir.pop() ).toBe(2); 296 | 297 | pu_ir.push(5); 298 | expect( pu_ir.pop() ).toBe(3); 299 | 300 | pu_ir.push(6); 301 | expect( pu_ir.pop() ).toBe(4); 302 | 303 | // we've now full cycled in twice 304 | pu_ir.push(7); 305 | expect( pu_ir.pop() ).toBe(5); 306 | 307 | pu_ir.push(8); 308 | expect( pu_ir.pop() ).toBe(6); 309 | 310 | // we've now full cycled out twice; done 311 | pu_ir.push(9); 312 | expect( pu_ir.pop() ).toBe(7); 313 | 314 | }); 315 | 316 | 317 | 318 | test('[UNIT] shove/1', () => { 319 | 320 | // declare a three item cb 321 | const pu_ir = new circular_buffer(3); 322 | 323 | // can accept three items 324 | pu_ir.shove(1); 325 | pu_ir.shove(2); 326 | pu_ir.shove(3); 327 | 328 | expect( pu_ir.shove(4) ).toBe(1); 329 | expect( pu_ir.shove(5) ).toBe(2); 330 | expect( pu_ir.shove(6) ).toBe(3); 331 | expect( pu_ir.shove(7) ).toBe(4); 332 | 333 | }); 334 | 335 | 336 | 337 | test('[UNIT] shove/1 on empty', () => { 338 | 339 | // declare a zero item cb 340 | const pu_ir = new circular_buffer(0); 341 | 342 | // cannot accept a shove, no space 343 | expect( () => pu_ir.shove(4) ).toThrow(); 344 | 345 | }); 346 | 347 | 348 | 349 | test('[UNIT] at/1', () => { 350 | 351 | const at_ir = new circular_buffer(3); 352 | 353 | // can accept and check a first item 354 | at_ir.push(1); 355 | expect( at_ir.at(0) ).toBe(1); 356 | 357 | // can accept a second item; both first and second work 358 | at_ir.push(2); 359 | expect( at_ir.at(0) ).toBe(1); 360 | expect( at_ir.at(1) ).toBe(2); 361 | 362 | // popping does what's expected 363 | at_ir.pop(); 364 | expect( at_ir.at(0) ).toBe(2); 365 | 366 | // pushing still does what's expected after a pop 367 | at_ir.push(3); 368 | expect( at_ir.at(0) ).toBe(2); 369 | expect( at_ir.at(1) ).toBe(3); 370 | 371 | // double popping does what's expected 372 | at_ir.pop(); 373 | at_ir.pop(); 374 | expect( () => at_ir.at(0) ).toThrow(); 375 | 376 | // pushing works after an empty 377 | at_ir.push(4); 378 | expect( at_ir.at(0) ).toBe(4); 379 | 380 | }); 381 | 382 | 383 | 384 | 385 | 386 | test('[UNIT] pos/1', () => { 387 | 388 | const at_ir = new circular_buffer(3); 389 | 390 | // can accept and check a first item 391 | at_ir.push(1); 392 | expect( at_ir.pos(0) ).toBe(1); 393 | 394 | // can accept a second item; both first and second work 395 | at_ir.push(2); 396 | expect( at_ir.pos(0) ).toBe(1); 397 | expect( at_ir.pos(1) ).toBe(2); 398 | 399 | // after popping, pos(0) no longer exists; pos(1) should still be 2 400 | at_ir.pop(); 401 | expect( () => at_ir.pos(0) ).toThrow(); 402 | expect( at_ir.pos(1) ).toBe(2); 403 | 404 | // pushing still does what's expected after a pop 405 | at_ir.push(3); 406 | expect( at_ir.pos(1) ).toBe(2); 407 | expect( at_ir.pos(2) ).toBe(3); 408 | 409 | // double popping does what's expected 410 | at_ir.pop(); 411 | at_ir.pop(); 412 | expect( () => at_ir.at(0) ).toThrow(); 413 | expect( () => at_ir.at(2) ).toThrow(); 414 | 415 | // pushing works after an empty 416 | at_ir.push(4); 417 | expect( at_ir.pos(3) ).toBe(4); 418 | 419 | }); 420 | 421 | 422 | 423 | 424 | 425 | test('[UNIT] resize/1', () => { 426 | 427 | const orig = circular_buffer.from([1,2,3,4,5]); 428 | expect(orig.toArray()).toStrictEqual([1,2,3,4,5]); 429 | 430 | orig.resize(4); 431 | expect(orig.toArray()).toStrictEqual([1,2,3,4]); 432 | 433 | orig.resize(6); 434 | expect(orig.toArray()).toStrictEqual([1,2,3,4]); 435 | 436 | orig.push(-5); 437 | orig.push(-6); 438 | expect(orig.toArray()).toStrictEqual([1,2,3,4,-5,-6]); 439 | 440 | }); 441 | 442 | 443 | 444 | 445 | 446 | test('[UNIT] resize prefers end/1', () => { 447 | 448 | const orig = circular_buffer.from([1,2,3,4,5]); 449 | expect(orig.toArray()).toStrictEqual([1,2,3,4,5]); 450 | 451 | orig.resize(4, true); 452 | expect(orig.toArray()).toStrictEqual([2,3,4,5]); 453 | 454 | orig.resize(6, true); 455 | expect(orig.toArray()).toStrictEqual([2,3,4,5]); 456 | 457 | orig.push(-6); 458 | orig.push(-7); 459 | expect(orig.toArray()).toStrictEqual([2,3,4,5,-6,-7]); 460 | 461 | orig.resize(6, true); 462 | expect(orig.toArray()).toStrictEqual([2,3,4,5,-6,-7]); 463 | orig.resize(4, true); 464 | expect(orig.toArray()).toStrictEqual([4,5,-6,-7]); 465 | orig.resize(6, true); 466 | expect(orig.toArray()).toStrictEqual([4,5,-6,-7]); 467 | orig.resize(4, true); 468 | expect(orig.toArray()).toStrictEqual([4,5,-6,-7]); 469 | 470 | }); 471 | 472 | 473 | 474 | 475 | 476 | test('[UNIT] fill/5', () => { 477 | 478 | // declare a five item cb 479 | const filler = new circular_buffer(5); 480 | 481 | // can accept five items 482 | expect(filler.fill(1)).toEqual([1,1,1,1,1]); 483 | 484 | // can overwrite five items 485 | expect(filler.fill(2)).toEqual([2,2,2,2,2]); 486 | 487 | }); 488 | 489 | 490 | 491 | 492 | 493 | test('[UNIT] indexOf/1-2', () => { 494 | 495 | const finder = new circular_buffer(5); 496 | [1,2,1,2,1].forEach(i => finder.push(i)); 497 | 498 | expect(finder.indexOf(1)).toEqual(0); 499 | expect(finder.indexOf(2)).toEqual(1); 500 | 501 | expect(finder.indexOf(1,0)).toEqual(0); 502 | expect(finder.indexOf(2,0)).toEqual(1); 503 | 504 | expect(finder.indexOf(1,1)).toEqual(2); 505 | expect(finder.indexOf(2,1)).toEqual(1); 506 | 507 | expect(finder.indexOf(1,2)).toEqual(2); 508 | expect(finder.indexOf(2,2)).toEqual(3); 509 | 510 | expect(finder.indexOf(1,4)).toEqual(4); 511 | expect(finder.indexOf(2,4)).toEqual(-1); 512 | 513 | expect(finder.indexOf(1,6)).toEqual(-1); 514 | expect(finder.indexOf(2,6)).toEqual(-1); 515 | 516 | expect(finder.indexOf(3) ).toEqual(-1); 517 | expect(finder.indexOf(3,0)).toEqual(-1); 518 | expect(finder.indexOf(3,6)).toEqual(-1); 519 | 520 | }); 521 | 522 | 523 | 524 | 525 | 526 | test('[UNIT] clear/0', () => { 527 | 528 | // declare a five item cb 529 | const filler = new circular_buffer(5); 530 | 531 | filler.push(1); 532 | filler.push(2); 533 | filler.push(3); 534 | filler.push(4); 535 | filler.push(5); 536 | 537 | const was = filler.clear(); 538 | 539 | expect(filler.length).toEqual(0); 540 | expect(was).toStrictEqual([1,2,3,4,5]); 541 | 542 | }); 543 | 544 | 545 | 546 | 547 | 548 | test('[UNIT] fill/full/5', () => { 549 | // declare a five item cb 550 | const filler = new circular_buffer(5); 551 | 552 | filler.push(1); 553 | filler.push(1); 554 | filler.push(1); 555 | filler.push(1); 556 | filler.push(1); 557 | /* we're full */ 558 | 559 | expect(filler.at(0)).toEqual(1); 560 | 561 | expect(filler.at(4)).toEqual(1); 562 | 563 | filler.fill(2); 564 | /* fill() will overwrite */ 565 | 566 | expect(filler.at(0)).toEqual(2); 567 | 568 | expect(filler.at(4)).toEqual(2); 569 | /* archaic, but tests ok */ 570 | 571 | }); 572 | 573 | 574 | 575 | 576 | 577 | test('[UNIT] fill/partial/3', () => { 578 | // declare a three item cb 579 | const filler = new circular_buffer(3); 580 | 581 | filler.push(1); 582 | /*we have one item present*/ 583 | 584 | expect(filler.at(0)).toEqual(1); 585 | 586 | filler.fill(2); 587 | /*fill() will overwrite*/ 588 | 589 | expect(filler.at(0)).toEqual(2); 590 | expect(filler.at(1)).toEqual(2); 591 | expect(filler.at(2)).toEqual(2); 592 | }); 593 | 594 | 595 | 596 | 597 | 598 | test('[UNIT] pop/0', () => { 599 | 600 | const popper = new circular_buffer(3); 601 | 602 | // can't pop from a new/empty cb 603 | expect( () => popper.pop() ).toThrow(); 604 | 605 | // can push once; can only pop once, 2nd will throw 606 | popper.push(1); 607 | expect( popper.pop() ).toBe(1); 608 | expect( () => popper.pop() ).toThrow(); 609 | 610 | // can push again; can only pop once, 2nd will throw 611 | popper.push(2); 612 | expect( popper.pop() ).toBe(2); 613 | expect( () => popper.pop() ).toThrow(); 614 | 615 | // can double push; can pop twice, 3rd will throw 616 | popper.push(3); 617 | popper.push(4); 618 | expect( popper.pop() ).toBe(3); 619 | expect( popper.pop() ).toBe(4); 620 | expect( () => popper.pop() ).toThrow(); 621 | 622 | // can triple push; fourth will fail; can pop thrice, 4th will throw as expected 623 | popper.push(5); 624 | popper.push(6); 625 | popper.push(7); 626 | expect( () => popper.push(8) ).toThrow('Cannot push, structure is full to capacity'); 627 | expect( popper.pop() ).toBe(5); 628 | expect( popper.pop() ).toBe(6); 629 | expect( popper.pop() ).toBe(7); 630 | expect( () => popper.pop() ).toThrow(); 631 | 632 | }); 633 | 634 | 635 | 636 | 637 | 638 | test('[UNIT] toArray/0', () => { 639 | 640 | const popper = new circular_buffer(3); 641 | 642 | expect( popper.toArray() ).toStrictEqual( [] ); 643 | 644 | popper.push(1); 645 | expect( popper.toArray() ).toStrictEqual( [1] ); 646 | 647 | popper.push(2); 648 | expect( popper.toArray() ).toStrictEqual( [1,2] ); 649 | 650 | popper.push(3); 651 | expect( popper.toArray() ).toStrictEqual( [1,2,3] ); 652 | 653 | popper.pop(); 654 | popper.push(4); 655 | expect( popper.toArray() ).toStrictEqual( [2,3,4] ); 656 | 657 | popper.pop(); 658 | popper.push(5); 659 | expect( popper.toArray() ).toStrictEqual( [3,4,5] ); 660 | 661 | popper.pop(); 662 | popper.push(6); 663 | expect( popper.toArray() ).toStrictEqual( [4,5,6] ); 664 | 665 | popper.pop(); 666 | popper.push(7); 667 | expect( popper.toArray() ).toStrictEqual( [5,6,7] ); 668 | 669 | }); 670 | 671 | 672 | 673 | 674 | 675 | test('[UNIT] available getter', () => { 676 | 677 | const popper = new circular_buffer(3); 678 | 679 | expect( popper.available ).toBe(3); 680 | 681 | popper.push(1); 682 | expect( popper.available ).toBe(2); 683 | 684 | popper.push(2); 685 | expect( popper.available ).toBe(1); 686 | 687 | popper.push(3); 688 | expect( popper.available ).toBe(0); 689 | 690 | popper.pop(); 691 | expect( popper.available ).toBe(1); 692 | 693 | }); 694 | 695 | 696 | 697 | 698 | 699 | test('[UNIT] isEmpty getter', () => { 700 | 701 | const popper = new circular_buffer(3); 702 | expect( popper.isEmpty ).toBe(true); 703 | 704 | popper.push(1); 705 | expect( popper.isEmpty ).toBe(false); 706 | 707 | popper.pop(); 708 | expect( popper.isEmpty ).toBe(true); 709 | 710 | }); 711 | 712 | 713 | 714 | 715 | 716 | test('[UNIT] isFull getter', () => { 717 | 718 | const popper = new circular_buffer(1); 719 | expect( popper.isFull ).toBe(false); 720 | 721 | popper.push(1); 722 | expect( popper.isFull ).toBe(true); 723 | 724 | popper.pop(); 725 | expect( popper.isFull ).toBe(false); 726 | 727 | }); 728 | 729 | 730 | 731 | 732 | 733 | test('[UNIT] find/1', () => { 734 | 735 | const dogs = ['yorkie', 'beagle', 'poodle'], 736 | data = ['siamese', 'beagle', 'cockatoo'], 737 | nums = ['1', '2', '3'], 738 | is_dog: TraversalFunctor = (animal: string) => dogs.includes(animal), 739 | cb = new circular_buffer(3), 740 | cb_none = new circular_buffer(3); 741 | 742 | data.forEach( animal => cb.push( animal ) ); 743 | nums.forEach( number => cb_none.push( number ) ); 744 | 745 | const old = cb.toArray(); 746 | 747 | expect( cb.find( is_dog ) ).toBe( 'beagle' ); 748 | expect( cb_none.find( is_dog ) ).toBe( undefined ); 749 | 750 | expect( cb.toArray() ).toStrictEqual(old); 751 | 752 | }); 753 | 754 | 755 | 756 | 757 | 758 | test('[UNIT] every/1 immutable', () => { 759 | 760 | const cb = circular_buffer.from([3,2,1]), 761 | old = cb.toArray(); 762 | 763 | expect( cb.every( i => typeof i === 'number') ).toBe(true); 764 | expect( cb.every( i => ((i % 2) === 0) ) ).toBe(false); 765 | 766 | expect( cb.toArray() ).toStrictEqual(old); 767 | 768 | }); 769 | 770 | 771 | 772 | 773 | 774 | test('[UNIT] every/1 mutative', () => { 775 | 776 | const cb = circular_buffer.from([3,2,1]), 777 | old = cb.toArray(); 778 | 779 | expect( cb.every( i => i += 1 ) ).toBe(true); 780 | expect( cb.every( i => i -= 2 ) ).toBe(false); // first index hits zero 781 | 782 | expect( cb.toArray() ).toStrictEqual(old); 783 | 784 | }); 785 | 786 | 787 | 788 | 789 | 790 | test('[UNIT] some/1', () => { 791 | 792 | const cb = circular_buffer.from([3,2,1]), 793 | old = cb.toArray(); 794 | 795 | expect( cb.some( i => typeof i === 'number') ).toBe(true); 796 | expect( cb.some( i => ((i % 2) === 0) ) ).toBe(true); 797 | expect( cb.some( i => ((i % 2) === 29) ) ).toBe(false); 798 | 799 | expect( cb.toArray() ).toStrictEqual(old); 800 | 801 | }); 802 | 803 | 804 | 805 | 806 | 807 | test('[UNIT] Reverse', () => { 808 | 809 | const cb = circular_buffer.from([3,2,1]); 810 | cb.reverse(); 811 | 812 | expect( cb.pop() ).toBe(1); 813 | expect( cb.pop() ).toBe(2); 814 | expect( cb.pop() ).toBe(3); 815 | 816 | }); 817 | 818 | 819 | 820 | 821 | 822 | describe('[UNIT] Error cases', () => { 823 | 824 | const overflow = (size: number) => { 825 | 826 | describe('Overflow', () => { 827 | test(`* push overfill on precisely ${size.toLocaleString()}`, () => { 828 | 829 | const pf = new circular_buffer(size); 830 | 831 | expect(() => { for (let i=0; i pf.push(size)).toThrow(); 833 | 834 | }); 835 | }); 836 | 837 | } 838 | 839 | [1, 2, 3, 10, 1_000].map(overflow); 840 | 841 | describe('Bad constructors', () => { 842 | test('Negative-sized', () => { expect( () => new circular_buffer(-1) ).toThrow(); }) 843 | test('Fraction-sized', () => { expect( () => new circular_buffer(1.5) ).toThrow(); }) 844 | test('Infinity-sized', () => { expect( () => new circular_buffer(Number.POSITIVE_INFINITY) ).toThrow(); }) 845 | test('NInfinity-sized', () => { expect( () => new circular_buffer(Number.NEGATIVE_INFINITY) ).toThrow(); }) 846 | test('NaN-sized', () => { expect( () => new circular_buffer(NaN) ).toThrow(); }) 847 | }); 848 | 849 | describe('at/1 out-of-range', () => { 850 | 851 | const at_ooo = new circular_buffer(3); 852 | at_ooo.push(1); 853 | 854 | test('Above capacity', () => { expect( () => at_ooo.at(4) ).toThrow(); }) 855 | test('Above length', () => { expect( () => at_ooo.at(2) ).toThrow(); }) 856 | test('Below zero', () => { expect( () => at_ooo.at(-1) ).toThrow(); }) 857 | test('Fractional', () => { expect( () => at_ooo.at(1.5) ).toThrow(); }) 858 | 859 | at_ooo.push(2); 860 | at_ooo.pop(); 861 | 862 | }); 863 | 864 | }); 865 | -------------------------------------------------------------------------------- /docs/docs/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | circular_buffer_js 7 | 8 | 9 | 10 | 11 | 12 | 13 |
14 |
15 |
16 |
17 | 28 |
29 |
30 | Options 31 |
32 |
33 | All 34 |
    35 |
  • Public
  • 36 |
  • Public/Protected
  • 37 |
  • All
  • 38 |
39 |
40 | 41 | 42 | 43 | 44 |
45 |
46 | Menu 47 |
48 |
49 |
50 |
51 |
52 |
53 |

circular_buffer_js

54 |
55 |
56 |
57 |
58 |
59 |
60 |
61 | 62 |

circular_buffer_js

63 |
64 |

MIT License 65 | NPM Version 66 | Coveralls 67 | Issues 68 | GitHub contributors

69 |
npm install --save-dev circular_buffer_js
 70 | 
71 | 78 |

Typescript implementation of a circular buffer, and JS compiles to a es6 79 | module minified, es6 commonjs minified, es6 iife minified, and es6 iife full.

80 |
    81 |
  1. Well tested.
      82 |
    • 100% coverage, 100% property coverage.
    • 83 |
    84 |
  2. 85 |
  3. Tiny.
      86 |
    • The es6 minified module build is currently 1.4k.
    • 87 |
    88 |
  4. 89 |
  5. Dependency-free.
      90 |
    • Only dev-time deps like typescript are involved.
    • 91 |
    92 |
  6. 93 |
94 |



95 | 96 |

API

97 |
98 |

You should consider viewing the 99 | real documentation, 100 | but:

101 |
// yields a buffer of fixed size `size`
102 | const cb  = new circular_buffer(size),
103 |       cb2 = circular_buffer.from([1,2,3]);
104 | 
105 | cb.push(item);       // inserts `item` at end of `cb`, then returns `item`
106 | cb.shove(item);      // inserts `item` at end of `cb`; remove if needed, returns removed
107 | cb.pop();            // removes and returns first element
108 | cb.at(location);     // shows the element at 0-indexed offset `location`
109 | cb.pos(location);    // shows the element at run-indexed offset `location`
110 | cb.offset();         // shows the delta from 0-indexed to head
111 | cb.indexOf(item);    // returns the index of the first `item`, or -1
112 | cb.find(pred);       // return the the first match or undefined
113 | cb.every(pred);      // tests if every queue element satisfies the predicate
114 | cb.some(pred);       // tests if at least one element satisfies the predicate
115 | cb.fill(item);       // maxes `length` and sets every element to `item`
116 | cb.clear();          // empties the container
117 | cb.reverse();        // reverses the container
118 | cb.resize(size,pE);  // change to new size, truncating if required; pE to prefer end
119 | cb.toArray();        // return an array of the current contents of the queue
120 | 
121 | cb.first;            // returns the first value in the queue; throws when empty
122 | cb.last;             // returns the last value in the queue; throws when empty
123 | cb.isFull;           // returns `true` if no space left; `false` otherwise
124 | cb.isEmpty;          // returns `true` if no space remains; `false` otherwise
125 | cb.available;        // returns the number of spaces remaining currently
126 | cb.capacity;         // returns the total `size` allocated
127 | cb.length;           // returns the amount of space currently used
128 | 
129 |



130 | 131 |

What is this?

132 |
133 |

This is a circular buffer (or cycle buffer, ring queue, etc.) It was written 134 | because a library I wanted to use had a native buggy implementation, so I 135 | provided something more trustworthy.

136 |

A circular buffer is a fixed size buffer that allows you to push and pop 137 | forever, as a first in first out queue-like structure. Circular buffers are 138 | more efficient than queues, but can overflow.

139 |
140 | 141 |

Basic usage

142 |
143 |
import { circular_buffer } from 'circular_buffer_js';
144 | 
145 | const cb = new circular_buffer(3);  // [ , , ]
146 | 
147 | cb.push(1); // ok: [1, , ]
148 | cb.push(2); // ok: [1,2, ]
149 | cb.push(3); // ok: [1,2,3]
150 | 
151 | cb.at(0); // 1
152 | cb.first; // 1
153 | cb.last;  // 3
154 | 
155 | cb.push(4); // throws - full! ; [1,2,3]
156 | 
157 | cb.pop(); // 1: [2,3, ]
158 | cb.at(0); // 2: [2,3, ]
159 | 
160 | cb.push(4); // ok: [2,3,4]
161 | cb.push(5); // throws - full! ; [2,3,4]
162 | 
163 | cb.pop(); // 2: [3,4, ]
164 | cb.pop(); // 3: [4, , ]
165 | cb.pop(); // 4: [ , , ]
166 | 
167 | cb.pop(); // throws - empty! ; [ , , ]
168 | 
169 |
170 | 171 |

Typescript

172 |
173 |

It's typescript, so you can also

174 |
import { circular_buffer } from 'circular_buffer_js';
175 | const cb = new circular_buffer<number>(3);
176 | 
177 |
178 | 179 |

Node CommonJS

180 |
181 |

And there's a CommonJS build, so you can

182 |
const cbuf            = require('circular_buffer_js'),
183 |       circular_buffer = new cbuf.circular_buffer;
184 | 
185 |
186 | 187 |

Browser <script>

188 |
189 |

There're also two iife builds - both regular and minified - so that you can 190 | use this in older browsers, or from CDN.

191 |
<script defer type="text/javascript" src="circular_buffer_js.min.js"></script>
192 | <script defer type="text/javascript">
193 | 
194 |   window.onload = () => {
195 | 
196 |     console.log(
197 |       `Using circular buffer version ${circular_buffer.version}`
198 |     );
199 | 
200 |                       // package      // class
201 |     const mybuf = new circular_buffer.circular_buffer(5);
202 | 
203 |   };
204 | 
205 | </script>
206 | 
207 |



208 | 209 |

Alternatives

210 |
211 |

If this doesn't meet your needs, please try:

212 | 225 |
226 |
227 | 246 |
247 |
248 |
249 |
250 |

Legend

251 |
252 |
    253 |
  • Variable
  • 254 |
  • Type alias with type parameter
  • 255 |
256 |
    257 |
  • Class with type parameter
  • 258 |
259 |
260 |
261 |
262 |
263 |

Generated using TypeDoc

264 |
265 |
266 | 267 | 268 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### Changelog 2 | 3 | All notable changes to this project will be documented in this file. Dates are displayed in UTC. 4 | 5 | #### [Unreleased](https://github.com/StoneCypher/circular_buffer_js/compare/v1.8.2...HEAD) 6 | 7 | - 1.9.0 implement shove/1, fixes #55 [`#55`](https://github.com/StoneCypher/circular_buffer_js/issues/55) 8 | - Add release process. Re-achieve 100% double-coverage [`bb822c2`](https://github.com/StoneCypher/circular_buffer_js/commit/bb822c298fed01c8284188c65dfaa479c5dc96b9) 9 | - Minor improvements to the release process [`c789600`](https://github.com/StoneCypher/circular_buffer_js/commit/c789600d2edd17dbc400e5b3d4a4b3f9f93c2ba2) 10 | 11 | #### [v1.8.2](https://github.com/StoneCypher/circular_buffer_js/compare/v1.8.1...v1.8.2) 12 | 13 | > 26 February 2022 14 | 15 | #### [v1.8.1](https://github.com/StoneCypher/circular_buffer_js/compare/v1.8.0...v1.8.1) 16 | 17 | > 26 February 2022 18 | 19 | #### [v1.8.0](https://github.com/StoneCypher/circular_buffer_js/compare/v1.7.1...v1.8.0) 20 | 21 | > 26 February 2022 22 | 23 | - Add offset and pos [`#54`](https://github.com/StoneCypher/circular_buffer_js/pull/54) 24 | - stoch test in for .offset, fixes #52 .offset implemented, fixes #53 .offset tested [`#52`](https://github.com/StoneCypher/circular_buffer_js/issues/52) [`#53`](https://github.com/StoneCypher/circular_buffer_js/issues/53) 25 | - stoch test in for .pos, fixes #50 .pos implemented, fixes #51 .pos tested [`#50`](https://github.com/StoneCypher/circular_buffer_js/issues/50) [`#51`](https://github.com/StoneCypher/circular_buffer_js/issues/51) 26 | - Add tests for set .capacity, fixes #49 [`#49`](https://github.com/StoneCypher/circular_buffer_js/issues/49) 27 | - draft implementations [`0dd5d0f`](https://github.com/StoneCypher/circular_buffer_js/commit/0dd5d0f1c73b7752f22367f62e771978625e79c3) 28 | - unit tests [`3d2637f`](https://github.com/StoneCypher/circular_buffer_js/commit/3d2637f7548883daccb26328c698d3bace86306e) 29 | - SetLength test was wrong: it asserted that length matched what was set, which isn't true when setting a higher size [`60c7819`](https://github.com/StoneCypher/circular_buffer_js/commit/60c781994047822fb263c05764f7d7aa19791bbb) 30 | - SetLength is either a broken test or has found a bug [`172d926`](https://github.com/StoneCypher/circular_buffer_js/commit/172d926e6888bf02b3818971f5441e8667cef6f1) 31 | 32 | #### [v1.7.1](https://github.com/StoneCypher/circular_buffer_js/compare/v1.7.0...v1.7.1) 33 | 34 | > 29 July 2021 35 | 36 | - improve docblock [`37706f8`](https://github.com/StoneCypher/circular_buffer_js/commit/37706f8c53f9027d2024446f0aa8ba971a816e13) 37 | 38 | #### [v1.7.0](https://github.com/StoneCypher/circular_buffer_js/compare/v1.6.0...v1.7.0) 39 | 40 | > 26 July 2021 41 | 42 | - resize/1, fixes #3 [`#3`](https://github.com/StoneCypher/circular_buffer_js/issues/3) 43 | 44 | #### [v1.6.0](https://github.com/StoneCypher/circular_buffer_js/compare/v1.5.2...v1.6.0) 45 | 46 | > 26 July 2021 47 | 48 | - indexOf/1-2 [`d27166b`](https://github.com/StoneCypher/circular_buffer_js/commit/d27166b38f9a50d21f261057b0b1e8b789300d9a) 49 | 50 | #### [v1.5.2](https://github.com/StoneCypher/circular_buffer_js/compare/v1.5.1...v1.5.2) 51 | 52 | > 26 July 2021 53 | 54 | - better find test [`facf650`](https://github.com/StoneCypher/circular_buffer_js/commit/facf650b288660be70a441ab6c1debf529ad857b) 55 | 56 | #### [v1.5.1](https://github.com/StoneCypher/circular_buffer_js/compare/v1.5.0...v1.5.1) 57 | 58 | > 26 July 2021 59 | 60 | - readme mistake about find [`5acae2b`](https://github.com/StoneCypher/circular_buffer_js/commit/5acae2ba2f19060e58a5ad8802a7cd663a4d572b) 61 | 62 | #### [v1.5.0](https://github.com/StoneCypher/circular_buffer_js/compare/v1.3.0...v1.5.0) 63 | 64 | > 26 July 2021 65 | 66 | - find/1-2, fixes #15 [`#15`](https://github.com/StoneCypher/circular_buffer_js/issues/15) 67 | - some/1, fixes #46 [`#46`](https://github.com/StoneCypher/circular_buffer_js/issues/46) 68 | - Close a fairly gross line-left-behind bug causing unexpected reversals [`c18c0e2`](https://github.com/StoneCypher/circular_buffer_js/commit/c18c0e2c2513b793db29f41b515a79072abd8bee) 69 | 70 | #### [v1.3.0](https://github.com/StoneCypher/circular_buffer_js/compare/v1.2.0...v1.3.0) 71 | 72 | > 25 July 2021 73 | 74 | - every/1-2, fixes #45 [`#45`](https://github.com/StoneCypher/circular_buffer_js/issues/45) 75 | 76 | #### [v1.2.0](https://github.com/StoneCypher/circular_buffer_js/compare/v1.1.0...v1.2.0) 77 | 78 | > 25 July 2021 79 | 80 | - reverse/0 [`765c58b`](https://github.com/StoneCypher/circular_buffer_js/commit/765c58b809df6aa481de8601f3b3df492d6e7263) 81 | 82 | #### [v1.1.0](https://github.com/StoneCypher/circular_buffer_js/compare/v1.0.1...v1.1.0) 83 | 84 | > 25 July 2021 85 | 86 | - from/1-3, fixes #9 [`#9`](https://github.com/StoneCypher/circular_buffer_js/issues/9) 87 | 88 | #### [v1.0.1](https://github.com/StoneCypher/circular_buffer_js/compare/v1.0.0...v1.0.1) 89 | 90 | > 19 July 2021 91 | 92 | - Fix badges [`7e19481`](https://github.com/StoneCypher/circular_buffer_js/commit/7e19481a6671949a2ee8a1d768e2e02b5eb6cd37) 93 | 94 | ### [v1.0.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.25.0...v1.0.0) 95 | 96 | > 19 July 2021 97 | 98 | - Declare 1.0.0 [`4c2c630`](https://github.com/StoneCypher/circular_buffer_js/commit/4c2c6305b291afc3b2c9940b665e7a974a7baadd) 99 | 100 | #### [v0.25.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.24.1...v0.25.0) 101 | 102 | > 19 July 2021 103 | 104 | - tagging done [`40ab035`](https://github.com/StoneCypher/circular_buffer_js/commit/40ab0358768a4a9fa48129f55f7f9c220ae3ddc3) 105 | - prep for 1.0.0 underway, gonna go tag a lot of things [`c5aee62`](https://github.com/StoneCypher/circular_buffer_js/commit/c5aee629ddf93ba2bba4b03fffdfc1c2b80eee11) 106 | 107 | #### [v0.24.1](https://github.com/StoneCypher/circular_buffer_js/compare/v0.24.0...v0.24.1) 108 | 109 | > 19 July 2021 110 | 111 | - we do need a few steenking badges [`6f3cbe0`](https://github.com/StoneCypher/circular_buffer_js/commit/6f3cbe023bd75c20bdb5bff0a19b1af356c66c0a) 112 | 113 | #### [v0.24.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.23.1...v0.24.0) 114 | 115 | > 19 July 2021 116 | 117 | - Create landing page, fixes #32 [`#32`](https://github.com/StoneCypher/circular_buffer_js/issues/32) 118 | 119 | #### [v0.23.1](https://github.com/StoneCypher/circular_buffer_js/compare/v0.23.0...v0.23.1) 120 | 121 | > 19 July 2021 122 | 123 | - lcov path [`ea41e13`](https://github.com/StoneCypher/circular_buffer_js/commit/ea41e13714df743274c3ef1a961a65ec512c3c1d) 124 | 125 | #### [v0.23.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.22.0...v0.23.0) 126 | 127 | > 19 July 2021 128 | 129 | - eslint [`#44`](https://github.com/StoneCypher/circular_buffer_js/pull/44) 130 | - Setting up coveralls [`20d395a`](https://github.com/StoneCypher/circular_buffer_js/commit/20d395a0390afc15873ac15a731055c58397a4e3) 131 | - Setting up coveralls with correct package number [`52de726`](https://github.com/StoneCypher/circular_buffer_js/commit/52de7264afe5a760ec89e8e85812294854a0b349) 132 | 133 | #### [v0.22.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.21.3...v0.22.0) 134 | 135 | > 19 July 2021 136 | 137 | - Write the changelog to disk [`#43`](https://github.com/StoneCypher/circular_buffer_js/pull/43) 138 | - Add changelog [`#42`](https://github.com/StoneCypher/circular_buffer_js/pull/42) 139 | - eslint [`1caa887`](https://github.com/StoneCypher/circular_buffer_js/commit/1caa88768b99bddf4615e19aad129fdeae334fc4) 140 | 141 | #### [v0.21.3](https://github.com/StoneCypher/circular_buffer_js/compare/v0.21.2...v0.21.3) 142 | 143 | > 19 July 2021 144 | 145 | - Write the changelog to disk [`2d035c2`](https://github.com/StoneCypher/circular_buffer_js/commit/2d035c24cfc9db52656c9294be86ca9645e043fa) 146 | - Okay changelogs are semi-automated now [`1e54733`](https://github.com/StoneCypher/circular_buffer_js/commit/1e547331e509d22d184225418d107bf5425805cf) 147 | - Okay, NPM standard doesn't work here [`1ad912b`](https://github.com/StoneCypher/circular_buffer_js/commit/1ad912b2bc7c5d6c674a85e4b2cb5dd761bf9251) 148 | 149 | #### [v0.21.2](https://github.com/StoneCypher/circular_buffer_js/compare/v0.21.1...v0.21.2) 150 | 151 | > 19 July 2021 152 | 153 | - Automating the build system [`52eb471`](https://github.com/StoneCypher/circular_buffer_js/commit/52eb4712e7c01ea5e0be9c4c1e78c74b2c2d8066) 154 | - Automating the bump system [`2923f64`](https://github.com/StoneCypher/circular_buffer_js/commit/2923f64969fcbdfec1add6970e2c06888bdec57b) 155 | 156 | #### [v0.21.1](https://github.com/StoneCypher/circular_buffer_js/compare/v0.20.1...v0.21.1) 157 | 158 | > 19 July 2021 159 | 160 | - Setting up a changelog tagging toolchain [`456e34f`](https://github.com/StoneCypher/circular_buffer_js/commit/456e34fca7bfa876009c497a917530221b1ad322) 161 | 162 | #### [v0.20.1](https://github.com/StoneCypher/circular_buffer_js/compare/v0.19.0...v0.20.1) 163 | 164 | > 19 July 2021 165 | 166 | - clear/0 now returns toArray/0, fixes #39 [`#39`](https://github.com/StoneCypher/circular_buffer_js/issues/39) 167 | - toArray/0, simpler .pop/0, fixes #8 [`#8`](https://github.com/StoneCypher/circular_buffer_js/issues/8) 168 | 169 | #### [v0.19.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.18.1...v0.19.0) 170 | 171 | > 19 July 2021 172 | 173 | - There, wrote some basic documentation, fixes #41 [`#41`](https://github.com/StoneCypher/circular_buffer_js/issues/41) 174 | 175 | #### [v0.18.1](https://github.com/StoneCypher/circular_buffer_js/compare/v0.18.0...v0.18.1) 176 | 177 | > 19 July 2021 178 | 179 | - we're putting the docs in the repo now [`85bf3f4`](https://github.com/StoneCypher/circular_buffer_js/commit/85bf3f4ba1176f13a17e3eddaec2b4c011b35d8e) 180 | - rebuild for putting the docs in the repo [`a35ae5b`](https://github.com/StoneCypher/circular_buffer_js/commit/a35ae5bd38bfa2353e1622cda56209dc0c93b999) 181 | 182 | #### [v0.18.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.17.0...v0.18.0) 183 | 184 | > 19 July 2021 185 | 186 | - implements .first and .last, fixes #40 [`#40`](https://github.com/StoneCypher/circular_buffer_js/issues/40) 187 | - narrow the example code so comments fit on npm [`f0f2eb8`](https://github.com/StoneCypher/circular_buffer_js/commit/f0f2eb8c9f30b78ca749b9ce68dd6f4057ec85cd) 188 | - change the api in the readme [`810babe`](https://github.com/StoneCypher/circular_buffer_js/commit/810babe1cf56c8b191ca38023d97def98120ea70) 189 | - and the api [`83e8edb`](https://github.com/StoneCypher/circular_buffer_js/commit/83e8edbad820a1ab5ee45b65fc6c9f9dcaa979f8) 190 | 191 | #### [v0.17.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.16.0...v0.17.0) 192 | 193 | > 19 July 2021 194 | 195 | - .npmignore to reduce the package, improve test labels [`464bf40`](https://github.com/StoneCypher/circular_buffer_js/commit/464bf40b814b8f42e8f6247a22602b6561090688) 196 | - changes methods to getters, camelcases booleans to disambiguate [`c44cdb4`](https://github.com/StoneCypher/circular_buffer_js/commit/c44cdb4ee23830b4c606e7345631b2963e127bdb) 197 | 198 | #### [v0.16.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.15.15...v0.16.0) 199 | 200 | > 19 July 2021 201 | 202 | - implements clear/0 [`#38`](https://github.com/StoneCypher/circular_buffer_js/pull/38) 203 | - readme improvements [`a6b608d`](https://github.com/StoneCypher/circular_buffer_js/commit/a6b608d0dc9272c31cfb3548730e52723f804dec) 204 | 205 | #### [v0.15.15](https://github.com/StoneCypher/circular_buffer_js/compare/v0.15.11...v0.15.15) 206 | 207 | > 19 July 2021 208 | 209 | - sets ref to docs in readme, fixes #33; .nojekyll fixes #34; small readme tips; adds fill/1 to api list in readme [`#33`](https://github.com/StoneCypher/circular_buffer_js/issues/33) [`#34`](https://github.com/StoneCypher/circular_buffer_js/issues/34) 210 | - better github action only calls macs once, improves node range [`d428fbd`](https://github.com/StoneCypher/circular_buffer_js/commit/d428fbd2a6da533a1637424a2ca42e153bbe6875) 211 | - more .nojekyll in more directories because le sigh [`5a573e4`](https://github.com/StoneCypher/circular_buffer_js/commit/5a573e4dc2cc43aa33792517b5251f32fbb0bd22) 212 | 213 | #### [v0.15.11](https://github.com/StoneCypher/circular_buffer_js/compare/v0.15.8...v0.15.11) 214 | 215 | > 19 July 2021 216 | 217 | - Should close both issues - module path in package was missing dist, didn't copy subordinate .d.ts - fixes #31 [`#31`](https://github.com/StoneCypher/circular_buffer_js/issues/31) 218 | - Indicate types in package.json, fixes #30; fill stoch coverage defect in fill/1 [`#30`](https://github.com/StoneCypher/circular_buffer_js/issues/30) 219 | - lawdy. @drohen catching me screwing the pooch left and right. really need to automate the ci build to npm [`b9f6d25`](https://github.com/StoneCypher/circular_buffer_js/commit/b9f6d25747b06a4eba3816fabcd4e8a2a3ac9e45) 220 | - bump and rebuild [`38f1661`](https://github.com/StoneCypher/circular_buffer_js/commit/38f1661249e37b87e49f9e5aae5d7aa14666c60d) 221 | - merge error in readme apparently [`fbb1a4f`](https://github.com/StoneCypher/circular_buffer_js/commit/fbb1a4ffe759a3043f70953ce8ac66baec6bead7) 222 | 223 | #### [v0.15.8](https://github.com/StoneCypher/circular_buffer_js/compare/v0.15.0...v0.15.8) 224 | 225 | > 19 July 2021 226 | 227 | - Fill [`#26`](https://github.com/StoneCypher/circular_buffer_js/pull/26) 228 | - build wasn't up to date (gross) fixes #28 [`#28`](https://github.com/StoneCypher/circular_buffer_js/issues/28) 229 | - fixes #17 [`#17`](https://github.com/StoneCypher/circular_buffer_js/issues/17) 230 | - fixes #17 - Adjusted comments, fixed off-by-one error, 'any' to 'T' patch [`#17`](https://github.com/StoneCypher/circular_buffer_js/issues/17) 231 | - fixes #17 [`#17`](https://github.com/StoneCypher/circular_buffer_js/issues/17) 232 | - merging with master [`f6e988f`](https://github.com/StoneCypher/circular_buffer_js/commit/f6e988fda337953b95e967ca44a3523f54f33f27) 233 | - test for fill/partial/3 [`bbf9dd6`](https://github.com/StoneCypher/circular_buffer_js/commit/bbf9dd6409b258002d826246cfa98a09063cac99) 234 | - testing fill [`d4711ec`](https://github.com/StoneCypher/circular_buffer_js/commit/d4711ec69b1e60e5628879ff4f2ead495b90fd6a) 235 | - catching up -nothing to see here [`87eccd6`](https://github.com/StoneCypher/circular_buffer_js/commit/87eccd65ea1b5475785b47afe3371bfe6c71017c) 236 | - try, try again [`e184841`](https://github.com/StoneCypher/circular_buffer_js/commit/e1848414e607fde139548b48e21f96b6e133fa52) 237 | - ... [`b1ad533`](https://github.com/StoneCypher/circular_buffer_js/commit/b1ad533afd865fce40ff9e66995be097edddd801) 238 | - fix that ts-jest was a dep instead of a devdep; readme; disable failing docs path [`53afc6a`](https://github.com/StoneCypher/circular_buffer_js/commit/53afc6ac8f27b8185b783d9fdf0cd0af499c89ad) 239 | - better readme [`769dfbe`](https://github.com/StoneCypher/circular_buffer_js/commit/769dfbe2b421cf7f4e6b94edb931295eb5eb16f1) 240 | - Let's try auto-docs again [`9c40f37`](https://github.com/StoneCypher/circular_buffer_js/commit/9c40f376941fac6c7ecdd34053f0df57fde3372c) 241 | - dix for length and fill/full test [`b4386ae`](https://github.com/StoneCypher/circular_buffer_js/commit/b4386aecb2a32465bed432ca3872c600a17fbea0) 242 | - Let's take another stab at auto-docs [`feca402`](https://github.com/StoneCypher/circular_buffer_js/commit/feca4020cd3ff2d841e76acd5042e095467a17fa) 243 | - local [`dd2b989`](https://github.com/StoneCypher/circular_buffer_js/commit/dd2b98940237a220a5ca05d7bb9252b17acf15fc) 244 | - Update README.md [`3dac9ec`](https://github.com/StoneCypher/circular_buffer_js/commit/3dac9ecdace9de079efb3cb226d4ae02fdda0bf2) 245 | - putting fill where it belongs? [`3b4051e`](https://github.com/StoneCypher/circular_buffer_js/commit/3b4051e77ff12e26dc509090aaf5b86b294db941) 246 | - Update README.md [`562eb0b`](https://github.com/StoneCypher/circular_buffer_js/commit/562eb0b74a9f7d6b793bb7b8d35de30bb8116fd4) 247 | - initial setup [`09f426b`](https://github.com/StoneCypher/circular_buffer_js/commit/09f426b6a10df0cb11407dc453dc67614826a4f0) 248 | - converted toStrictEqual to toEqual [`1539e27`](https://github.com/StoneCypher/circular_buffer_js/commit/1539e27ef75c55b220dacaf33808033d280e3651) 249 | - regen [`ec02720`](https://github.com/StoneCypher/circular_buffer_js/commit/ec02720be251cc8ace2ac0671f372d62c50004da) 250 | - second attempt withut the golfing [`92d087e`](https://github.com/StoneCypher/circular_buffer_js/commit/92d087ea971822ea0c5fd89a7bc2eb58412a2f10) 251 | - sync with main [`e693d7f`](https://github.com/StoneCypher/circular_buffer_js/commit/e693d7f38758b432823a0679fb276fd747a7b648) 252 | - resolving conflicts [`d0b995f`](https://github.com/StoneCypher/circular_buffer_js/commit/d0b995f859bdc377e1d8fe24e3182612e2fcdb73) 253 | - autodoc yaml fixes? [`9b83b01`](https://github.com/StoneCypher/circular_buffer_js/commit/9b83b01d4354504df65fa5946658b58f041911f1) 254 | - added rangeError [`a4d8448`](https://github.com/StoneCypher/circular_buffer_js/commit/a4d8448a42009ef7c2dc64b308c9aee4401377eb) 255 | - conflicting files [`2b71ab5`](https://github.com/StoneCypher/circular_buffer_js/commit/2b71ab52ed94623838986d52212ee765d464a6ab) 256 | - spacing [`851e491`](https://github.com/StoneCypher/circular_buffer_js/commit/851e491a386f9167234ea80e1ab5dda81010559e) 257 | - actually since we know it's ubuntu, we can do it directly there [`6f6a361`](https://github.com/StoneCypher/circular_buffer_js/commit/6f6a3615ec8533b52225cf13c3f4dd1ffef89334) 258 | - add needs clause to enforce seriality; fix script location in yaml [`09e1ca7`](https://github.com/StoneCypher/circular_buffer_js/commit/09e1ca7fe0fb40c0b90374a0f1c776e90bc745b6) 259 | - resolving the other two conflicting branches [`b804a28`](https://github.com/StoneCypher/circular_buffer_js/commit/b804a28d2c677106d5fca51aa4cdb445cd7ccaaa) 260 | - Let's try auto-docs ... AGAIN. Trying to cope with rimraf install now [`f91de5d`](https://github.com/StoneCypher/circular_buffer_js/commit/f91de5d6196df00afdf6f5da634225c7e4987cf2) 261 | - spacing [`7e140a9`](https://github.com/StoneCypher/circular_buffer_js/commit/7e140a9f2ecfdce0a302beed4df46b9f3716edad) 262 | - stray rimraf ref [`b3e2720`](https://github.com/StoneCypher/circular_buffer_js/commit/b3e2720e6e60f17855fa10035d2e94173fa79ad4) 263 | - adding a cache of package, installed rimraf in pages thread [`e5e832d`](https://github.com/StoneCypher/circular_buffer_js/commit/e5e832d7790dd9526892c71dde052cd4bd677b33) 264 | - fill function attempt 1 [`4f345ac`](https://github.com/StoneCypher/circular_buffer_js/commit/4f345ac56bd59ebf72a3d4dddb8e098cdce9a30a) 265 | - trigger build without causing a fault [`8c7bc7b`](https://github.com/StoneCypher/circular_buffer_js/commit/8c7bc7bc139681a0effd8841d0c62e60ea66bdc1) 266 | - trigger build [`67b789f`](https://github.com/StoneCypher/circular_buffer_js/commit/67b789f7173d71f948a446ecca9129a96fb298c4) 267 | - Update README.md [`c3c1974`](https://github.com/StoneCypher/circular_buffer_js/commit/c3c1974d3f2a79c8c4c5718106a0045a0ac29259) 268 | - fix for stray blank line [`d427252`](https://github.com/StoneCypher/circular_buffer_js/commit/d427252ba3bc75cc3524bc2c397e9e8a9c1084e9) 269 | - Update README.md [`1e4c0bc`](https://github.com/StoneCypher/circular_buffer_js/commit/1e4c0bc892984be60f2148f0cee0b5c5fa934141) 270 | 271 | #### [v0.15.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.13.2...v0.15.0) 272 | 273 | > 19 July 2021 274 | 275 | - main and module fields. re-add dist. disable underway doc stuff [`2c15cd3`](https://github.com/StoneCypher/circular_buffer_js/commit/2c15cd3e883bae1f14873ab7cfebe012cef0ef7e) 276 | - move archival script to appropriate branch; place warning dummies [`e9da39b`](https://github.com/StoneCypher/circular_buffer_js/commit/e9da39b37c0264bfd9e3baf8dee0406505c6d93b) 277 | - Attempting automatic doc archival [`3ed2e08`](https://github.com/StoneCypher/circular_buffer_js/commit/3ed2e085c33188fb6c8cce397645a359a44dba05) 278 | - maybe that error is actually about the following line [`0561b59`](https://github.com/StoneCypher/circular_buffer_js/commit/0561b59fd749efd86ecaa93f45eb01a6145ef086) 279 | - trying a slightly more detailed approach [`c0e1aed`](https://github.com/StoneCypher/circular_buffer_js/commit/c0e1aedaebc7e803f5604dfb934644804366e12b) 280 | - right, that package doesn't exist anymore, do it directly [`e1d3643`](https://github.com/StoneCypher/circular_buffer_js/commit/e1d36430e7a5871959b1a5df0ff5f59677516d5e) 281 | - maybe ... maybe that's a string? [`18eba1e`](https://github.com/StoneCypher/circular_buffer_js/commit/18eba1e6f5def424e0f9393c99bcb4c4f65be7fc) 282 | 283 | #### [v0.13.2](https://github.com/StoneCypher/circular_buffer_js/compare/v0.13.1...v0.13.2) 284 | 285 | > 19 July 2021 286 | 287 | - 100% spec, 100% stoch [`e14a0d0`](https://github.com/StoneCypher/circular_buffer_js/commit/e14a0d071935a4554c7d295052d19cceb6505675) 288 | 289 | #### [v0.13.1](https://github.com/StoneCypher/circular_buffer_js/compare/v0.13.0...v0.13.1) 290 | 291 | > 19 July 2021 292 | 293 | - More serious stochastics. Separate stoch coverage fdrom spec coverage [`84987d6`](https://github.com/StoneCypher/circular_buffer_js/commit/84987d635116edd08ca2e1b8511d7a56695f2e7b) 294 | 295 | #### [v0.13.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.12.0...v0.13.0) 296 | 297 | > 19 July 2021 298 | 299 | - stoch testing :D [`9e68192`](https://github.com/StoneCypher/circular_buffer_js/commit/9e6819289769e1424fd72175ca6220c00b9bb48b) 300 | 301 | #### [v0.12.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.11.2...v0.12.0) 302 | 303 | > 19 July 2021 304 | 305 | - empty/0, full/0, tests [`75d622f`](https://github.com/StoneCypher/circular_buffer_js/commit/75d622f6ef32a0fcd7e2056767e6117789d02b89) 306 | 307 | #### [v0.11.2](https://github.com/StoneCypher/circular_buffer_js/compare/v0.11.0...v0.11.2) 308 | 309 | > 19 July 2021 310 | 311 | - early 100% coverage, no stoch yet [`bcfd509`](https://github.com/StoneCypher/circular_buffer_js/commit/bcfd509669e4c3c79515e25a3accf7c515318efd) 312 | - weird testing thing [`0f9c917`](https://github.com/StoneCypher/circular_buffer_js/commit/0f9c91725d07281a45412b7494db7ba2d104ed28) 313 | - Much improve the spec. Rename it to not be topic specific. [`1fbaaa8`](https://github.com/StoneCypher/circular_buffer_js/commit/1fbaaa89f8dd197d425ce2618c63fbe6d5043156) 314 | 315 | #### [v0.11.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.10.0...v0.11.0) 316 | 317 | > 19 July 2021 318 | 319 | - first steps towards basic unit tests [`92f1d67`](https://github.com/StoneCypher/circular_buffer_js/commit/92f1d673a94df8a67fb194da38c5d85035d66349) 320 | 321 | #### [v0.10.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.9.0...v0.10.0) 322 | 323 | > 19 July 2021 324 | 325 | - working fast-check in jest, and in build [`4e3de2c`](https://github.com/StoneCypher/circular_buffer_js/commit/4e3de2c416a5c7d85991fdbb08daefdd9c641889) 326 | 327 | #### [v0.9.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.8.0...v0.9.0) 328 | 329 | > 19 July 2021 330 | 331 | - working trivial jest [`74e005e`](https://github.com/StoneCypher/circular_buffer_js/commit/74e005ea5aeeca0547bdedff05f5f02263adbef3) 332 | 333 | #### [v0.8.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.7.0...v0.8.0) 334 | 335 | > 19 July 2021 336 | 337 | - servicable pop/0 [`aaaabf8`](https://github.com/StoneCypher/circular_buffer_js/commit/aaaabf8d78929c4c6ea8c88bd536afae29d311e4) 338 | 339 | #### [v0.7.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.6.0...v0.7.0) 340 | 341 | > 19 July 2021 342 | 343 | - Move docs to a branch to reduce install size. Remove docs from main entirely. Remove versioning scheme from main. Expect to recreate in CI [`f61c05b`](https://github.com/StoneCypher/circular_buffer_js/commit/f61c05bd943bd47c6c31680df31173151521898c) 344 | - servicable at/1, push/1 [`a7ae11a`](https://github.com/StoneCypher/circular_buffer_js/commit/a7ae11a0c7587a3bbe76f7f4df22957da0cccbd0) 345 | - first steps at ci automation [`99e265c`](https://github.com/StoneCypher/circular_buffer_js/commit/99e265ca9ef6a402009e5a8f14fe8b9ea77b5945) 346 | - vestige of old docs directory [`24c3fff`](https://github.com/StoneCypher/circular_buffer_js/commit/24c3ffffa09d6cb0ddd88af9329c942c9c09f9b6) 347 | - Add CI script and push to trigger [`49ae90f`](https://github.com/StoneCypher/circular_buffer_js/commit/49ae90f70ebc36f704a1f97814369eac638f4077) 348 | 349 | #### [v0.6.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.5.0...v0.6.0) 350 | 351 | > 19 July 2021 352 | 353 | - Minification. Moved verbose targets to non-tracked build. Minified into dist instead. Kept unminified iife only. [`edbadd8`](https://github.com/StoneCypher/circular_buffer_js/commit/edbadd84715838e260ea89fc47b08d10faa023fe) 354 | 355 | #### [v0.5.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.4.0...v0.5.0) 356 | 357 | > 19 July 2021 358 | 359 | - Versioned documentation build now works :D [`95b65de`](https://github.com/StoneCypher/circular_buffer_js/commit/95b65debd12d61790fa410ec58421c6fdbc1ca93) 360 | - Rollup bundling. Output is unminified esm, iife, cjs. [`7f0c630`](https://github.com/StoneCypher/circular_buffer_js/commit/7f0c630ee7608a100e6bc249dfcb3a68c14b5205) 361 | 362 | #### [v0.4.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.3.0...v0.4.0) 363 | 364 | > 19 July 2021 365 | 366 | - docs directory set up. typedoc extracting to current directory [`b320512`](https://github.com/StoneCypher/circular_buffer_js/commit/b3205126fb2d0417d7aca9f14bc6bf0adbff46d1) 367 | 368 | #### [v0.3.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.2.0...v0.3.0) 369 | 370 | > 19 July 2021 371 | 372 | - typescript now installed, building. version now part of build [`e9304d3`](https://github.com/StoneCypher/circular_buffer_js/commit/e9304d3b4433f59594955ba3e125a74101632695) 373 | 374 | #### [v0.2.0](https://github.com/StoneCypher/circular_buffer_js/compare/v0.1.0...v0.2.0) 375 | 376 | > 19 July 2021 377 | 378 | - build and clean processes; version number file generated [`57e4227`](https://github.com/StoneCypher/circular_buffer_js/commit/57e422739723e4ab85830aaacf7c8e264052447e) 379 | 380 | #### v0.1.0 381 | 382 | > 19 July 2021 383 | 384 | - Initial commit [`3675cd8`](https://github.com/StoneCypher/circular_buffer_js/commit/3675cd88609d5518474c5c55bd64843380f1364f) 385 | - npm init and npm install [`c93c939`](https://github.com/StoneCypher/circular_buffer_js/commit/c93c93930a92587506b6047188a13b7da836e16b) 386 | -------------------------------------------------------------------------------- /src/ts/circular_buffer.ts: -------------------------------------------------------------------------------- 1 | 2 | import { version } from './generated/package_version'; 3 | 4 | 5 | 6 | 7 | 8 | type TraversalFunctor = (_element: T, _index?: number, _array?: T[]) => unknown; 9 | 10 | 11 | 12 | 13 | 14 | /********* 15 | * 16 | * This is a circular queue. 17 | * 18 | */ 19 | 20 | 21 | class circular_buffer { 22 | 23 | 24 | 25 | /** The actual dataset. Not in order as the outside world would expect. If 26 | you want this functionality, use {@link toArray | .toArray()} instead. */ 27 | private _values : T[]; 28 | 29 | 30 | /** The current cursor in the underlying array. You should never need 31 | this; it is an internal implementation detail. This describes the 32 | start position in the storage of the storage mapping. */ 33 | private _cursor : number; 34 | 35 | 36 | /** The current offset in the underlying array. You should never need 37 | this; it is an internal implementation detail. This is an eviction 38 | count from the datastructure, giving you total queue depth. */ 39 | private _offset : number; 40 | 41 | /** The current used range within the dataset array. Values outside this 42 | range aren't trustworthy. Use {@link length | .length} instead. */ 43 | private _length : number; 44 | 45 | /** The current size cap, as a cache. Use {@link capacity | .capacity} 46 | instead. */ 47 | private _capacity : number; 48 | 49 | 50 | 51 | 52 | 53 | /********* 54 | * 55 | * Create a circular queue of the size of the argument, `uCapacity`. 56 | * 57 | * ```typescript 58 | * const cb = new circular_buffer(3); 59 | * cb.capacity; // 3 60 | * ``` 61 | * 62 | * Queues may be, but do not have to be, typed. If they are, all methods 63 | * will also be appropriately typed, both in arguments and return values. 64 | * 65 | * ```typescript 66 | * const cb = new circular_buffer(2); 67 | * ``` 68 | */ 69 | 70 | constructor(uCapacity: number) { 71 | 72 | if (!( Number.isInteger(uCapacity) )) { throw new RangeError(`Capacity must be an integer, received ${uCapacity}`); } 73 | if (uCapacity < 0) { throw new RangeError(`Capacity must be a non-negative integer, received ${uCapacity}`); } 74 | 75 | this._values = new Array(uCapacity); 76 | this._capacity = uCapacity; 77 | this._cursor = 0; 78 | this._offset = 0; 79 | this._length = 0; 80 | 81 | } 82 | 83 | 84 | 85 | 86 | 87 | /********* 88 | * 89 | * The number of spaces offered, total, regardless of what's currently used. 90 | * 91 | * ```typescript 92 | * const cb = new circular_buffer(3); 93 | * cb.capacity; // 3 94 | * cb.push(1); // ok, returns 1 95 | * cb.capacity; // 3 96 | * cb.pop(); // ok, returns 1, container now empty 97 | * cb.capacity; // 3 98 | * ``` 99 | */ 100 | 101 | get capacity(): number { 102 | return this._capacity; 103 | } 104 | 105 | 106 | 107 | 108 | 109 | /********* 110 | * 111 | * Setting `.capacity` resizes the container (and clips contents if 112 | * necessary.) Calling this is equivalent to calling [[circular_buffer_js.resize]]. 113 | * 114 | * ```typescript 115 | * const cb = new circular_buffer(3); 116 | * cb.capacity; // 3 - [ , , ] 117 | * cb.push(1); // ok, returns 1 118 | * cb.capacity; // 3 - [1, , ] 119 | * cb.resize(4); // ok 120 | * cb.capacity; // 4 - [1, , , ] 121 | * ``` 122 | */ 123 | 124 | set capacity(newSize: number) { 125 | this.resize(newSize); 126 | } 127 | 128 | 129 | 130 | 131 | 132 | /********* 133 | * 134 | * The number of spaces currently filled. 135 | * 136 | * ```typescript 137 | * const cb = new circular_buffer(3); 138 | * 139 | * cb.length; // 0 140 | * cb.push(1); // ok, returns 1 141 | * cb.length; // 1 142 | * cb.pop(); // ok, returns 1, container now empty 143 | * cb.length; // 0 144 | * cb.fill(3); // [3,3,3] 145 | * cb.length; // 3 146 | * cb.clear(); // [ , , ] 147 | * cb.length; // 0 148 | * ``` 149 | */ 150 | 151 | get length(): number { 152 | return this._length; 153 | } 154 | 155 | 156 | 157 | 158 | 159 | /********* 160 | * 161 | * Sets the length of the data inside the queue, but does not change the size 162 | * of the queue itself (if you'd like this, set [[circular_buffer_js.capacity]] 163 | * or call [[circular_buffer_js.resize]] instead, as you prefer.) 164 | * 165 | * As an odd result, setting `.length` to a length longer than or equal to 166 | * the `.length` of the data, but smaller than the `.capacity` of the 167 | * container, is a no-op. 168 | * 169 | * Attempting to set a `.length` longer than the `.capacity` of the container 170 | * will throw. 171 | * 172 | * ```typescript 173 | * const cb = circular_buffer.from([1,2,3]); 174 | * 175 | * cb.length; // 3 176 | * cb.toArray(); // [1,2,3] 177 | * cb.length = 2; // ok, truncates the `3` at end 178 | * cb.length; // 2 179 | * cb.toArray(); // [1,2, ] 180 | * cb.length = 3; // ok, no change 181 | * cb.length; // 2 182 | * cb.toArray(); // [1,2, ] 183 | * cb.length = 1; // ok, truncates the `2` at end 184 | * cb.toArray(); // [1, , ] 185 | * cp.capacity = 1; // ok, resizes the container 186 | * cb.toArray(); // [1] 187 | * cb.length = 2; // throws, container too small 188 | * cb.length = -2; // throws, no negative lengths 189 | * cb.length = 1.2; // throws, no fractional lengths 190 | * ``` 191 | */ 192 | 193 | set length(newLength: number) { 194 | 195 | if (newLength > this._capacity) { 196 | throw new RangeError(`Requested new length [${newLength}] exceeds container capacity [${this._capacity}]`); 197 | } 198 | 199 | if (newLength < 0) { 200 | throw new RangeError(`Requested new length [${newLength}] cannot be negative`); 201 | } 202 | 203 | if (!(Number.isInteger(newLength))) { 204 | throw new RangeError(`Requested new length [${newLength}] must be an integer`); 205 | } 206 | 207 | // resizing up or 0, but within container capacity, is a no-op 208 | if (this._length <= newLength) { return; } 209 | 210 | // resizing down just means decrementing the valid length 211 | this._length = newLength; 212 | 213 | } 214 | 215 | 216 | 217 | 218 | 219 | /********* 220 | * 221 | * The number of spaces available to be filled (ie, `.capacity - .length`.) 222 | * 223 | * ```typescript 224 | * const cb = new circular_buffer(3); 225 | * 226 | * cb.available; // 3 227 | * cb.push(1); // ok, returns 1 228 | * cb.available; // 2 229 | * cb.pop(); // ok, returns 1, container now empty 230 | * cb.available; // 3 231 | * cb.fill(3); // [3,3,3] 232 | * cb.available; // 0 233 | * cb.clear(); // [ , , ] 234 | * cb.available; // 3 235 | * ``` 236 | */ 237 | 238 | get available(): number { 239 | return this._capacity - this._length; 240 | } 241 | 242 | 243 | 244 | 245 | 246 | /********* 247 | * 248 | * `true` when the container has no contents (ie, `.length === 0`); `false` 249 | * otherwise. 250 | * 251 | * ```typescript 252 | * const cb = new circular_buffer(3); 253 | * 254 | * cb.isEmpty; // true 255 | * cb.push(1); // ok, returns 1 256 | * cb.isEmpty; // false 257 | * cb.clear(); // ok, container now empty 258 | * cb.isEmpty; // true 259 | * ``` 260 | */ 261 | 262 | get isEmpty(): boolean { 263 | return this._length === 0; 264 | } 265 | 266 | 267 | 268 | 269 | 270 | /********* 271 | * 272 | * `true` when the container has no space left (ie, `.length === .capacity`); 273 | * `false` otherwise. 274 | * 275 | * ```typescript 276 | * const cb = new circular_buffer(3); 277 | * 278 | * cb.isFull; // false 279 | * 280 | * cb.push(1); // ok, returns 1 281 | * cb.push(2); // ok, returns 2 282 | * cb.push(3); // ok, returns 3 283 | * 284 | * cb.isFull; // true 285 | * 286 | * cb.clear(); // ok, container now empty 287 | * cb.isFull; // false 288 | * ``` 289 | */ 290 | 291 | get isFull(): boolean { 292 | return this._length === this._capacity; 293 | } 294 | 295 | 296 | 297 | 298 | 299 | /********* 300 | * 301 | * Gets the first element of the queue; throws RangeError if the queue is 302 | * empty. 303 | * 304 | * ```typescript 305 | * const cb = new circular_buffer(3); 306 | * 307 | * cb.push(1); // ok, returns 1 308 | * cb.push(2); // ok, returns 2 309 | * cb.push(3); // ok, returns 3 310 | * 311 | * cb.first; // 1 312 | * 313 | * cb.clear(); // ok, container now empty 314 | * cb.first; // throws RangeError, because the container is empty 315 | * ``` 316 | */ 317 | 318 | get first() : T { 319 | 320 | if (this.isEmpty) { 321 | throw new RangeError('Cannot return first element of an empty container'); 322 | } 323 | 324 | return this.at(0); 325 | 326 | } 327 | 328 | 329 | 330 | 331 | 332 | /********* 333 | * 334 | * Gets the last element of the queue; throws RangeError if the queue is 335 | * empty. 336 | * 337 | * ```typescript 338 | * const cb = new circular_buffer(3); 339 | * 340 | * cb.push(1); // ok, returns 1 341 | * cb.push(2); // ok, returns 2 342 | * cb.push(3); // ok, returns 3 343 | * 344 | * cb.last; // 3 345 | * 346 | * cb.clear(); // ok, container now empty 347 | * cb.last; // throws RangeError, because the container is empty 348 | * ``` 349 | */ 350 | 351 | get last() : T { 352 | 353 | if (this.isEmpty) { 354 | throw new RangeError('Cannot return last element of an empty container'); 355 | } 356 | 357 | return this.at(this.length - 1); 358 | 359 | } 360 | 361 | 362 | 363 | 364 | 365 | /********* 366 | * 367 | * Creates a circular buffer from an `ArrayLike` or an `Iterable`, with a 368 | * matching capacity. Static method, and as such should not be called from 369 | * an instance (so, do not call using `new`.) 370 | * 371 | * ```typescript 372 | * const cb = circular_buffer.from([1,2,3]); 373 | * 374 | * cb.pop(); // ok, returns 1 375 | * cb.pop(); // ok, returns 2 376 | * cb.pop(); // ok, returns 3 377 | * 378 | * cb.pop(); // throws RangeError, empty 379 | * ``` 380 | */ 381 | 382 | static from(i: Iterable | ArrayLike, map_fn?: (_k: T, _i: number) => T, t?: unknown): circular_buffer { 383 | 384 | const new_array: T[] = map_fn 385 | ? Array.from(i, map_fn, t) 386 | : Array.from(i); 387 | 388 | const target_length = new_array.length; 389 | 390 | const ncb = new circular_buffer(target_length); 391 | ncb._values = new_array; 392 | ncb._length = target_length; 393 | 394 | return ncb; 395 | 396 | } 397 | 398 | 399 | 400 | 401 | 402 | /********* 403 | * 404 | * Pushes a value onto the end of the container; throws `RangeError` if the 405 | * container is already full. 406 | * 407 | * ```typescript 408 | * const cb = new circular_buffer(3); 409 | * 410 | * cb.push(1); // ok, returns 1 411 | * cb.push(2); // ok, returns 2 412 | * cb.push(3); // ok, returns 3 413 | * 414 | * cb.push(4); // throws RangeError, container only has 3 spots 415 | * ``` 416 | */ 417 | 418 | push(v: T): T { 419 | 420 | if (this.isFull) { 421 | throw new RangeError(`Cannot push, structure is full to capacity`); 422 | } 423 | 424 | this._values[(this._cursor + this._length++) % this._capacity] = v; 425 | 426 | return v; 427 | 428 | } 429 | 430 | 431 | 432 | 433 | 434 | /********* 435 | * 436 | * Pushes a value onto the end of the container; removes front to make space 437 | * if necessary. Throws `RangeError` if the container is zero-size. 438 | * 439 | * Returns the value shoved out, if any. 440 | * 441 | * ```typescript 442 | * const cb = new circular_buffer(3); 443 | * 444 | * cb.shove(1); // ok, returns undefined 445 | * cb.shove(2); // ok, returns undefined 446 | * cb.shove(3); // ok, returns undefined 447 | * 448 | * cb.toArray(); // [1,2,3] 449 | * 450 | * cb.push(4); // ok, returns 1 451 | * 452 | * cb.toArray(); // [2,3,4] 453 | * ``` 454 | */ 455 | 456 | shove(v: T): T | undefined { 457 | 458 | let shoved: T | undefined; 459 | 460 | if (this._capacity === 0) { 461 | throw new RangeError(`Cannot shove, structure is zero-capacity`); 462 | } 463 | 464 | if (this.isFull) { 465 | shoved = this.pop(); 466 | } 467 | 468 | this.push(v); 469 | 470 | return shoved; 471 | 472 | } 473 | 474 | 475 | 476 | 477 | 478 | /********* 479 | * 480 | * Fills a container with a repeated value. 481 | * 482 | * ```typescript 483 | * const cb = new circular_buffer(3); 484 | * 485 | * cb.length; // 0 486 | * cb.at(2); // throws RangeError 487 | * 488 | * cb.fill('Bob'); // ['Bob', 'Bob', 'Bob'] 489 | * cb.length; // 3 490 | * cb.at(2); // 'Bob' 491 | * ``` 492 | */ 493 | 494 | fill(x:T): T[] { 495 | 496 | for (let i = 0; i < this._capacity; i++) { 497 | this._values[i] = x; 498 | } 499 | 500 | this._length = this._capacity; 501 | 502 | return this._values; 503 | 504 | } 505 | 506 | 507 | 508 | 509 | 510 | /********* 511 | * 512 | * Returns the index of the first element matching the search 513 | * element provided, or -1 in the case of no matching elements. 514 | * 515 | * ```typescript 516 | * const numbers = circular_buffer.from(['One', 'Two', 'Three']); 517 | * 518 | * console.log(`Index of Two: ${numbers.indexOf('Two')}`); // expects 1 519 | * console.log(`Index of Four: ${numbers.indexOf('Four')}`); // expects -1 520 | * ``` 521 | * 522 | * You may also pass a starting point, if you want to. 523 | * 524 | * ```typescript 525 | * const letters = circular_buffer.from(['a','b','a','b']); 526 | * console.log(`Index of b starting from 2: ${numbers.indexOf('b', 2)}`); // expects 3 527 | * ``` 528 | * 529 | */ 530 | 531 | indexOf( searchElement: T, fromIndex?: number ): number { 532 | 533 | const normalized = this.toArray(); 534 | return normalized.indexOf(searchElement, fromIndex); 535 | 536 | } 537 | 538 | 539 | 540 | 541 | 542 | /********* 543 | * 544 | * Using an identifier predicate, return either the first matching 545 | * element or `undefined`. 546 | * 547 | * ```typescript 548 | * const dogs = ['beagle', 'doberman', 'deputy']; 549 | * is_dog = animal => dogs.includes(animal); 550 | * 551 | * const room = ['siamese', 'beagle', 'cockatoo']; 552 | * 553 | * console.log(room.find(is_dog)); // prints 'beagle' 554 | * ``` 555 | */ 556 | 557 | find( predicate: TraversalFunctor, thisArg?: unknown ): T | unknown { 558 | return this.toArray().find(predicate, thisArg); 559 | } 560 | 561 | 562 | 563 | 564 | 565 | /********* 566 | * 567 | * Iterates a container with a predicate, testing for all truthy. 568 | * 569 | * ```typescript 570 | * const cb = circular_buffer.from([1,2,'three']); 571 | * cb.every( i => typeof i === 'number' ); // false 572 | * ``` 573 | */ 574 | 575 | every( functor: TraversalFunctor, thisArg?: unknown ): boolean { 576 | 577 | const normalized = this.toArray(), 578 | res = normalized.every(functor, thisArg); 579 | 580 | // every can mutate, so, store the result, which will usually be nothing 581 | 582 | this._values = normalized; 583 | this._values.length = this._capacity; // stack with new empties 584 | this._cursor = 0; // accomodate internal rotation 585 | // do not mutate this._offset; it doesn't change 586 | 587 | return res; 588 | 589 | } 590 | 591 | 592 | 593 | 594 | 595 | /********* 596 | * 597 | * Iterates a container with a predicate, testing for at least one truthy. 598 | * 599 | * ```typescript 600 | * const cb = circular_buffer.from([1,2,'three']); 601 | * cb.some( i => typeof i === 'string' ); // true 602 | * ``` 603 | */ 604 | 605 | some( functor: TraversalFunctor, thisArg?: unknown ): boolean { 606 | 607 | const normalized = this.toArray(), 608 | res = normalized.some(functor, thisArg); 609 | 610 | // every can mutate, so, store the result, which will usually be nothing 611 | 612 | this._values = normalized; 613 | this._values.length = this._capacity; // stack with new empties 614 | this._cursor = 0; // accomodate internal rotation 615 | // do not mutate _this.offset; it doesn't change 616 | 617 | return res; 618 | 619 | } 620 | 621 | 622 | 623 | 624 | 625 | /********* 626 | * 627 | * Reverses a container. 628 | * 629 | * ```typescript 630 | * const cb = circular_buffer.from([3,2,1]); 631 | * cb.reverse(); 632 | * 633 | * cb.pop(); // ok, returns 1 634 | * cb.pop(); // ok, returns 2 635 | * cb.pop(); // ok, returns 3 636 | * ``` 637 | */ 638 | 639 | reverse(): circular_buffer { 640 | 641 | const normalized: T[] = this.toArray(); // internally rotate to origin and ditch empties 642 | 643 | this._values = normalized.reverse(); // reverse data 644 | this._values.length = this._capacity; // stack with new empties 645 | this._cursor = 0; // accomodate internal rotation 646 | // do not mutate _this.offset; it doesn't change 647 | 648 | return this; 649 | 650 | } 651 | 652 | 653 | 654 | 655 | 656 | /********* 657 | * 658 | * Empties a container. Returns the previous contents. 659 | * 660 | * ```typescript 661 | * const cb = new circular_buffer(3); 662 | * 663 | * cb.push(10); // ok, returns 10 664 | * cb.push(20); // ok, returns 20 665 | * cb.push(30); // ok, returns 30 666 | * 667 | * cb.last; // 30 668 | * cb.length; // 3 669 | * 670 | * cb.clear(); // ok, returns [10,20,30]; container now empty 671 | * cb.last; // throws RangeError, because the container is empty 672 | * cb.length; // 0 673 | * ``` 674 | */ 675 | 676 | clear(): T[] { 677 | 678 | const old = this.toArray(); 679 | this._length = 0; 680 | 681 | return old; 682 | 683 | } 684 | 685 | 686 | 687 | 688 | 689 | /********* 690 | * 691 | * Pops a value from the front of the container, by returning it and removing 692 | * it from the container; throws `RangeError` if the container is already 693 | * empty. 694 | * 695 | * ```typescript 696 | * const cb = new circular_buffer(3); 697 | * 698 | * cb.push(1); // ok, returns 1 699 | * cb.push(2); // ok, returns 2 700 | * cb.pop(); // ok, returns 1 701 | * cb.pop(); // ok, returns 2 702 | * 703 | * cb.pop(); // throws RangeError, container has nothing to pop out 704 | * ``` 705 | * 706 | */ 707 | 708 | pop(): T | undefined { 709 | 710 | if (this._length <= 0) { 711 | throw new RangeError(`Cannot pop, structure is empty`); 712 | } 713 | 714 | const cache = this.at(0); 715 | 716 | --this._length; // the container is now one shorter 717 | ++this._offset; // the offset moved one forwards 718 | ++this._cursor; // the cursor moved one forwards 719 | 720 | if (this._cursor >= this._capacity) { // wrap the cursor if necessary 721 | this._cursor -= this._capacity; 722 | } 723 | 724 | return cache; 725 | 726 | } 727 | 728 | 729 | 730 | 731 | 732 | /********* 733 | * 734 | * Returns the value at a given index, or throws RangeError if that value 735 | * does not exist (container too small or nonsensical index.) 736 | * 737 | * ```typescript 738 | * const cb = new circular_buffer(3); 739 | * 740 | * cb.push(1); // ok, returns 1 741 | * cb.push(2); // ok, returns 2 742 | * cb.push(3); // ok, returns 3 743 | * 744 | * cb.at(0); // ok, returns 1 745 | * cb.at(2); // ok, returns 3 746 | * 747 | * cb.at(4); // throws RangeError, larger than the container 748 | * 749 | * cb.at(-1); // throws RangeError, nonsense index 750 | * cb.at(0.5); // throws RangeError, nonsense index 751 | * cb.at("Z"); // throws RangeError, nonsense index 752 | * ``` 753 | * 754 | */ 755 | 756 | at(i: number): T { 757 | 758 | if (i < 0) { throw new RangeError(`circular_buffer does not support negative traversals; called at(${i})`); } 759 | if (!( Number.isInteger(i) )) { throw new RangeError(`Accessors must be non-negative integers; called at(${i})`); } 760 | 761 | if (i >= this._capacity) { throw new RangeError(`Requested cell ${i} exceeds container permanent capacity`); } 762 | if (i >= this._length) { throw new RangeError(`Requested cell ${i} exceeds container current length`); } 763 | 764 | return this._values[(this._cursor + i) % this._capacity]!; // eslint-disable-line @typescript-eslint/no-non-null-assertion 765 | 766 | } 767 | 768 | 769 | 770 | 771 | 772 | /********* 773 | * 774 | * Returns the value at a given run position, or throws RangeError if that 775 | * value does not exist (container too small, too large, or nonsensical 776 | * index.) 777 | * 778 | * The difference between `pos/1` and `at/1` is that `at/1` gives you 779 | * elements against the current head cursor, whereas `pos/1` gives you 780 | * elements against the original queue head. So, most people will want 781 | * `at/1`, since it gives you contents according to what's currently in 782 | * the container. But if you're trying to get fixed positions in the 783 | * long term stream, and know that they're in the current window, like 784 | * you would have with a lockstep networking library, `pos/1` is what 785 | * you're looking for. 786 | * 787 | * ```typescript 788 | * const cb = new circular_buffer(3); 789 | * 790 | * cb.push(10); // ok, returns 10 791 | * cb.push(20); // ok, returns 20 792 | * cb.push(30); // ok, returns 30 793 | * 794 | * cb.at(0); // ok, returns 10 795 | * cb.pos(0); // ok, returns 10 796 | * cb.at(1); // ok, returns 20 797 | * cb.pos(1); // ok, returns 20 798 | * cb.at(2); // ok, returns 30 799 | * cb.pos(2); // ok, returns 30 800 | * 801 | * cb.pos(4); // throws RangeError, larger than the container 802 | * 803 | * cb.offset(); // 0 804 | * cb.pop(); // 10. container is now [20, 30, _] 805 | * cb.offset(); // 1 806 | * 807 | * cb.at(0); // ok, returns 20 808 | * cb.pos(0); // throws, as container is past this location 809 | * cb.at(1); // ok, returns 30 - at(1) is head+1 810 | * cb.pos(1); // ok, returns 20 - pos(1) is original position 1, which is currently head 811 | * cb.at(2); // throws, past fill 812 | * cb.pos(2); // ok, returns 30 813 | * 814 | * cb.pop(); // 10. container is now [20, 30, _] 815 | * 816 | * cb.at(0); // ok, returns 30 817 | * cb.pos(0); // throws, as container is past this location 818 | * cb.at(1); // throws, past fill 819 | * cb.pos(1); // throws, as container is past this location 820 | * cb.at(2); // throws, past fill 821 | * cb.pos(2); // ok, returns 30 822 | * 823 | * cb.at(-1); // throws RangeError, nonsense index 824 | * cb.at(0.5); // throws RangeError, nonsense index 825 | * cb.at("Z"); // throws RangeError, nonsense index 826 | * ``` 827 | * 828 | */ 829 | 830 | pos(i: number): T { 831 | // no need to test anything - at/1 already throws wherever needed 832 | return this.at( i - this.offset() ); 833 | } 834 | 835 | 836 | 837 | 838 | 839 | /********* 840 | * 841 | * Fetches the current offset (or eviction count) of the container. This 842 | * allows a developer to know how deep into an infinite queue they are, and 843 | * how far back their rollback window reaches if any. 844 | * 845 | * ```typescript 846 | * const cb = new circular_buffer(3); 847 | * cb.toArray(); // [] 848 | * 849 | * cb.push(10); // ok, returns 1 850 | * cb.push(20); // ok, returns 2 851 | * cb.push(30); // ok, returns 3 852 | * cb.toArray(); // [10,20,30] 853 | * 854 | * cb.offset(); // 0 855 | * cb.pop(); // 10 856 | * cb.offset(); // 1 857 | * cb.pop(); // 20 858 | * cb.offset(); // 2 859 | * cb.pop(); // 30 860 | * cb.offset(); // 3 861 | * cb.pop(); // throws - queue is now empty 862 | * 863 | * ``` 864 | */ 865 | 866 | offset(): number { 867 | return this._offset; 868 | } 869 | 870 | 871 | 872 | 873 | 874 | /********* 875 | * 876 | * Changes the capacity of the queue. If the new capacity of the queue is 877 | * too small for the prior contents, they are trimmed. The second argument, 878 | * `preferEnd`, an optional boolean that defaults false, will cause removals 879 | * to be taken from the front instead of the back. 880 | * 881 | * ```typescript 882 | * const cb = new circular_buffer(3); 883 | * cb.toArray(); // [] 884 | * 885 | * cb.push(1); // ok, returns 1 886 | * cb.push(2); // ok, returns 2 887 | * cb.push(3); // ok, returns 3 888 | * cb.toArray(); // [1,2,3] 889 | * 890 | * cb.resize(5); // ok 891 | * cb.toArray(); // [1,2,3, , ] 892 | * 893 | * cb.resize(2); // ok 894 | * cb.toArray(); // [1,2] 895 | * 896 | * cb.resize(4); // ok 897 | * cb.toArray(); // [1,2, , ] 898 | * 899 | * cb.resize(0); // ok 900 | * cb.toArray(); // [] 901 | * 902 | * cb.resize(4); // ok 903 | * cb.toArray(); // [ , , , ] 904 | * 905 | * cb.push(1); // ok, returns 1 906 | * cb.push(2); // ok, returns 2 907 | * cb.push(3); // ok, returns 3 908 | * cb.push(4); // ok, returns 4 909 | * cb.toArray(); // [1,2,3,4] 910 | * 911 | * cb.resize(2); // ok 912 | * cb.toArray(); // [3,4] 913 | * ``` 914 | */ 915 | 916 | resize(newSize: number, preferEnd = false): void { 917 | 918 | // first reorganize at zero 919 | this._values = this.toArray(); 920 | this._cursor = 0; 921 | // do not mutate _this.offset; it doesn't change 922 | 923 | const oldSize = this._length; 924 | this._length = Math.min(this._length, newSize); 925 | 926 | // next update the expected size, and act on it 927 | this._capacity = newSize; 928 | 929 | if (newSize >= oldSize) { 930 | this._values.length = newSize; // pad 931 | } else { 932 | if (preferEnd) { 933 | const tmp = this._values.slice(oldSize - newSize); 934 | this._values = tmp; 935 | } else { 936 | this._values.length = newSize; // truncate 937 | } 938 | } 939 | 940 | } 941 | 942 | 943 | 944 | 945 | 946 | /********* 947 | * 948 | * Returns the complete, ordered contents of the queue, as an array. 949 | * 950 | * ```typescript 951 | * const cb = new circular_buffer(3); 952 | * cb.toArray(); // [] 953 | * 954 | * cb.push(1); // ok, returns 1 955 | * cb.push(2); // ok, returns 2 956 | * cb.push(3); // ok, returns 3 957 | * cb.toArray(); // [1,2,3] 958 | * 959 | * cb.pop(); // ok, returns 1 960 | * cb.toArray(); // [2,3] 961 | * ``` 962 | * 963 | */ 964 | 965 | toArray(): T[] { 966 | 967 | const startPoint = this._cursor % this._capacity; 968 | 969 | if (this._capacity > (startPoint + this._length)) { 970 | // no need to splice, length doesn't wrap 971 | return this._values.slice(startPoint, startPoint+this._length); 972 | 973 | } else { 974 | 975 | // length wraps 976 | const base = this._values.slice(startPoint, this._capacity); 977 | base.push( ... this._values.slice(0, this.length - (this._capacity - startPoint)) ); 978 | 979 | return base; 980 | 981 | } 982 | 983 | } 984 | 985 | 986 | 987 | } 988 | 989 | 990 | 991 | 992 | 993 | export { 994 | 995 | version, 996 | circular_buffer, 997 | 998 | TraversalFunctor 999 | 1000 | }; 1001 | -------------------------------------------------------------------------------- /src/ts/tests/circular_buffer.stoch.ts: -------------------------------------------------------------------------------- 1 | 2 | import * as assert from 'assert'; 3 | import * as fc from 'fast-check'; 4 | import { version, circular_buffer } from '../circular_buffer'; 5 | 6 | 7 | 8 | 9 | 10 | type CbModel = { 11 | length : number, 12 | capacity : number, 13 | offset : number 14 | }; 15 | 16 | type num_cb = circular_buffer; 17 | type cb_command = fc.Command; 18 | 19 | // const rand = (n: number) => 20 | // Math.floor(Math.random() * n); 21 | 22 | 23 | 24 | 25 | 26 | test('Resizing never throws', () => { 27 | 28 | const testq = new circular_buffer(2); 29 | 30 | fc.assert( 31 | fc.property( 32 | 33 | fc.boolean(), 34 | fc.integer({ min: 0, max: 500 }), 35 | fc.double(), 36 | 37 | (shouldFromEnd: boolean, newSize: number, newContent: number) => { 38 | testq.resize(newSize, shouldFromEnd); 39 | testq.fill(newContent); 40 | } 41 | 42 | ) 43 | ); 44 | 45 | }); 46 | 47 | 48 | 49 | 50 | 51 | // from/1,2 don't really make sense in the command model 52 | 53 | test('[STOCH] simple from/1', () => { 54 | 55 | fc.assert( 56 | fc.property( 57 | 58 | fc.array(whatever()), 59 | 60 | (source: unknown[]) => { 61 | 62 | const cb = circular_buffer.from(source); 63 | expect(cb.length).toBe(source.length); 64 | 65 | const cbl = cb.length; 66 | for (let i=0; i cb.pop()).toThrow(); 71 | 72 | } 73 | 74 | ) 75 | ); 76 | 77 | }); 78 | 79 | 80 | 81 | 82 | 83 | // from/1,2 don't really make sense in the command model 84 | 85 | test('[STOCH] functor from/2', () => { 86 | 87 | fc.assert( 88 | fc.property( 89 | 90 | fc.array(fc.float()), 91 | 92 | (source: number[]) => { 93 | 94 | const cb = circular_buffer.from(source, (n: number) => n*10); 95 | expect(cb.length).toBe(source.length); 96 | 97 | const cbl = cb.length; 98 | for (let i=0; i cb.pop()).toThrow(); 103 | 104 | } 105 | 106 | ) 107 | ); 108 | 109 | }); 110 | 111 | 112 | 113 | 114 | 115 | function whatever(): fc.Arbitrary { 116 | 117 | return fc.anything({ 118 | withBigInt : true, 119 | withBoxedValues : true, 120 | withDate : true, 121 | withMap : true, 122 | withNullPrototype : true, 123 | withObjectString : true, 124 | withSet : true, 125 | withTypedArray : true 126 | }); 127 | 128 | } 129 | 130 | 131 | 132 | 133 | 134 | class PushCommand implements cb_command { 135 | 136 | constructor(readonly value: number) {} // eslint-disable-line no-unused-vars 137 | 138 | check = (_m: Readonly): boolean => true; // allowed to push into a full cb because we test overflows 139 | 140 | run(m: CbModel, r: num_cb): void { 141 | 142 | const newValue = whatever(); 143 | 144 | if (m.length < m.capacity) { // cb isn't full, so should work 145 | r.push( newValue ); 146 | ++m.length; 147 | assert.deepEqual( r.last, newValue ); 148 | } else { // cb _is_ full, so should fail 149 | assert.throws( () => r.push( newValue ) ); 150 | } 151 | 152 | } 153 | 154 | toString = () => `push(${this.value})`; 155 | 156 | } 157 | 158 | 159 | 160 | 161 | 162 | class ShoveCommand implements cb_command { 163 | 164 | constructor(readonly value: number) {} // eslint-disable-line no-unused-vars 165 | 166 | check = (_m: Readonly): boolean => true; // allowed to push into a full cb because we test overflows 167 | 168 | run(m: CbModel, r: num_cb): void { 169 | 170 | const newValue = whatever(); 171 | 172 | if (m.length < m.capacity) { // cb isn't full, so should increase size 173 | r.shove( newValue ); 174 | ++m.length; 175 | assert.deepEqual( r.last, newValue ); 176 | } else if (m.capacity === 0) { // special case: cb is zero-sized 177 | assert.throws( () => r.shove( newValue ) ); // nowhere to shove 178 | } else { // cb is full, so shouldn't increase size 179 | r.shove( newValue ); 180 | assert.deepEqual( r.last, newValue ); 181 | } 182 | 183 | } 184 | 185 | toString = () => `shove(${this.value})`; 186 | 187 | } 188 | 189 | 190 | 191 | 192 | class PopCommand implements cb_command { 193 | 194 | toString = () => 'pop'; 195 | check = (_m: Readonly): boolean => true; // allowed to pop from an empty cb because we test underflows 196 | 197 | run(m: CbModel, r: circular_buffer): void { 198 | 199 | if (m.length > 0) { 200 | 201 | const oldFirst = r.first, 202 | popped = r.pop(); 203 | 204 | --m.length; 205 | ++m.offset; 206 | 207 | assert.deepEqual( popped, oldFirst ); 208 | 209 | } else { 210 | assert.throws( () => r.pop() ); 211 | } 212 | 213 | } 214 | 215 | } 216 | 217 | 218 | 219 | 220 | 221 | class SetCapacityCommand implements cb_command { 222 | 223 | _sizeSeed : number; 224 | _calcSize? : number; 225 | 226 | constructor(readonly sizeSeed: number) { this._sizeSeed = sizeSeed; } // see https://github.com/dubzzz/fast-check/issues/2136 227 | 228 | toString = () => `set capacity(${this._calcSize ?? 'no size!'})`; 229 | check = (_m: Readonly) => true; // you should always be allowed to resize 230 | 231 | run(m: CbModel, r: circular_buffer): void { 232 | 233 | this._calcSize = (m.capacity === 0)? 0 : this._sizeSeed % m.capacity; 234 | 235 | const newSize = this._calcSize, 236 | was = r.toArray(), 237 | oldSize = was.length; 238 | 239 | r.capacity = newSize; 240 | m.capacity = newSize; 241 | m.length = Math.min(m.length, newSize); 242 | 243 | const nowIs = r.toArray(); 244 | 245 | assert.equal(r.capacity, newSize); 246 | assert.equal(m.capacity, newSize); 247 | 248 | 249 | for (let i=0, iC=Math.min(oldSize, newSize); i `resize(${this._calcSize ?? 'no size!'})`; 269 | check = (_m: Readonly) => true; // you should always be allowed to resize 270 | 271 | run(m: CbModel, r: circular_buffer): void { 272 | 273 | this._calcSize = (m.capacity === 0)? 0 : this._sizeSeed % m.capacity; 274 | 275 | const newSize = this._calcSize, 276 | was = r.toArray(), 277 | oldSize = was.length; 278 | 279 | r.resize(newSize); 280 | m.capacity = newSize; 281 | m.length = Math.min(m.length, newSize); 282 | 283 | assert.equal(r.capacity, newSize, "datastructure size and expected new size match"); 284 | assert.equal(m.capacity, newSize, "model size and expected new size match"); 285 | 286 | 287 | const nowIs = r.toArray(); 288 | 289 | for (let i=0, iC=Math.min(oldSize, newSize); i `resize_end(${this._calcSize ?? 'no size!'},true)`; 309 | check = (_m: Readonly) => true; // you should always be allowed to resize 310 | 311 | run(m: CbModel, r: circular_buffer): void { 312 | 313 | this._calcSize = (m.capacity === 0)? 0 : this._sizeSeed % m.capacity; 314 | 315 | const newSize = this._calcSize, 316 | was = r.toArray(), 317 | oldSize = was.length; 318 | 319 | r.resize(newSize); 320 | m.capacity = newSize; 321 | m.length = Math.min(m.length, newSize); 322 | 323 | const dir = (oldSize < newSize)? 'gr' : 'sh'; 324 | 325 | assert.equal(r.capacity, newSize, `(end ${dir}) datastructure size and expected new size match`); 326 | assert.equal(m.capacity, newSize, `(end ${dir}) model size and expected new size match`); 327 | 328 | 329 | // if it grew or stayed still, offset should be zero 330 | // if it shrank, offset should be positive to the degree of the shrinkage 331 | // const offset = Math.max(0, (oldSize - newSize)); 332 | // const nowIs = r.toArray(); 333 | 334 | // for (let i=0, iC=Math.min(oldSize, newSize); i `set_length(${this._calcSize ?? `seed ${this._sizeSeed}`})`; 354 | check = (_m: Readonly) => true; // you should always be allowed to call set length 355 | 356 | run(m: CbModel, r: circular_buffer): void { 357 | 358 | assert.equal(m.length, r.length); 359 | 360 | this._calcSize = (m.capacity === 0)? 0 : this._sizeSeed % m.capacity; 361 | 362 | const newSize = this._calcSize, 363 | was = r.toArray(), 364 | oldSize = was.length; 365 | 366 | r.length = newSize; 367 | m.length = Math.min(newSize, oldSize); 368 | 369 | const nowIs = r.toArray(); 370 | 371 | assert.equal(r.length, Math.min(newSize, oldSize)); 372 | assert.equal(m.length, Math.min(newSize, oldSize)); 373 | 374 | 375 | for (let i=0, iC=Math.min(oldSize, newSize); i 'get_length'; 390 | check = (_m: Readonly) => true; // you should always be allowed to call get length 391 | 392 | run(m: CbModel, r: circular_buffer): void { 393 | assert.equal(m.length, r.length); 394 | } 395 | 396 | } 397 | 398 | 399 | 400 | 401 | 402 | class EveryCommand implements cb_command { 403 | 404 | toString = () => 'every'; 405 | check = (_m: Readonly) => true; // you should always be allowed to call every 406 | 407 | run(_m: CbModel, r: circular_buffer): void { 408 | 409 | const before = r.toArray(); 410 | assert.equal( r.every( _i => true ), true ); 411 | const after = r.toArray(); 412 | 413 | assert.deepEqual(before, after); 414 | 415 | } 416 | 417 | } 418 | 419 | 420 | 421 | 422 | 423 | class FindCommand implements cb_command { 424 | 425 | toString = () => 'every'; 426 | check = (_m: Readonly) => true; // test the nonsense case length 0 too 427 | 428 | run(_m: CbModel, r: circular_buffer): void { 429 | 430 | const before = r.toArray(); 431 | 432 | if (r.length) { 433 | for (let i=0, iC = r.length; i el === here)).toBe(here); // test that every element may be found 436 | } 437 | } else { 438 | expect(r.find(i => i === 'bob')).toBe(undefined); 439 | } 440 | 441 | const after = r.toArray(); 442 | 443 | assert.deepEqual(before, after); 444 | 445 | } 446 | 447 | } 448 | 449 | 450 | 451 | 452 | 453 | class SomeCommand implements cb_command { 454 | 455 | toString = () => 'some'; 456 | check = (_m: Readonly) => true; // you should always be allowed to call any 457 | 458 | run(_m: CbModel, r: circular_buffer): void { 459 | 460 | const before = r.toArray(); 461 | if (r.length) { assert.equal( r.some( (_i: unknown) => true) , true ); } 462 | const after = r.toArray(); 463 | 464 | if ([ before, after ].length !== 2) { true; } 465 | 466 | assert.deepEqual(before, after); 467 | 468 | } 469 | 470 | } 471 | 472 | 473 | 474 | 475 | 476 | class ReverseCommand implements cb_command { 477 | 478 | toString = () => 'reverse'; 479 | check = (_m: Readonly) => true; // you should always be allowed to call reverse 480 | 481 | run(_m: CbModel, r: circular_buffer): void { 482 | 483 | const before = r.toArray(); 484 | 485 | r.reverse(); 486 | 487 | const after = r.toArray(), 488 | afterRev = after.reverse(); 489 | 490 | assert.deepEqual(before, afterRev); 491 | 492 | } 493 | 494 | } 495 | 496 | 497 | 498 | 499 | 500 | class FirstCommand implements cb_command { 501 | 502 | toString = () => 'first'; 503 | check = (_m: Readonly) => true; // we test underflows, so, allowed at any time 504 | 505 | run(_m: CbModel, r: circular_buffer): void { 506 | if (r.isEmpty) { 507 | assert.throws( () => r.first ); 508 | } else { 509 | assert.doesNotThrow( () => r.first ); 510 | } 511 | } 512 | 513 | } 514 | 515 | 516 | 517 | 518 | 519 | class LastCommand implements cb_command { 520 | 521 | toString = () => 'last'; 522 | check = (_m: Readonly) => true; // we test underflows, so, allowed at any time 523 | 524 | run(_m: CbModel, r: circular_buffer): void { 525 | if (r.isEmpty) { 526 | assert.throws( () => r.last ); 527 | } else { 528 | assert.doesNotThrow( () => r.last ); 529 | } 530 | } 531 | 532 | } 533 | 534 | 535 | 536 | 537 | 538 | class AvailableCommand implements cb_command { 539 | 540 | toString = () => 'available'; 541 | check = (_m: Readonly) => true; // you should always be allowed to call available 542 | 543 | run(m: CbModel, r: circular_buffer): void { 544 | assert.equal(m.capacity - m.length, r.available); 545 | } 546 | 547 | } 548 | 549 | 550 | 551 | 552 | 553 | class FullCommand implements cb_command { 554 | 555 | toString = () => 'full'; 556 | check = (_m: Readonly) => true; // you should always be allowed to call full 557 | 558 | run(m: CbModel, r: circular_buffer): void { 559 | assert.equal(m.length === m.capacity, r.isFull); 560 | } 561 | 562 | } 563 | 564 | 565 | 566 | 567 | 568 | class EmptyCommand implements cb_command { 569 | 570 | toString = () => 'empty'; 571 | check = (_m: Readonly) => true; // you should always be allowed to call empty 572 | 573 | run(m: CbModel, r: circular_buffer): void { 574 | assert.equal(m.length === 0, r.isEmpty); 575 | } 576 | 577 | } 578 | 579 | 580 | 581 | 582 | 583 | class CapacityCommand implements cb_command { 584 | 585 | toString = () => 'capacity'; 586 | check = (_m: Readonly) => true; // you should always be allowed to call capacity 587 | 588 | run(m: CbModel, r: circular_buffer): void { 589 | assert.equal(m.capacity, r.capacity); 590 | } 591 | 592 | } 593 | 594 | 595 | 596 | 597 | 598 | class FillCommand implements cb_command { 599 | 600 | toString = () => 'fill'; 601 | check = (_m: Readonly) => true; // you should always be allowed to call fill 602 | 603 | run(m: CbModel, r: circular_buffer): void { 604 | r.fill( whatever() ); 605 | m.length = r.length; 606 | assert.equal(r.length, r.capacity); 607 | assert.equal(m.length, m.capacity); 608 | } 609 | 610 | } 611 | 612 | 613 | 614 | 615 | 616 | class IndexOfCommand implements cb_command { 617 | 618 | toString = () => 'indexOf'; 619 | check = (_m: Readonly) => true; // test the sane and empty cases both 620 | 621 | run(_m: CbModel, r: circular_buffer): void { 622 | 623 | const was = r.toArray(); 624 | 625 | for (let i=0, iC = r.length; i < iC; ++i) { 626 | 627 | const toMatch = r.at(i), 628 | idx = r.indexOf(toMatch); 629 | 630 | // if they match, the test is already successful 631 | // the goal is to find the matching index of whatever's 632 | // at the cell 633 | // 634 | // there is a valid special case: if this is the 2nd or 635 | // later amongst repeated values. then, the first will 636 | // be found instead. if they don't match, see if that's 637 | // what's happening. 638 | 639 | if (i !== idx) { 640 | assert.deepEqual(r.at(idx), toMatch); 641 | } 642 | 643 | } 644 | 645 | const now = r.toArray(); 646 | assert.deepEqual(was, now); 647 | 648 | } 649 | 650 | } 651 | 652 | 653 | 654 | 655 | 656 | class ClearCommand implements cb_command { 657 | 658 | toString = () => 'clear'; 659 | check = (_m: Readonly) => true; // you should always be allowed to call clear 660 | 661 | run(m: CbModel, r: circular_buffer): void { 662 | 663 | const wlen = r.length, 664 | was = r.clear(); 665 | 666 | m.length = r.length; 667 | 668 | assert.equal(r.length, 0); 669 | assert.equal(m.length, 0); 670 | assert.equal(was.length, wlen); 671 | 672 | } 673 | 674 | } 675 | 676 | 677 | 678 | 679 | 680 | class AtCommand implements cb_command { 681 | 682 | toString = () => 'at'; 683 | check = (_m: Readonly) => true; // tests the empty case so run either way 684 | 685 | run(_m: CbModel, r: circular_buffer): void { 686 | 687 | if (r.isEmpty) { 688 | assert.throws( () => r.at(0) ); 689 | } else { 690 | 691 | for (let e1=0, eC = r.length; e1 < eC; ++e1) { 692 | assert.doesNotThrow( () => r.at(e1) ); 693 | } 694 | 695 | } 696 | 697 | } 698 | 699 | } 700 | 701 | 702 | 703 | 704 | 705 | class PosCommand implements cb_command { 706 | 707 | toString = () => 'pos'; 708 | check = (_m: Readonly) => true; // tests the empty case so run either way 709 | 710 | run(_m: CbModel, r: circular_buffer): void { 711 | 712 | if (r.isEmpty) { 713 | assert.throws( () => r.at(0) ); 714 | } else { 715 | 716 | const ofs = r.offset(); 717 | 718 | for (let e1=0, eC = r.length; e1 < eC; ++e1) { 719 | assert.doesNotThrow( () => r.pos(e1 + ofs) ); // can be looked up 720 | assert.equal( r.pos(e1 + ofs), r.at(e1) ); // matches what .at() says 721 | } 722 | 723 | } 724 | 725 | } 726 | 727 | } 728 | 729 | 730 | 731 | 732 | 733 | class OffsetCommand implements cb_command { 734 | 735 | toString = () => 'offset'; 736 | check = (_m: Readonly) => true; // tests the empty case so run either way 737 | 738 | run(_m: CbModel, r: circular_buffer): void { 739 | assert.doesNotThrow( () => r.offset() ); // can be looked up 740 | if (r.length) { 741 | assert.equal( r.pos(r.offset()), r.at(0) ); // offset matches head 742 | } 743 | } 744 | 745 | } 746 | 747 | 748 | 749 | 750 | 751 | class ToArrayCommand implements cb_command { 752 | 753 | toString = () => 'to_array'; 754 | check = (_m: Readonly) => true; // you should always be allowed to call to_array 755 | 756 | run(m: CbModel, r: circular_buffer): void { 757 | const res = r.toArray(); 758 | assert.equal(r.length, res.length); 759 | assert.equal(m.length, res.length); 760 | } 761 | 762 | } 763 | 764 | 765 | 766 | 767 | 768 | describe('[STOCH] Bad constructor harassment', () => { 769 | 770 | test('Floats', () => { 771 | fc.assert( 772 | fc.property( 773 | fc.nat(), 774 | sz => assert.throws( 775 | () => new circular_buffer( Number.isInteger(sz)? sz+0.1 : sz ) // if an int, non-int it 776 | ) 777 | ) 778 | ); 779 | }); 780 | 781 | test('Non-positive', () => { 782 | fc.assert( 783 | fc.property( 784 | fc.nat(), 785 | sz => { 786 | assert.throws( () => new circular_buffer( sz === 0? -2 : sz * -1 ) ); 787 | } 788 | ) 789 | ); 790 | }); 791 | 792 | test('Inf, NInf, NaN', () => { 793 | assert.throws( () => new circular_buffer( Number.POSITIVE_INFINITY ) ); 794 | assert.throws( () => new circular_buffer( Number.NEGATIVE_INFINITY ) ); 795 | assert.throws( () => new circular_buffer( NaN ) ); 796 | }); 797 | 798 | }); 799 | 800 | 801 | 802 | 803 | 804 | describe('[STOCH] Bad resize harassment', () => { 805 | 806 | test('Floats', () => { 807 | fc.assert( 808 | fc.property( 809 | fc.float(), 810 | sz => assert.throws( 811 | () => { 812 | const cb = new circular_buffer(1); 813 | if (Number.isInteger(sz)) { 814 | cb.length = sz + 0.1; 815 | } else { 816 | cb.length = sz; 817 | } 818 | } 819 | ) 820 | ) 821 | ); 822 | }); 823 | 824 | test('Non-positive', () => { 825 | fc.assert( 826 | fc.property( 827 | fc.nat(), 828 | sz => assert.throws( 829 | () => { 830 | const cb = new circular_buffer(1); 831 | cb.length = sz === 0? -2 : sz * -1; 832 | } 833 | ) 834 | ) 835 | ); 836 | }); 837 | 838 | test('Inf, NInf, NaN', () => { 839 | 840 | assert.throws( () => { 841 | const cb = new circular_buffer(0); 842 | cb.length = Number.POSITIVE_INFINITY; 843 | }); 844 | 845 | assert.throws( () => { 846 | const cb = new circular_buffer(0); 847 | cb.length = Number.NEGATIVE_INFINITY; 848 | }); 849 | 850 | assert.throws( () => { 851 | const cb = new circular_buffer(0); 852 | cb.length = NaN; 853 | }); 854 | 855 | }); 856 | 857 | }); 858 | 859 | 860 | 861 | 862 | 863 | describe('[STOCH] at/1 good calls', () => { 864 | test('Check position <=5000 in container size <= 5000', () => { 865 | 866 | fc.assert( 867 | fc.property( 868 | fc.integer(1, 5000), 869 | fc.integer(0, 5000), 870 | (sz, at) => { 871 | const cb = new circular_buffer(sz); 872 | for (let i=0; i { 887 | 888 | const cb = new circular_buffer(3); 889 | cb.push(1); 890 | 891 | test('Floats', () => { 892 | fc.assert( 893 | fc.property( 894 | fc.float(), 895 | sz => { 896 | fc.pre( !( Number.isInteger(sz) ) ); 897 | assert.throws( () => cb.at( sz ) ); 898 | } 899 | ) 900 | ) 901 | }); 902 | 903 | test('Non-positive', () => { 904 | fc.assert( 905 | fc.property( 906 | fc.nat(), 907 | sz => { 908 | fc.pre(sz !== 0); 909 | assert.throws( () => cb.at( sz * -1 ) ); 910 | } 911 | ) 912 | ); 913 | }); 914 | 915 | test('Over-capacity lookup', () => { 916 | assert.throws( () => cb.at( 4 ) ); 917 | }); 918 | 919 | test('Over-length lookup', () => { 920 | assert.throws( () => cb.at( 2 ) ); 921 | }); 922 | 923 | test('Inf, NInf, 0, NaN', () => { 924 | assert.throws( () => cb.at( Number.POSITIVE_INFINITY ) ); 925 | assert.throws( () => cb.at( Number.NEGATIVE_INFINITY ) ); 926 | assert.throws( () => cb.at( NaN ) ); 927 | }); 928 | 929 | }); 930 | 931 | 932 | 933 | 934 | 935 | // describe('[STOCH] pos/1 good calls', () => { 936 | // test('Check position <=5000 in container size <= 5000', () => { 937 | 938 | // fc.assert( 939 | // fc.property( 940 | // fc.integer(1, 5000), 941 | // fc.integer(0, 5000), 942 | // (sz, at) => { 943 | // const cb = new circular_buffer(sz); 944 | // for (let i=0; i { 959 | 960 | const MaxCommandCount = 100, 961 | MinBufferSize = 1, 962 | 963 | TinyRunCount = 1000, 964 | SmallRunCount = 200, 965 | RegularRunCount = 40, 966 | LargeRunCount = 8, 967 | 968 | TinyMaxBufferSize = 1, 969 | SmallMaxBufferSize = 5, 970 | RegularMaxBufferSize = 50, 971 | LargeMaxBufferSize = 500; 972 | 973 | const PushARandomInteger = fc.integer().map(v => new PushCommand(v) ), 974 | ShoveARandomInteger = fc.integer().map(v => new ShoveCommand(v) ), 975 | Resize = fc.nat().map( v => new ResizeCommand(v) ), 976 | ResizeEnd = fc.nat().map( v => new ResizeEndCommand(v) ), 977 | SetCapacity = fc.nat().map( v => new SetCapacityCommand(v) ), 978 | SetLength = fc.nat().map( v => new SetLengthCommand(v) ), 979 | GetLength = fc.constant( new GetLengthCommand() ), 980 | Pop = fc.constant( new PopCommand() ), 981 | Every = fc.constant( new EveryCommand() ), 982 | Find = fc.constant( new FindCommand() ), 983 | Some = fc.constant( new SomeCommand() ), 984 | Reverse = fc.constant( new ReverseCommand() ), 985 | Available = fc.constant( new AvailableCommand() ), 986 | Capacity = fc.constant( new CapacityCommand() ), 987 | At = fc.constant( new AtCommand() ), 988 | Pos = fc.constant( new PosCommand() ), 989 | Offset = fc.constant( new OffsetCommand() ), 990 | ToArray = fc.constant( new ToArrayCommand() ), 991 | Fill = fc.constant( new FillCommand() ), 992 | IndexOf = fc.constant( new IndexOfCommand() ), 993 | Clear = fc.constant( new ClearCommand() ), 994 | Full = fc.constant( new FullCommand() ), 995 | Empty = fc.constant( new EmptyCommand() ), 996 | First = fc.constant( new FirstCommand() ), 997 | Last = fc.constant( new LastCommand() ); 998 | 999 | const AllCommands = [ PushARandomInteger, ShoveARandomInteger, Pop, GetLength, SetLength, SetCapacity, Every, Find, Some, Reverse, Available, Capacity, At, Pos, Offset, Resize, ResizeEnd, ToArray, Fill, IndexOf, Clear, Full, Empty, First, Last ], 1000 | AllCommandNames = `PushARandomInteger, ShoveARandomInteger, Pop, GetLength, SetLength, SetCapacity, Every, Find, Some, Reverse, Available, Capacity, At, Pos, Offset, Resize, ResizeEnd, ToArray, Fill, IndexOf, Clear, Full, Empty, First, Last`, 1001 | CommandGenerator = fc.commands(AllCommands, MaxCommandCount); 1002 | 1003 | // define the possible commands and their inputs 1004 | 1005 | const TinySizeGenerator : fc.ArbitraryWithShrink = fc.integer(MinBufferSize, TinyMaxBufferSize), 1006 | SmallSizeGenerator : fc.ArbitraryWithShrink = fc.integer(MinBufferSize, SmallMaxBufferSize), 1007 | RegularSizeGenerator : fc.ArbitraryWithShrink = fc.integer(MinBufferSize, RegularMaxBufferSize), 1008 | LargeSizeGenerator : fc.ArbitraryWithShrink = fc.integer(MinBufferSize, LargeMaxBufferSize); 1009 | 1010 | type guideSize = [ fc.ArbitraryWithShrink, number, number ]; 1011 | 1012 | const Sizes: guideSize[] = [ 1013 | [ TinySizeGenerator, TinyRunCount, TinyMaxBufferSize ], 1014 | [ SmallSizeGenerator, SmallRunCount, SmallMaxBufferSize ], 1015 | [ RegularSizeGenerator, RegularRunCount, RegularMaxBufferSize ], 1016 | [ LargeSizeGenerator, LargeRunCount, LargeMaxBufferSize ] 1017 | ]; 1018 | 1019 | 1020 | const CommandInstance = (sz: number, cmds: Iterable) => { 1021 | 1022 | const s = () => 1023 | ({ model : { length: 0, offset: 0, capacity: sz }, 1024 | real : new circular_buffer(sz) } 1025 | ); 1026 | 1027 | fc.modelRun(s, cmds); 1028 | 1029 | }; 1030 | 1031 | 1032 | 1033 | describe(AllCommandNames, () => { 1034 | 1035 | function test_generator(SizeGenerator: fc.ArbitraryWithShrink, RunCount: number, MaxBufferSize: number) { 1036 | 1037 | return test(`size <= ${MaxBufferSize}, ${RunCount} runs <= ${MaxCommandCount} commands`, () => { 1038 | 1039 | // run everything on small sizes 1040 | fc.assert( 1041 | fc.property(SizeGenerator, CommandGenerator, CommandInstance), 1042 | { numRuns: RunCount, verbose: true } 1043 | ); 1044 | 1045 | }); 1046 | 1047 | } 1048 | 1049 | Sizes.map( 1050 | ([SzGen, RcGen, MxBufSz]) => test_generator(SzGen, RcGen, MxBufSz) 1051 | ); 1052 | 1053 | }); 1054 | 1055 | 1056 | }); 1057 | 1058 | 1059 | 1060 | 1061 | 1062 | 1063 | describe('[STOCH] version', () => { 1064 | test('Version is present', () => expect(typeof version).toBe('string')); 1065 | test('Version is non-empty', () => expect(version.length > 0).toBe(true)); 1066 | }); 1067 | --------------------------------------------------------------------------------