Skip to content

Commit

Permalink
fix(fifo-cache): fix remove implementation, update docs
Browse files Browse the repository at this point in the history
  • Loading branch information
zhibirc committed Dec 8, 2023
1 parent 7b6c419 commit 135e459
Show file tree
Hide file tree
Showing 6 changed files with 136 additions and 66 deletions.
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@

## Table of Contents

[First-In-First-Out (FIFO) Cache](./fifo-cache/)
[Random Replacement (RR) Cache](./rr-cache/)
🐢 [First-In-First-Out (FIFO) Cache](./fifo-cache/)

🐝 [Random Replacement (RR) Cache](./rr-cache/)

## Motivation

Expand Down
50 changes: 30 additions & 20 deletions fifo-cache/index.d.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,37 @@
interface IFifoCache {
get size(): number;
set capacity(value: number);
read: (key: any) => any;
store: (key: any, value: any) => void;
has: (key: any) => boolean;
clear: () => void;
}
type TConfigOptions = {
import { ICache } from '../libs/types.js';
declare class FifoCache implements ICache {
#private;
constructor(capacity: number);
get stats(): {
size: number;
capacity: number;
locked: boolean;
hitRatio: number;
};
set lock(state: boolean);
/**
* Capacity means how many items can be stored at the same time in cache.
* For FIFO cache, by definition, capacity is a required restriction,
* without it becomes almost meaningless, so this option is mandatory.
* Read value stored in cache by assosiated key.
* @param {*} key - cache record's key
* @return {*|void} record's value retrieved by key or undefined if record is absent
*/
capacity: number;
};
declare class FifoCache implements IFifoCache {
#private;
constructor(options: TConfigOptions);
get size(): number;
set capacity(value: number);
read(key: any): any;
store(key: any, value: any): void;
add(key: any, value: any): void;
/**
* Check if record by given key exists in cache.
* @param {*} key - cache record's key
* @return {boolean} return true if record is in the cache
*/
has(key: any): boolean;
/**
* Remove an item from the cache.
* @param {*} key - cache record's key
* @return {void}
*/
remove(key: any): void;
/**
* Remove all items from the cache and clear internal structures.
* @return {void}
*/
clear(): void;
}
export default FifoCache;
114 changes: 82 additions & 32 deletions fifo-cache/index.js
Original file line number Diff line number Diff line change
@@ -1,65 +1,115 @@
;
const Node = (data, next) => ({ data, next });
const Node = (data, next = null, prev = null) => ({ data, next, prev });
class FifoCache {
#hits;
#misses;
#capacity;
#store;
#locked;
#head;
#tail;
#map;
constructor(options) {
const { capacity } = options;
if (!Number.isInteger(capacity) || capacity <= 0)
constructor(capacity) {
if (!Number.isInteger(capacity) || capacity <= 0) {
throw new Error('invalid "capacity": positive integer expected');
}
this.#hits = 0;
this.#misses = 0;
this.#capacity = capacity;
// Store is a Singly linked list to support fast add (to tail) and delete (from head) records.
this.#store = { head: null, tail: null };
// Struct for fast access to cache records, use as <key:reference> lookup table.
this.#locked = false;
// store is a Doubly Linked List to support fast add (to tail) and delete (from head) records,
// as well as effective deletion
this.#head = null;
this.#tail = null;
// struct for fast access to cache records, use as <key:node> lookup table
// to compensate O(N) bottleneck to search node in such list
this.#map = new Map();
}
get size() {
return this.#map.size;
get stats() {
return {
size: this.#map.size,
capacity: this.#capacity,
locked: this.#locked,
hitRatio: this.#hits / (this.#hits + this.#misses)
};
}
set capacity(value) {
if (!Number.isInteger(value) || value <= 0)
throw new Error('invalid "capacity": positive integer expected');
if (value < this.#capacity) {
// @todo: implement
}
this.#capacity = value;
set lock(state) {
this.#locked = Boolean(state);
}
/**
* Read value stored in cache by assosiated key.
* @param {*} key - cache record's key
* @return {*|void} record's value retrieved by key or undefined if record is absent
*/
read(key) {
if (this.#map.has(key)) {
return this.#map.get(key)?.data.value;
this.#hits += 1;
return this.#map.get(key).data.value;
}
return null;
this.#misses += 1;
}
store(key, value) {
add(key, value) {
// check if cache capacity limit is reached
if (this.#map.size === this.#capacity) {
// evict head since we are out of capacity
const node = this.#store.head;
this.#store.head = node.next;
this.#map.delete(node.data.key);
node.next = null;
const head = this.#head;
this.#head = head.next;
head.next = null;
this.#head.prev = null;
this.#map.delete(head.data.key);
}
const node = Node({ key, value });
if (this.#map.size === 0) {
this.#store.head = node;
this.#head = node;
}
else if (this.#map.size === 1) {
this.#store.tail = node;
this.#store.head.next = this.#store.tail;
this.#tail = node;
this.#head.next = this.#tail;
this.#tail.prev = this.#head;
}
else {
this.#store.tail.next = node;
this.#store.tail = node;
this.#tail.next = node;
node.prev = this.#tail;
this.#tail = node;
}
this.#map.set(key, node);
}
/**
* Check if record by given key exists in cache.
* @param {*} key - cache record's key
* @return {boolean} return true if record is in the cache
*/
has(key) {
return this.#map.has(key);
}
/**
* Remove an item from the cache.
* @param {*} key - cache record's key
* @return {void}
*/
remove(key) {
if (this.#map.has(key)) {
const node = this.#map.get(key);
if (node === this.#head) {
this.#head = node.next;
this.#head.prev = null;
}
else if (node === this.#tail) {
this.#tail = node.prev;
this.#tail.next = null;
}
else {
node.prev.next = node.next;
node.next.prev = node.prev;
}
this.#map.delete(key);
}
}
/**
* Remove all items from the cache and clear internal structures.
* @return {void}
*/
clear() {
this.#store.head = null;
this.#store.tail = null;
this.#hits = this.#misses = 0;
this.#head = this.#tail = null;
this.#map.clear();
}
}
Expand Down
21 changes: 15 additions & 6 deletions fifo-cache/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ class FifoCache implements ICache {
this.#capacity = capacity;
this.#locked = false;

// store is a Singly Linked List to support fast add (to tail) and delete (from head) records
// store is a Doubly Linked List to support fast add (to tail) and delete (from head) records,
// as well as effective deletion
this.#head = null;
this.#tail = null;
// struct for fast access to cache records, use as <key:reference> lookup table
// struct for fast access to cache records, use as <key:node> lookup table
// to compensate O(N) bottleneck to search node in such list
this.#map = new Map();
}

Expand Down Expand Up @@ -114,10 +116,17 @@ class FifoCache implements ICache {
if (this.#map.has(key)) {
const node = this.#map.get(key);

node.prev.next = node.next;
node.next.prev = node.prev;
node.next = null;
node.prev = null;
if (node === this.#head) {
this.#head = node.next;
this.#head.prev = null;
} else if (node === this.#tail) {
this.#tail = node.prev;
this.#tail.next = null;
} else {
node.prev.next = node.next;
node.next.prev = node.prev;
}

this.#map.delete(key);
}
}
Expand Down
4 changes: 2 additions & 2 deletions rr-cache/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ declare class RRCache implements ICache {
/**
* Read value stored in cache by assosiated key.
* @param {*} key - cache record's key
* @return {*|null} record's value retrieved by key or null if record is absent
* @return {*|void} record's value retrieved by key or undefined if record is absent
*/
read(key: any): any;
add(key: any, value: any): void;
Expand All @@ -29,7 +29,7 @@ declare class RRCache implements ICache {
*/
remove(key: any): void;
/**
* Remove all items from the cache.
* Remove all items from the cache and clear internal structures.
* @return {void}
*/
clear(): void;
Expand Down
8 changes: 4 additions & 4 deletions rr-cache/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -27,20 +27,19 @@ class RRCache {
};
}
set lock(state) {
this.#locked = state;
this.#locked = Boolean(state);
}
/**
* Read value stored in cache by assosiated key.
* @param {*} key - cache record's key
* @return {*|null} record's value retrieved by key or null if record is absent
* @return {*|void} record's value retrieved by key or undefined if record is absent
*/
read(key) {
if (this.#store.has(key)) {
this.#hits += 1;
return this.#store.get(key).value;
}
this.#misses += 1;
return null;
}
add(key, value) {
if (this.#locked)
Expand Down Expand Up @@ -82,10 +81,11 @@ class RRCache {
}
}
/**
* Remove all items from the cache.
* Remove all items from the cache and clear internal structures.
* @return {void}
*/
clear() {
this.#hits = this.#misses = 0;
this.#keys.length = this.#freeSlots.length = 0;
this.#keys.length = this.#capacity;
this.#store.clear();
Expand Down

0 comments on commit 135e459

Please sign in to comment.