Skip to content

feat(lru-cache): implements multiple LRUCache #49

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 4 commits into from
Mar 30, 2020
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
72 changes: 72 additions & 0 deletions lab/exercises/01-arrays/longest-unique-characters.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* @param {string} s
* @return {number}
*/
function lengthOfLongestSubstring(s) {
let max = 0;
let start = 0;
const map = {};

for (let i = 0; i < s.length; i++) {
const char = s[i];

if (map[char]) {
start = map[char] + 1;
}

map[char] = i;
max = Math.max(1 + i - start, max);
}

return max;
}

const assert = require('assert');

const testCases = { abcabcbb: 3 };

for (const [string, unique] of Object.entries(testCases)) {
assert.equal(lengthOfLongestSubstring(string), unique);
}

/*
Longest string without duplicate chars.
"abcabcbb"
3 (abc)
i=6
c=b
s=5
h={a:3,b:4,c:2}
m=3
---
a
1
aa
1
ab
2
abc
3
aab
2
aabca
3
"dvdf"
3 (vdf)
"abcabcbb"
3 (abc)
---
map: O(n)
backtracking
*/
29 changes: 3 additions & 26 deletions src/data-structures/custom/lru-cache-1.js
Original file line number Diff line number Diff line change
@@ -1,30 +1,7 @@

