Browse Source

08: Circular Buffer.

Frédéric G. MARAND 1 month ago
parent
commit
2f4889e2f7

+ 13 - 0
circular-buffer/.eslintignore

@@ -0,0 +1,13 @@
+!.meta
+
+# Protected or generated
+.git
+.vscode
+
+# When using npm
+node_modules/*
+
+# Configuration files
+.eslintrc.cjs
+babel.config.cjs
+jest.config.cjs

+ 38 - 0
circular-buffer/.eslintrc.cjs

@@ -0,0 +1,38 @@
+module.exports = {
+  root: true,
+  parserOptions: {
+    tsconfigRootDir: __dirname,
+    project: ['./tsconfig.json'],
+  },
+  overrides: [
+    // Student provided files
+    {
+      files: ['*.ts'],
+      excludedFiles: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
+      extends: '@exercism/eslint-config-typescript',
+    },
+    // Exercism given tests
+    {
+      files: ['*.test.ts'],
+      excludedFiles: ['custom.test.ts'],
+      env: {
+        jest: true,
+      },
+      extends: '@exercism/eslint-config-typescript/maintainers',
+    },
+    // Student provided tests
+    {
+      files: ['custom.test.ts'],
+      env: {
+        jest: true,
+      },
+      extends: '@exercism/eslint-config-typescript',
+    },
+    // Exercism provided files
+    {
+      files: ['.meta/proof.ci.ts', '.meta/exemplar.ts', '*.test.ts'],
+      excludedFiles: ['custom.test.ts'],
+      extends: '@exercism/eslint-config-typescript/maintainers',
+    },
+  ],
+}

+ 23 - 0
circular-buffer/.exercism/config.json

@@ -0,0 +1,23 @@
+{
+  "authors": [
+    "jspengeman"
+  ],
+  "contributors": [
+    "masters3d",
+    "SleeplessByte"
+  ],
+  "files": {
+    "solution": [
+      "circular-buffer.ts"
+    ],
+    "test": [
+      "circular-buffer.test.ts"
+    ],
+    "example": [
+      ".meta/proof.ci.ts"
+    ]
+  },
+  "blurb": "A data structure that uses a single, fixed-size buffer as if it were connected end-to-end.",
+  "source": "Wikipedia",
+  "source_url": "https://en.wikipedia.org/wiki/Circular_buffer"
+}

+ 1 - 0
circular-buffer/.exercism/metadata.json

@@ -0,0 +1 @@
+{"track":"typescript","exercise":"circular-buffer","id":"cd8de89c713747cdaa5534903bc74176","url":"https://exercism.org/tracks/typescript/exercises/circular-buffer","handle":"Fairgame","is_requester":true,"auto_approve":false}

File diff suppressed because it is too large
+ 3 - 0
circular-buffer/.yarn/releases/yarn-3.6.4.cjs


+ 45 - 0
circular-buffer/HELP.md

@@ -0,0 +1,45 @@
+# Help
+
+## Running the tests
+
+Execute the tests with:
+
+```bash
+$ yarn test
+```
+
+## Skipped tests
+
+In the test suites all tests but the first have been skipped.
+
+Once you get a test passing, you can enable the next one by changing `xit` to
+`it`.
+
+## Submitting your solution
+
+You can submit your solution using the `exercism submit circular-buffer.ts` command.
+This command will upload your solution to the Exercism website and print the solution page's URL.
+
+It's possible to submit an incomplete solution which allows you to:
+
+- See how others have completed the exercise
+- Request help from a mentor
+
+## Need to get help?
+
+If you'd like help solving the exercise, check the following pages:
+
+- The [TypeScript track's documentation](https://exercism.org/docs/tracks/typescript)
+- The [TypeScript track's programming category on the forum](https://forum.exercism.org/c/programming/typescript)
+- [Exercism's programming category on the forum](https://forum.exercism.org/c/programming/5)
+- The [Frequently Asked Questions](https://exercism.org/docs/using/faqs)
+
+Should those resources not suffice, you could submit your (incomplete) solution to request mentoring.
+
+To get help if you're having trouble, you can use one of the following resources:
+
+- [TypeScript QuickStart](https://www.typescriptlang.org/docs/handbook/release-notes/overview.html)
+- [ECMAScript 2015 Language Specification](https://www.ecma-international.org/wp-content/uploads/ECMA-262_6th_edition_june_2015.pdf) (pdf)
+- [Mozilla JavaScript Reference](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference)
+- [/r/typescript](https://www.reddit.com/r/typescript) is the TypeScript subreddit.
+- [StackOverflow](https://stackoverflow.com/questions/tagged/typescript) can be used to search for your problem and see if it has been answered already. You can also ask and answer questions.

+ 78 - 0
circular-buffer/README.md

@@ -0,0 +1,78 @@
+# Circular Buffer
+
+Welcome to Circular Buffer on Exercism's TypeScript Track.
+If you need help running the tests or submitting your code, check out `HELP.md`.
+
+## Instructions
+
+A circular buffer, cyclic buffer or ring buffer is a data structure that uses a single, fixed-size buffer as if it were connected end-to-end.
+
+A circular buffer first starts empty and of some predefined length.
+For example, this is a 7-element buffer:
+
+```text
+[ ][ ][ ][ ][ ][ ][ ]
+```
+
+Assume that a 1 is written into the middle of the buffer (exact starting location does not matter in a circular buffer):
+
+```text
+[ ][ ][ ][1][ ][ ][ ]
+```
+
+Then assume that two more elements are added — 2 & 3 — which get appended after the 1:
+
+```text
+[ ][ ][ ][1][2][3][ ]
+```
+
+If two elements are then removed from the buffer, the oldest values inside the buffer are removed.
+The two elements removed, in this case, are 1 & 2, leaving the buffer with just a 3:
+
+```text
+[ ][ ][ ][ ][ ][3][ ]
+```
+
+If the buffer has 7 elements then it is completely full:
+
+```text
+[5][6][7][8][9][3][4]
+```
+
+When the buffer is full an error will be raised, alerting the client that further writes are blocked until a slot becomes free.
+
+When the buffer is full, the client can opt to overwrite the oldest data with a forced write.
+In this case, two more elements — A & B — are added and they overwrite the 3 & 4:
+
+```text
+[5][6][7][8][9][A][B]
+```
+
+3 & 4 have been replaced by A & B making 5 now the oldest data in the buffer.
+Finally, if two elements are removed then what would be returned is 5 & 6 yielding the buffer:
+
+```text
+[ ][ ][7][8][9][A][B]
+```
+
+Because there is space available, if the client again uses overwrite to store C & D then the space where 5 & 6 were stored previously will be used not the location of 7 & 8.
+7 is still the oldest element and the buffer is once again full.
+
+```text
+[C][D][7][8][9][A][B]
+```
+
+## Source
+
+### Created by
+
+- @jspengeman
+
+### Contributed to by
+
+- @masters3d
+- @SleeplessByte
+
+### Based on
+
+Wikipedia - https://en.wikipedia.org/wiki/Circular_buffer

+ 4 - 0
circular-buffer/babel.config.cjs

@@ -0,0 +1,4 @@
+module.exports = {
+  presets: ['@exercism/babel-preset-typescript'],
+  plugins: [],
+}

+ 106 - 0
circular-buffer/circular-buffer.test.ts

@@ -0,0 +1,106 @@
+import CircularBuffer, {
+  BufferFullError,
+  BufferEmptyError,
+} from './circular-buffer'
+
+describe('CircularBuffer', () => {
+  it('reading an empty buffer throws a BufferEmptyError', () => {
+    const buffer = new CircularBuffer<string>(1)
+    expect(() => buffer.read()).toThrow(BufferEmptyError)
+  })
+
+  it('write and read back one item', () => {
+    const buffer = new CircularBuffer<string>(1)
+    buffer.write('1')
+    expect(buffer.read()).toBe('1')
+    expect(() => buffer.read()).toThrow(BufferEmptyError)
+  })
+
+  it('write and read back multiple items', () => {
+    const buffer = new CircularBuffer<string>(2)
+    buffer.write('1')
+    buffer.write('2')
+    expect(buffer.read()).toBe('1')
+    expect(buffer.read()).toBe('2')
+    expect(() => buffer.read()).toThrow(BufferEmptyError)
+  })
+
+  it('clearing a buffer', () => {
+    const buffer = new CircularBuffer<string>(2)
+    buffer.write('1')
+    buffer.write('2')
+    buffer.clear()
+    expect(() => buffer.read()).toThrow(BufferEmptyError)
+    buffer.write('3')
+    buffer.write('4')
+    expect(buffer.read()).toBe('3')
+    expect(buffer.read()).toBe('4')
+  })
+
+  it('alternate write and read', () => {
+    const buffer = new CircularBuffer<string>(2)
+    buffer.write('1')
+    expect(buffer.read()).toBe('1')
+    buffer.write('2')
+    expect(buffer.read()).toBe('2')
+  })
+
+  it('reads back oldest item', () => {
+    const buffer = new CircularBuffer<string>(3)
+    buffer.write('1')
+    buffer.write('2')
+    buffer.read()
+    buffer.write('3')
+    expect(buffer.read()).toBe('2')
+    expect(buffer.read()).toBe('3')
+  })
+
+  it('writing to a full buffer throws a BufferFullError', () => {
+    const buffer = new CircularBuffer<string>(2)
+    buffer.write('1')
+    buffer.write('2')
+    expect(() => buffer.write('A')).toThrow(BufferFullError)
+  })
+
+  it('forced writes over write oldest item in a full buffer', () => {
+    const buffer = new CircularBuffer<string>(2)
+    buffer.write('1')
+    buffer.write('2')
+    buffer.forceWrite('A')
+    expect(buffer.read()).toBe('2')
+    expect(buffer.read()).toBe('A')
+    expect(() => buffer.read()).toThrow(BufferEmptyError)
+  })
+
+  it('forced writes act like write in a non-full buffer', () => {
+    const buffer = new CircularBuffer<string>(2)
+    buffer.write('1')
+    buffer.forceWrite('2')
+    expect(buffer.read()).toBe('1')
+    expect(buffer.read()).toBe('2')
+    expect(() => buffer.read()).toThrow(BufferEmptyError)
+  })
+
+  it('alternate force write and read into full buffer', () => {
+    const buffer = new CircularBuffer<string>(5)
+    buffer.write('1')
+    buffer.write('2')
+    buffer.write('3')
+    buffer.read()
+    buffer.read()
+    buffer.write('4')
+    buffer.read()
+    buffer.write('5')
+    buffer.write('6')
+    buffer.write('7')
+    buffer.write('8')
+    buffer.forceWrite('A')
+    buffer.forceWrite('B')
+    expect(buffer.read()).toBe('6')
+    expect(buffer.read()).toBe('7')
+    expect(buffer.read()).toBe('8')
+    expect(buffer.read()).toBe('A')
+    expect(buffer.read()).toBe('B')
+    expect(() => buffer.read()).toThrow(BufferEmptyError)
+  })
+})

+ 56 - 0
circular-buffer/circular-buffer.ts

@@ -0,0 +1,56 @@
+export class BufferFullError extends Error {
+}
+
+export class BufferEmptyError extends Error {
+}
+
+export default class CircularBuffer<T> {
+    protected base: number;
+    protected cap: number;
+    protected data: Array<any>
+    protected len: number;
+
+    constructor(initial: number) {
+        if (initial % 1 !== 0) {
+            throw new Error("initial capacity must be an integer");
+        }
+        this.base = 0;
+        this.cap = initial;
+        this.data = new Array<any>(this.cap)
+        this.len = 0;
+    }
+
+    write(value: any): void {
+        if (this.len === this.cap) {
+            throw new BufferFullError();
+        }
+        this.data[(this.base + this.len) % this.cap] = value;
+        this.len++;
+    }
+
+    read(): any {
+        if (this.len === 0) {
+            throw new BufferEmptyError();
+        }
+        const value = this.data[this.base];
+        this.base = (this.base + 1) % this.cap;
+        this.len--;
+        return value;
+    }
+
+    forceWrite(value: any): void {
+        this.data[(this.base + this.len) % this.cap] = value;
+        if (this.cap === this.len) {
+            this.base = (this.base + 1) % this.cap;
+            return;
+        }
+        this.len++;
+    }
+
+    clear(): void {
+        this.base = 0;
+        this.data = new Array<any>(this.cap)
+        this.len = 0;
+    }
+}
+

+ 19 - 0
circular-buffer/jest.config.cjs

@@ -0,0 +1,19 @@
+module.exports = {
+  verbose: true,
+  projects: ['<rootDir>'],
+  testMatch: [
+    '**/__tests__/**/*.[jt]s?(x)',
+    '**/test/**/*.[jt]s?(x)',
+    '**/?(*.)+(spec|test).[jt]s?(x)',
+  ],
+  testPathIgnorePatterns: [
+    '/(?:production_)?node_modules/',
+    '.d.ts$',
+    '<rootDir>/test/fixtures',
+    '<rootDir>/test/helpers',
+    '__mocks__',
+  ],
+  transform: {
+    '^.+\\.[jt]sx?$': 'babel-jest',
+  },
+}

