Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .eslintrc.cjs
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ module.exports = {
},
},
rules: {
'no-bitwise': 0,
'prefer-const': 0,
'no-param-reassign': 0,
'no-restricted-syntax': 0,
Expand Down
42 changes: 0 additions & 42 deletions src/data-structures/cache/lfu/index.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,28 +10,6 @@ describe('LFUCache', () => {
});
it('returns initial state correctly', () => {
expect(cache.size).toBe(0);
expect(cache.toArray()).toEqual([]);
expect(cache.isEmpty).toBeTruthy();
});

describe('toArray', () => {
it('returns values of items', () => {
// Arrange
cache.put('a', 1);
cache.put('b', 2);

// Act and Assert
expect(cache.toArray()).toEqual([1, 2]);
});

it('returns keys of items', () => {
// Arrange
cache.put('a', 1);
cache.put('b', 2);

// Act and Assert
expect(cache.toArray(({ key }) => key)).toEqual(['a', 'b']);
});
});

describe('put', () => {
Expand All @@ -40,7 +18,6 @@ describe('LFUCache', () => {
cache.put('a', 1);

// Assert
expect(cache.toArray()).toEqual([1]);
expect(cache.size).toBe(1);
});

Expand All @@ -49,7 +26,6 @@ describe('LFUCache', () => {
cache.put('a', 1);

// Assert
expect(cache.toArray()).toEqual([1]);
expect(cache.size).toBe(1);
});

Expand All @@ -61,7 +37,6 @@ describe('LFUCache', () => {
cache.put('a', 2);

// Assert
expect(cache.toArray()).toEqual([2]);
expect(cache.size).toBe(1);
});

Expand All @@ -85,7 +60,6 @@ describe('LFUCache', () => {
// [2] -> 1, 2

// Assert
expect(lfu.toArray()).toEqual([4, 1, 2]);
expect(lfu.size).toBe(3);
});
});
Expand All @@ -106,7 +80,6 @@ describe('LFUCache', () => {

// Act and Assert
expect(cache.get('a')).toBe(1);
expect(cache.toArray()).toEqual([1]);
expect(cache.size).toBe(1);
});

Expand All @@ -129,7 +102,6 @@ describe('LFUCache', () => {
// [2] -> 1, 2

// Assert
expect(lfu.toArray()).toEqual([4, 1, 2]);
expect(lfu.get('c')).toBeNull();
expect(lfu.get('d')).toBe(4);
expect(lfu.get('a')).toBe(1);
Expand All @@ -145,24 +117,10 @@ describe('LFUCache', () => {
lfu.put('b', 2);
lfu.put('c', 3);

expect(lfu.toArray()).toEqual([1, 2, 3]);
expect(lfu.isEmpty).toBeFalsy();

// Act
lfu.clear();

// Assert
expect(lfu.toArray()).toEqual([]);
expect(lfu.isEmpty).toBeTruthy();
expect(lfu.size).toBe(0);
});

describe('toStringTag', () => {
it('returns correct string representation', () => {
// Assert
expect(Object.prototype.toString.call(new LFUCache(6))).toBe(
'[object LFUCache]',
);
});
});
});
82 changes: 41 additions & 41 deletions src/data-structures/cache/lfu/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,19 +20,6 @@ export class LFUCache<Key extends keyof any, Value>
return this.#storage.size;
}

toArray<T>(
callbackfn: (item: Payload<Key, Value>) => T = (item) =>
item.value as unknown as T,
): T[] {
return Object.values(this.#storage.store)
.flatMap((list) => list?.toArray())
.map(callbackfn);
}

get isEmpty() {
return this.toArray().length === 0;
}

put(key: Key, value: Value) {
const storage = this.#storage;

Expand All @@ -41,7 +28,7 @@ export class LFUCache<Key extends keyof any, Value>
}

if (storage.size === this.#capacity) {
storage.evictLeastFrequentItem();
storage.evictLeastFrequentlyUsed();
}

storage.setItem(key, value);
Expand All @@ -65,13 +52,10 @@ export class LFUCache<Key extends keyof any, Value>
clear() {
this.#storage.clear();
}

// eslint-disable-next-line class-methods-use-this
get [Symbol.toStringTag]() {
return 'LFUCache';
}
}