/**
* Design and implement a data structure for Least Recently Used (LRU) cache. It should support the following operations: get and put.
get(key) - Get the value (will always be positive) of the key if the key exists in the cache, otherwise return -1.
put(key, value) - Set or insert the value if the key is not already present. When the cache reached its capacity, it should invalidate the least recently used item before inserting a new item.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LRUCache cache = new LRUCache( 2);
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
* https://leetcode.com/problems/lru-cache/description/
*
* @param {number} capacity
* Least Recently Used (LRU) cache.
* Map + Array: O(n)
* @param {number} capacity - Number of items to hold.
*/
const LRUCache = function (capacity) {
this.map = new Map();
Expand Down
72 changes: 72 additions & 0 deletions src/data-structures/custom/lru-cache-2.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
/**
* Least Recently Used (LRU) cache.
* Map + (Hash)Set: O(1)
* @param {number} capacity - Number of items to hold.
*/
var LRUCache = function(capacity) {
this.capacity = capacity || 2;
this.map = new Map();
this.set = new Set();
this.size = 0;
};

/**
* @param {number} key
* @return {number}
*/
LRUCache.prototype.get = function(key) {
if (!this.map.has(key)) return -1;
// move to top
this.set.delete(key);
this.set.add(key);

return this.map.get(key);
};

/**
* @param {number} key
* @param {number} value
* @return {void}
*/
LRUCache.prototype.put = function(key, value) {
this.map.set(key, value);
// move to top
this.set.delete(key);
this.set.add(key);

if (this.set.size > this.capacity) {
const leastUsedKey = this.set.values().next().value;
this.map.delete(leastUsedKey);
this.set.delete(leastUsedKey);
}

this.size = this.map.size;
};

/**
* Your LRUCache object will be instantiated and called as such:
* var obj = new LRUCache(capacity)
* var param_1 = obj.get(key)
* obj.put(key,value)
*/


/*
Implement a hashMap cache with a given capacity that once reach deletes the least used element and store the new one.
---
c = new LRUCache(2);
c.put(1,1);
c.put(2,2);
c.put(3,3); // deletes key 1
c = new LRUCache(2);
c.put(1,1);
c.put(2,2);
c.get(1);
c.put(3,3); // deletes key 2
*/

module.exports = LRUCache;
51 changes: 51 additions & 0 deletions src/data-structures/custom/lru-cache-3.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
const DLinkedList = require('../linked-lists/linked-list');
/**
* Least Recently Used (LRU) cache.
* Map + Double LinkedList: O(1)
* @param {number} capacity - Number of items to hold.
*/
class LRUCache extends Map {
constructor(capacity) {
super(); // initialize map
this.capacity = capacity;
this.list = new DLinkedList();
}

get(key) {
if (!super.has(key)) { return -1; }

// console.log('get', {key});
const node = super.get(key);
this.moveToHead(key, node);

return node.value.value;
}

put(key, value) {
// console.log('put', {key, value});
let node;
if (super.has(key)) {
node = super.get(key);
node.value.value = value;
} else {
node = this.list.addLast({key, value});
}
this.moveToHead(key, node);

if (this.list.size > this.capacity) {
const firstNode = this.list.removeFirst();
super.delete(firstNode.key);
}
}

moveToHead(key, node) {
// remove node and put it in front
this.list.removeByNode(node);
const newNode = this.list.addLast(node.value);
super.set(key, newNode);
// console.log('\tlist', Array.from(this.list).map(l => l.node.value));
// console.log('\tlist', Array.from(this.list).map(l => l.node.value.key));
}
}

module.exports = LRUCache;
39 changes: 8 additions & 31 deletions src/data-structures/custom/lru-cache.js
Original file line number Diff line number Diff line change
@@ -1,34 +1,7 @@
/**
* Design and implement a data structure for Least Recently Used (LRU) cache.
* It should support the following operations: get and put.
get(key) - Get the value (will always be positive) of the key
if the key exists in the cache, otherwise return -1.
put(key, value) - Set or insert the value if the key is not already present.
When the cache reached its capacity, it should invalidate the least
recently used item before inserting a new item.
Follow up:
Could you do both operations in O(1) time complexity?
Example:
LRUCache cache = new LRUCache( 2);
cache.put(1, 1);
cache.put(2, 2);
cache.get(1); // returns 1
cache.put(3, 3); // evicts key 2
cache.get(2); // returns -1 (not found)
cache.put(4, 4); // evicts key 1
cache.get(1); // returns -1 (not found)
cache.get(3); // returns 3
cache.get(4); // returns 4
* https://leetcode.com/problems/lru-cache/description/
* https://leetcode.com/submissions/detail/178329173/
*
* @param {number} capacity
* Least Recently Used (LRU) cache.
* (ordered) Map: O(1)
* @param {number} capacity - Number of items to hold.
*/
class LRUCache {
constructor(capacity) {
Expand All @@ -53,7 +26,7 @@ class LRUCache {
rotate(key) {
this.moveToTop(key);
while (this.map.size > this.capacity) {
const it = this.map.keys();
const it = this.map.keys(); // keys are in insertion order.
this.map.delete(it.next().value);
}
}
Expand All @@ -65,6 +38,10 @@ class LRUCache {
this.map.set(key, value);
}
}

get size() {
return this.map.size;
}
}

module.exports = LRUCache;
108 changes: 108 additions & 0 deletions src/data-structures/custom/lru-cache.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
const LRUCache = require('./lru-cache-3');

describe('LRU Cache', () => {
let c;

describe('#constructor', () => {
it('should initialize', () => {
c = new LRUCache();
expect(c).toBeDefined();
});

it('should initialize', () => {
c = new LRUCache(7);
expect(c.capacity).toEqual(7);
});
});

describe('when initialized', () => {
beforeEach(() => {
c = new LRUCache(2);
});

describe('#put', () => {
it('should insert new elements', () => {
c.put(1, 1);
expect(c.size).toEqual(1);
});

it('should update existing element', () => {
c.put(1, 1);
c.put(1, 2);
expect(c.size).toEqual(1);
});
});

describe('#get', () => {
it('should get element', () => {
c.put(1, 1);
expect(c.get(1)).toEqual(1);
});

it('should return -1 for non-existing elements', () => {
expect(c.get(1)).toEqual(-1);
});

it('should not add non-existing number to the top of the list', () => {
c.put(1, 1);
expect(c.get(8)).toEqual(-1);
c.put(2, 2);
expect(c.get(9)).toEqual(-1);
expect(c.get(1)).toEqual(1);
expect(c.get(2)).toEqual(2);
});

it('should return -1 for removed elements', () => {
c.put(1, 1);
c.put(2, 2);
c.put(3, 3);
expect(c.get(1)).toEqual(-1);
});

it('should not remove value if accessed recently', () => {
c.put(1, 1);
c.put(2, 2);
expect(c.get(1)).toEqual(1);
c.put(3, 3);
expect(c.get(1)).toEqual(1);
expect(c.get(2)).toEqual(-1);
});

it('should update a value', () => {
c.put(1, 1);
c.put(1, 2);
expect(c.get(1)).toEqual(2);
});
});

it('should work with size 10', () => {
c = new LRUCache(10);

c.put(10, 13);
c.put(3, 17);
c.put(6, 11);
c.put(10, 5);
c.put(9, 10);
expect(c.get(13)).toEqual(-1);
c.put(2, 19);
expect(c.get(2)).toEqual(19);
expect(c.get(3)).toEqual(17);
c.put(5, 25);
expect(c.get(8)).toEqual(-1);
c.put(9, 22);
c.put(5, 5);
c.put(1, 30);
expect(c.get(11)).toEqual(-1);
c.put(9, 12);
expect(c.get(7)).toEqual(-1);
expect(c.get(5)).toEqual(5);
expect(c.get(8)).toEqual(-1);
expect(c.get(9)).toEqual(12);
c.put(4, 30);
c.put(9, 3);
expect(c.get(9)).toEqual(3);
expect(c.get(10)).toEqual(5);
expect(c.get(10)).toEqual(5);
});
});
});
Loading