+ 32 - 0
circular-buffer/package.json

@@ -0,0 +1,32 @@
+{
+  "name": "@exercism/typescript-circular-buffer",
+  "version": "1.0.0",
+  "description": "Exercism exercises in Typescript.",
+  "private": true,
+  "repository": {
+    "type": "git",
+    "url": "https://github.com/exercism/typescript"
+  },
+  "type": "module",
+  "engines": {
+    "node": "^18.16.0 || >=20.0.0"
+  },
+  "devDependencies": {
+    "@exercism/babel-preset-typescript": "^0.4.0",
+    "@exercism/eslint-config-typescript": "^0.5.0",
+    "@types/jest": "^29.5.2",
+    "@types/node": "~18.16.16",
+    "babel-jest": "^29.5.0",
+    "core-js": "~3.30.2",
+    "eslint": "^8.42.0",
+    "jest": "^29.5.0",
+    "typescript": "~5.0.4"
+  },
+  "scripts": {
+    "test": "yarn lint:types && jest --no-cache",
+    "lint": "yarn lint:types && yarn lint:ci",
+    "lint:types": "yarn tsc --noEmit -p .",
+    "lint:ci": "eslint . --ext .tsx,.ts"
+  },
+  "packageManager": "yarn@3.6.4"
+}

+ 28 - 0
circular-buffer/tsconfig.json

