Skip to content

Commit edf11d9

Browse files
authoredOct 22, 2020
Merge pull request amejiarosario#91 from amejiarosario/feat/hashmap
feat(book/hashmap): add code examples and patterns
2 parents dc47b76 + f7b8d59 commit edf11d9

16 files changed

+865
-201
lines changed
 

‎book/D-interview-questions-solutions.asc

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -437,7 +437,8 @@ The complexity of any of the BFS methods or DFS is similar.
437437
:leveloffset: -1
438438

439439
[#hashmap-q-two-sum]
440-
include::content/part03/hashmap.asc[tag=hashmap-q-two-sum]
440+
include::content/part02/hash-map.asc[tag=hashmap-q-two-sum]
441+
// include::content/part03/hashmap.asc[tag=hashmap-q-two-sum]
441442

442443
This simple problem can have many solutions; let's explore some.
443444

@@ -480,7 +481,8 @@ include::interview-questions/two-sum.js[tags=description;solution]
480481

481482

482483
[#hashmap-q-subarray-sum-equals-k]
483-
include::content/part03/hashmap.asc[tag=hashmap-q-subarray-sum-equals-k]
484+
include::content/part02/hash-map.asc[tag=hashmap-q-subarray-sum-equals-k]
485+
// include::content/part03/hashmap.asc[tag=hashmap-q-subarray-sum-equals-k]
484486

485487
This problem has multiple ways to solve it. Let's explore some.
486488

‎book/content/part02/array.asc

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,11 @@ ifndef::imagesdir[]
33
:codedir: ../../../src
44
endif::[]
55

6+
(((Array))) (((Data Structures, Linear, Array)))
67
[[array]]
7-
=== Array [[array-chap]]
8-
(((Array)))
9-
(((Data Structures, Linear, Array)))
8+
[[array-chap]]
9+
=== Array
10+
1011
Arrays are one of the most used data structures. You probably have used it a lot already. But, are you aware of the runtimes of `push`, `splice`, `shift`, `indexOf`, and other operations? In this chapter, we are going deeper into the most common operations and their runtimes.
1112

1213
==== Array Basics
@@ -300,9 +301,7 @@ To sum up, the time complexity of an array is:
300301

301302
Many programming problems involve manipulating arrays. Here are some patterns that can help you improve your problem-solving skills.
302303

303-
(((Patterns, Two Pointers)))
304-
305-
===== Two Pointers Pattern
304+
===== Two Pointers Pattern (((Patterns, Two Pointers)))
306305

307306
Usually, we use one pointer to navigate each element in an array. However, there are times when having two pointers (left/right, low/high) comes in handy. Let's do some examples.
308307

@@ -371,9 +370,7 @@ These two pointers have a runtime of `O(n)`.
371370

372371
WARNING: This technique only works for sorted arrays. If the array was not sorted, you would have to sort it first or choose another approach.
373372

374-
(((Patterns, Sliding Window Pointers)))
375-
376-
===== Sliding Window Pattern
373+
===== Sliding Window Pattern (((Patterns, Sliding Window))) [[sliding-window-array]]
377374

378375
The sliding window pattern is similar to the two pointers. The difference is that the distance between the left and right pointer is always the same. Also, the numbers don't need to be sorted. Let's do an example!
379376

‎book/content/part02/hash-map.asc

Lines changed: 680 additions & 0 deletions
Large diffs are not rendered by default.

‎book/content/part03/map.asc

Lines changed: 0 additions & 71 deletions
This file was deleted.

‎book/content/part03/set.asc

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,9 @@ ifndef::imagesdir[]
33
:codedir: ../../../src
44
endif::[]
55

6+
(((Set))) (((Data Structures, Non-Linear, Set)))
67
[[set]]
78
=== Set
8-
(((Set)))
9-
(((Data Structures, Non-Linear, Set)))
109
A set is a data structure where duplicated entries are not allowed. A Set is like an array with only unique values.
1110

1211
NOTE: JavaScript already has a built-in Set data structure.

‎book/content/part03/treemap.asc

Lines changed: 49 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,20 @@ ifndef::imagesdir[]
33
:codedir: ../../../src
44
endif::[]
55

6-
[[treemap]]
7-
==== TreeMap
8-
(((TreeMap)))
9-
(((Data Structures, Non-Linear, TreeMap)))
10-
A TreeMap is a Map implementation using Binary Search Trees.
11-
(((Binary Search Tree)))
12-
(((BST)))
6+
(((TreeMap))) (((Data Structures, Non-Linear, TreeMap))) (((Binary Search Tree))) (((BST)))
7+
[[treemap-chap]]
8+
=== TreeMap
139

10+
A Map is an abstract data structure to store pairs of data: *key* and *value*. It also has a fast key lookup of `O(1)` for <<hashmap-chap>> or `O(log n)` for <<treemap-chap>>.
11+
12+
We can implement a Map using two different underlying data structures:
13+
14+
* *HashMap*: it’s a map implementation using an *array* and a *hash function*. The job of the hash function is to convert the `key` into an index that maps to the `value`. Optimized HashMap can have an average runtime of *O(1)*.
15+
* *TreeMap*: it’s a map implementation that uses a self-balanced Binary Search Tree (like <<c-avl-tree>> or Red-Black Tree). The BST nodes store the key, and the value and nodes are sorted by key guaranteeing an *O(log n)* look up.
16+
17+
We already covered <<hashmap-chap>>, so this chapter we are going to focus on TreeMap.
18+
19+
A TreeMap is a Map implementation using a Balanced Binary Search Trees.
1420
Implementing a Map with a tree, TreeMap, has a couple of advantages over a HashMap:
1521

1622
* Keys are always sorted.
@@ -36,7 +42,7 @@ class TreeMap {
3642
}
3743
----
3844

39-
===== Inserting values into a TreeMap
45+
==== Inserting values into a TreeMap
4046

4147
For inserting a value on a TreeMap, we first need to inialize the tree:
4248

@@ -58,7 +64,7 @@ include::{codedir}/data-structures/maps/tree-maps/tree-map.js[tag=set, indent=0]
5864

5965
Adding values is very easy (once we have the underlying tree implementation).
6066

61-
===== Getting values out of a TreeMap
67+
==== Getting values out of a TreeMap
6268

6369
When We search by key in a tree map, it takes *O(log n)*. This is the implementation:
6470

@@ -82,7 +88,7 @@ include::{codedir}/data-structures/maps/tree-maps/tree-map.js[tag=iterators, ind
8288
Generators are useful for producing values that can you can iterate in a `for...of` loop. Generators use the `function*` syntax which expects to have a `yield` with a value.
8389
****
8490

85-
===== Deleting values from a TreeMap
91+
==== Deleting values from a TreeMap
8692

8793
Removing elements from TreeMap is simple.
8894

@@ -95,3 +101,36 @@ include::{codedir}/data-structures/maps/tree-maps/tree-map.js[tag=delete, indent
95101
The BST implementation does all the heavy lifting.
96102

97103
That’s it! To see the full file in context, click here: https://github.com/amejiarosario/dsa.js/blob/f69b744a1bddd3d99243ca64b3ad46f3f2dd7342/src/data-structures/maps/tree-maps/tree-map.js#L22[here]
104+
105+
106+
<<<
107+
==== HashMap vs TreeMap
108+
109+
.A map can be implemented using hash functions or binary search tree:
110+
- *HashMap*: it’s a map implementation using an *array* and *hash function*. The job of the hash function is to convert the key into an index that contains the matching data. Optimized HashMap can have an average runtime of *O(1)*.
111+
- *TreeMap*: it’s a map implementation that uses a self-balanced Binary Search Tree (red-black tree). The BST nodes store the key, and the value and nodes are sorted by key guaranteeing an *O(log n)* look up.
112+
113+
114+
.When to use a TreeMap vs. HashMap?
115+
* `HashMap` is more time-efficient. A `TreeMap` is more space-efficient.
116+
* `TreeMap` search complexity is *O(log n)*, while an optimized `HashMap` is *O(1)* on average.
117+
* `HashMap`’s keys are in insertion order (or random depending in the implementation). `TreeMap`’s keys are always sorted.
118+
* `TreeMap` offers some statistical data for free such as: get minimum, get maximum, median, find ranges of keys. `HashMap` doesn’t.
119+
* `TreeMap` has a guarantee always an *O(log n)*, while `HashMap`s has an amortized time of *O(1)* but in the rare case of a rehash, it would take an *O(n)*.
120+
121+
==== TreeMap Time complexity vs HashMap
122+
123+
As we discussed so far, there is a trade-off between the implementations.
124+
(((Tables, Non-Linear DS, HashMap/TreeMap complexities))) (((Linear))) (((Runtime, Linear))) (((Logarithmic))) (((Runtime, Logarithmic)))
125+
126+
// also on: book/content/part03/time-complexity-graph-data-structures.asc
127+
// tag::table[]
128+
.Time complexity for different Maps implementations
129+
|===
130+
.2+.^s| Data Structure 2+^s| Searching By .2+^.^s| Insert .2+^.^s| Delete .2+^.^s| Space Complexity
131+
^|_Index/Key_ ^|_Value_
132+
| <<hashmap-chap, Hash Map>> ^|O(1) ^|O(n) ^|O(1)* ^|O(1) ^|O(n)
133+
| <<treemap-chap, Tree Map (Red-Black Tree)>> ^|O(log n) ^|O(n) ^|O(log n) ^|O(log n) ^|O(n)
134+
|===
135+
{empty}* = Amortized run time. E.g. rehashing might affect run time to *O(n)*.
136+
// end::table[]

‎book/content/part03/hashmap.asc renamed to ‎book/deprecated/old-hashmap.asc

Lines changed: 1 addition & 71 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ ifndef::imagesdir[]
33
:codedir: ../../../src
44
endif::[]
55

6-
[[hashmap]]
6+
[[hashmap-advanced]]
77
==== HashMap
88
(((HashMap)))
99
(((HashTable)))
@@ -308,73 +308,3 @@ Hash Map is optimal for searching values by key in constant time *O(1)*. However
308308

309309
indexterm:[Runtime, Linear]
310310
As you can notice, we have amortized times since it will take O(n) while it resizes in the unfortunate case of a rehash. After that, it will be *O(1)*.
311-
312-
313-
==== Practice Questions
314-
(((Interview Questions, Hash Map)))
315-
316-
317-
318-
// tag::hashmap-q-two-sum[]
319-
===== Fit 2 movies in a flight
320-
321-
*HM-1*) _You are working in an entertainment recommendation system for an airline. Given a flight duration (target) and an array of movies length, you need to recommend two movies that fit exactly the length of the flight. Return an array with the indices of the two numbers that add up to the target. No duplicates are allowed. If it's not possible to return empty `[]`._
322-
323-
// end::hashmap-q-two-sum[]
324-
325-
// _Seen in interviews at: Amazon, Google, Apple._
326-
327-
Examples:
328-
329-
[source, javascript]
330-
----
331-
twoSum([113, 248, 80, 200, 91, 201, 68], 316); // [1, 6] (248 + 68 = 316)
332-
twoSum([150, 100, 200], 300); // [2, 3] (100 + 200 = 300)
333-
twoSum([150, 100, 200], 150); // [] (No two numbers add up to 150)
334-
----
335-
336-
Starter code:
337-
338-
[source, javascript]
339-
----
340-
include::../../interview-questions/two-sum.js[tags=description;placeholder]
341-
----
342-
343-
344-
_Solution: <<hashmap-q-two-sum>>_
345-
346-
347-
348-
349-
350-
// tag::hashmap-q-subarray-sum-equals-k[]
351-
===== Subarray Sum that Equals K
352-
353-
*HM-2*) _Given an array of integers, find all the possible subarrays to add up to k. Return the count._
354-
355-
// end::hashmap-q-subarray-sum-equals-k[]
356-
357-
// _Seen in interviews at: Facebook, Google, Amazon_
358-
359-
Examples:
360-
361-
[source, javascript]
362-
----
363-
subarraySum([1], 1); // 1 (1 equals to 1 :)
364-
subarraySum([1, 1, 1], 1); // 3 ([1], [1], [1] equals 1)
365-
subarraySum([1, -1, 1], 0); // 2 (sum([1, -1]), sum([-1, 1]) equals 0)
366-
subaraySum([1, 2, 3, 0, 1, 4, 0, 5], 5) // 8
367-
// All of these 8 sub arrays add up to 5:
368-
// [2, 30], [2,3,0], [0,1,4], [0,1,4,0], [1,4], [1,4,0], [0,5], [5]
369-
----
370-
371-
Starter code:
372-
373-
[source, javascript]
374-
----
375-
include::../../interview-questions/subarray-sum-equals-k.js[tags=description;placeholder]
376-
----
377-
378-
379-
_Solution: <<hashmap-q-subarray-sum-equals-k>>_
380-

‎book/images/sliding-window-map.png

18.5 KB
Loading
Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
/**
2+
* Given text and banned words,
3+
* return the most common words in descending order.
4+
* @param {string} text - The text to parse.
5+
* @param {number} n - The number of results.
6+
* @return {string[]}
7+
*/
8+
// tag::map[]
9+
function mostCommonWords(text, n = 1) {
10+
const words = text.toLowerCase().split(/\W+/);
11+
12+
const map = words
13+
.reduce((m, w) => m.set(w, 1 + (m.get(w) || 0)), new Map());
14+
15+
return Array.from(map.entries())
16+
.sort((a, b) => b[1] - a[1])
17+
.slice(0, n)
18+
.map((w) => w[0]);
19+
}
20+
// end::map[]
21+
22+
// tag::brute[]
23+
function mostCommonWordsBrute(text, n = 1) {
24+
const words = text.toLowerCase().split(/\W+/);
25+
const entries = []; // array of [word, count] pairs
26+
27+
for (let i = 0; i < words.length; i++) {
28+
if (!words[i]) continue;
29+
let count = 1;
30+
for (let j = i + 1; j < words.length; j++) {
31+
if (words[i] === words[j]) {
32+
count++;
33+
words[j] = null; // removed letter once it's counted.
34+
}
35+
}
36+
entries.push([words[i], count]);
37+
}
38+
39+
return entries
40+
.sort((a, b) => b[1] - a[1])
41+
.slice(0, n)
42+
.map((w) => w[0]);
43+
}
44+
// end::brute[]
45+
46+
47+
module.exports = { mostCommonWords, mostCommonWordsBrute };
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
const { mostCommonWords, mostCommonWordsBrute } = require('./most-common-words-ii');
2+
3+
[mostCommonWordsBrute, mostCommonWords].forEach((fn) => {
4+
describe(`Most Common words: ${fn.name}`, () => {
5+
it('should work', () => {
6+
expect(fn(
7+
'The map, maps keys to values; Keys can be anything.',
8+
1,
9+
)).toEqual(['keys']);
10+
});
11+
12+
it('should work', () => {
13+
expect(fn(
14+
'Look at it! What is it? It does look like my code from 1 year ago',
15+
2,
16+
)).toEqual(['it', 'look']);
17+
});
18+
19+
it('should work', () => {
20+
expect(fn(
21+
'a; a,b, a\'s c a!; b,b, c.',
22+
4,
23+
)).toEqual(['a', 'b', 'c', 's']);
24+
});
25+
});
26+
});

‎book/part02-linear-data-structures.asc

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ When you are aware of the data structures implementations, you spot when to use
99

1010
.In this part we are going to learn about the following linear data structures:
1111
- <<array-chap>>
12+
- <<hashmap-chap>>
1213
- <<part02-linear-data-structures#linked-list>>
1314
- <<part02-linear-data-structures#stack>>
1415
- <<part02-linear-data-structures#queue>>
@@ -27,6 +28,9 @@ endif::[]
2728
<<<
2829
include::content/part02/array.asc[]
2930

31+
<<<
32+
include::content/part02/hash-map.asc[]
33+
3034
<<<
3135
include::content/part02/linked-list.asc[]
3236

‎book/part03-graph-data-structures.asc

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,8 +21,11 @@ include::content/part03/tree-search-traversal.asc[]
2121
<<<
2222
include::content/part03/binary-search-tree-traversal.asc[]
2323

24-
<<<
25-
include::content/part03/map.asc[]
24+
// <<<
25+
// include::content/part03/map.asc[]
26+
27+
<<
28+
include::content/part03/treemap.asc[]
2629

2730
<<<
2831
include::content/part03/set.asc[]
@@ -35,4 +38,3 @@ include::content/part03/graph-search.asc[]
3538

3639
<<<
3740
include::content/part03/time-complexity-graph-data-structures.asc[]
38-

‎book/part04-algorithmic-toolbox.asc

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,3 @@ include::content/part04/backtracking.asc[]
3636

3737
<<<
3838
include::content/part04/algorithmic-toolbox.asc[]
39-

‎book/readme.asc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ Adrian Mejia
1111
:revdate: {docdate}
1212
:docinfo:
1313
:toc:
14-
:toclevels: 2
14+
:toclevels: 4
1515
:pagenums:
1616
:front-cover-image: image:cover.png[width=1050,height=1600]
1717
:icons: font
Lines changed: 29 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,47 +1,45 @@
11
/**
22
* Least Recently Used (LRU) cache.
3-
* (ordered) Map: O(1)
4-
* @param {number} capacity - Number of items to hold.
3+
* Key/Value storage with fixed max number of items.
4+
* Least recently used items are discarded once the limit is reached.
5+
* Reading and updating the values mark the items as recently used.
6+
* @author Adrian Mejia <adrianmejia.com>
57
*/
6-
class LRUCache {
8+
class LRUCache extends Map {
9+
/**
10+
* @param {number} capacity - The max number of items on the cache
11+
*/
712
constructor(capacity) {
8-
this.map = new Map();
13+
super();
914
this.capacity = capacity;
1015
}
1116

17+
/**
18+
* Get value associated with the key. Mark keys as recently used.
19+
* @param {number} key
20+
* @returns {number} value or if not found -1
21+
*/
1222
get(key) {
13-
const value = this.map.get(key);
14-
if (value) {
15-
this.moveToTop(key);
16-
return value;
17-
}
18-
return -1;
23+
if (!super.has(key)) return -1;
24+
const value = super.get(key);
25+
this.put(key, value); // re-insert at the top (most recent).
26+
return value;
1927
}
2028

29+
/**
30+
* Upsert key/value pair. Updates mark keys are recently used.
31+
* @param {number} key
32+
* @param {number} value
33+
* @returns {void}
34+
*/
2135
put(key, value) {
22-
this.map.set(key, value);
23-
this.rotate(key);
24-
}
25-
26-
rotate(key) {
27-
this.moveToTop(key);
28-
while (this.map.size > this.capacity) {
29-
const it = this.map.keys(); // keys are in insertion order.
30-
this.map.delete(it.next().value);
31-
}
32-
}
33-
34-
moveToTop(key) {
35-
if (this.map.has(key)) {
36-
const value = this.map.get(key);
37-
this.map.delete(key);
38-
this.map.set(key, value);
36+
if (super.has(key)) super.delete(key);
37+
super.set(key, value);
38+
if (super.size > this.capacity) {
39+
const oldestKey = super.keys().next().value;
40+
super.delete(oldestKey);
3941
}
4042
}
41-
42-
get size() {
43-
return this.map.size;
44-
}
4543
}
4644

4745
module.exports = LRUCache;

‎src/data-structures/custom/lru-cache.spec.js

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,18 @@ describe('LRU Cache', () => {
7575
});
7676
});
7777

78+
it('should work with updates', () => {
79+
// ["LRUCache","put","put","put","put","get","get"]
80+
// [[2],[2,1],[1,1],[2,3],[4,1],[1],[2]]
81+
c = new LRUCache(2);
82+
c.put(2, 1);
83+
c.put(1, 1);
84+
c.put(2, 3);
85+
c.put(4, 1);
86+
c.get(1);
87+
c.get(2);
88+
});
89+
7890
it('should work with size 10', () => {
7991
c = new LRUCache(10);
8092

0 commit comments

Comments
 (0)
Please sign in to comment.