const INITIAL_MIN_FREQUENCY_VALUE = 1;

class Storage<Key extends keyof any, Value> {
#keyFrequencyMap = new Map<Key, number>();

Expand All @@ -82,9 +66,7 @@ class Storage<Key extends keyof any, Value> {

#keyNodeMap = new Map<Key, DoublyLinkedListNode<Payload<Key, Value>>>();

#INITIAL_MIN_FREQUENCY_VALUE = 1;

#currentMinFrequency = this.#INITIAL_MIN_FREQUENCY_VALUE;
#currentMinFrequency = INITIAL_MIN_FREQUENCY_VALUE;

get size() {
return this.#keyNodeMap.size;
Expand All @@ -93,16 +75,26 @@ class Storage<Key extends keyof any, Value> {
get store() {
return this.#frequencyBuckets;
}

getItem(key: Key) {
return this.#keyNodeMap.get(key);
const node = this.#keyNodeMap.get(key);
if (!node) return null;

this.#updateFrequency(key, node);

return node;
}

setItem(key: Key, value: Value) {
this.#increaseFrequency(key);
this.#resetMinFrequencyIfNeeded(key);
if (this.#keyNodeMap.has(key)) {
const node = this.#keyNodeMap.get(key)!;
node.data.value = value;

const frequency = this.#keyFrequencyMap.get(key)!;
this.#updateFrequency(key, node);

return;
}

const frequency = INITIAL_MIN_FREQUENCY_VALUE;
const bucket = this.#getOrCreateBucket(frequency);

bucket.append({ key, value });
Expand All @@ -111,21 +103,29 @@ class Storage<Key extends keyof any, Value> {
const newNode = bucket.tail!;
this.#keyNodeMap.set(key, newNode);
this.#keyFrequencyMap.set(key, frequency);
}

#increaseFrequency(key: Key) {
const INITIAL_FREQUENCY_VALUE = 0;
const frequency = this.#keyFrequencyMap.get(key) ?? INITIAL_FREQUENCY_VALUE;

this.#keyFrequencyMap.set(key, frequency + 1);
this.#currentMinFrequency = INITIAL_MIN_FREQUENCY_VALUE;
}

#resetMinFrequencyIfNeeded(key: Key) {
const frequency = this.#keyFrequencyMap.get(key);
#updateFrequency(key: Key, node: DoublyLinkedListNode<Payload<Key, Value>>) {
const oldFreq = this.#keyFrequencyMap.get(key)!;
const oldBucket = this.#frequencyBuckets[oldFreq];

oldBucket.deleteByRef(node);

if (frequency === this.#INITIAL_MIN_FREQUENCY_VALUE) {
this.#currentMinFrequency = frequency;
if (oldBucket.isEmpty && this.#currentMinFrequency === oldFreq) {
this.#currentMinFrequency += 1;
}

const newFreq = oldFreq + 1;
const newBucket = this.#getOrCreateBucket(newFreq);

newBucket.append(node.data);
this.#frequencyBuckets[newFreq] = newBucket;

const newNode = newBucket.tail!;
this.#keyNodeMap.set(key, newNode);
this.#keyFrequencyMap.set(key, newFreq);
}

#getOrCreateBucket(frequency: number) {
Expand All @@ -149,13 +149,13 @@ class Storage<Key extends keyof any, Value> {
}
}

evictLeastFrequentItem() {
evictLeastFrequentlyUsed() {
const minFreq = this.#currentMinFrequency;
const bucket = this.#frequencyBuckets[minFreq];

const leastFrequentNode = bucket.removeHead()!;
const victim = bucket.removeHead()!;

const { key } = leastFrequentNode.data;
const { key } = victim.data;
this.#keyNodeMap.delete(key);
this.#keyFrequencyMap.delete(key);
}
Expand All @@ -164,6 +164,6 @@ class Storage<Key extends keyof any, Value> {
this.#frequencyBuckets = {};
this.#keyNodeMap.clear();
this.#keyFrequencyMap.clear();
this.#currentMinFrequency = this.#INITIAL_MIN_FREQUENCY_VALUE;
this.#currentMinFrequency = INITIAL_MIN_FREQUENCY_VALUE;
}
}
Loading
Loading