@@ -0,0 +1,28 @@
+{
+  "display": "Configuration for Exercism TypeScript Exercises",
+  "compilerOptions": {
+    // Allows you to use the newest syntax, and have access to console.log
+    // https://www.typescriptlang.org/tsconfig#lib
+    "lib": ["ESNEXT", "dom"],
+    // Make sure typescript is configured to output ESM
+    // https://gist.github.com/sindresorhus/a39789f98801d908bbc7ff3ecc99d99c#how-can-i-make-my-typescript-project-output-esm
+    "module": "ES2020",
+    // Since this project is using babel, TypeScript may target something very
+    // high, and babel will make sure it runs on your local Node version.
+    // https://babeljs.io/docs/en/
+    "target": "ESNext", // ESLint doesn't support this yet: "es2022",
+
+    "strict": true,
+    "esModuleInterop": true,
+    "skipLibCheck": true,
+    "forceConsistentCasingInFileNames": true,
+
+    // Because we'll be using babel: ensure that Babel can safely transpile
+    // files in the TypeScript project.
+    //
+    // https://babeljs.io/docs/en/babel-plugin-transform-typescript/#caveats
+    "isolatedModules": true
+  },
+  "include": ["*.ts", "*.tsx", ".meta/*.ts", ".meta/*.tsx"],
+  "exclude": ["node_modules"]
+}

+ 15 - 5
package.json

@@ -1,10 +1,20 @@
 {
-  "dependencies": {
-    "typescript": "^5.3.3"
-  },
   "devDependencies": {
     "@exercism/babel-preset-typescript": "^0.4.0",
+    "@exercism/eslint-config-typescript": "^0.6.0",
     "@types/jest": "^29.5.12",
-    "jest": "^29.7.0"
-  }
+    "@types/node": "~18.16.16",
+    "babel-jest": "^29.5.0",
+    "core-js": "~3.30.2",
+    "eslint": "^8.42.0",
+    "jest": "^29.7.0",
+    "typescript": "^5.4.2"
+  },
+  "scripts": {
+    "test": "yarn lint:types && jest --no-cache",
+    "lint": "yarn lint:types && yarn lint:ci",
+    "lint:types": "yarn tsc --noEmit -p .",
+    "lint:ci": "eslint . --ext .tsx,.ts"
+  },
+  "packageManager": "yarn@3.6.4"
 }

File diff suppressed because it is too large
+ 703 - 7
yarn.lock


Some files were not shown because too many files changed in this diff