Skip to content

Commit a556acf

Browse files
committed
adds a for-loop based solution for the first challenge
1 parent 3e4ff3f commit a556acf

File tree

2 files changed

+136
-86
lines changed

2 files changed

+136
-86
lines changed

chapters/ch04.5-rolling-file-support.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ This line above [destructures](https://developer.mozilla.org/en-US/docs/Web/Java
7272
const { size, birthtimeMs } = await this.#log_file_handle.stat();
7373
```
7474

75-
We're destructuring the `size` and the `birthtimeMs` properties from teh `file_handle.stat()` method.
75+
We're destructuring the `size` and the `birthtimeMs` properties from the `file_handle.stat()` method.
7676

7777
Node.js provides us with a `stat` method on the `FileHandle` class, that can be used to check various stats of a file. The stat method provides a lot of useful information about the current file/file_handle. Some of them are methods, and some are properties.
7878

chapters/ch07-ex-implementing-a-trie.md

+135-85
Original file line numberDiff line numberDiff line change
@@ -24,13 +24,13 @@ The Trie starts with a root node that doesn't hold any character. It serves as t
2424
T O C
2525
```
2626

27-
- **Level 1**: You have the characters "O", "T", and "C" branching from the root node.
28-
- **Level 2 and Beyond**: These nodes further branch out to form the words.
29-
- "O" branches to "K", completing the word "OK".
30-
- "T" branches to "O", completing the word "TO".
31-
- "C" branches to "A" and "U":
32-
- "A" further branches to "R" for "CAR" and "T" for "CAT".
33-
- "U" further branches to "P", completing the word "CUP".
27+
- **Level 1**: You have the characters "O", "T", and "C" branching from the root node.
28+
- **Level 2 and Beyond**: These nodes further branch out to form the words.
29+
- "O" branches to "K", completing the word "OK".
30+
- "T" branches to "O", completing the word "TO".
31+
- "C" branches to "A" and "U":
32+
- "A" further branches to "R" for "CAR" and "T" for "CAT".
33+
- "U" further branches to "P", completing the word "CUP".
3434

3535
## End of the word
3636

@@ -61,9 +61,9 @@ In this first challenge, your task is to implement a Trie data structure with on
6161

6262
3. **Node Creation**: If a character in the word doesn't match any child node of the current node:
6363

64-
- Create a new node for that character.
65-
- Link this new node to the current one.
66-
- Move down to this new node and continue with the next character in the word.
64+
- Create a new node for that character.
65+
- Link this new node to the current one.
66+
- Move down to this new node and continue with the next character in the word.
6767

6868
4. **End-of-Word**: When you've inserted all the characters for a particular word, mark the last node in some way to indicate that it's the end of a valid word. This could be a boolean property in the node object, for example.
6969

@@ -73,21 +73,21 @@ Here's the boilerplate to get you started.
7373
7474
```js
7575
class TrieNode {
76-
constructor() {
77-
this.children = {}; // To store TrieNode children with char keys
78-
// this.children = new Map(); You may also use a Map instead.
79-
this.isEndOfWord = false; // To mark the end of a word
80-
}
76+
constructor() {
77+
this.children = new Map(); // To store TrieNode children with char keys
78+
// this.children = new Map(); You may also use a Map instead.
79+
this.isEndOfWord = false; // To mark the end of a word
80+
}
8181
}
8282

8383
class Trie {
84-
constructor() {
85-
this.root = new TrieNode();
86-
}
84+
constructor() {
85+
this.root = new TrieNode();
86+
}
8787

88-
insert(word) {
89-
// Your code here
90-
}
88+
insert(word) {
89+
// Your code here
90+
}
9191
}
9292
```
9393

@@ -108,43 +108,43 @@ Great. You just implemented a `Trie` which is a Tree data structure. You've also
108108

109109
```js
110110
class Trie {
111-
constructor() {
112-
this.root = new TrieNode();
113-
}
111+
constructor() {
112+
this.root = new TrieNode();
113+
}
114114

115-
insert(wordToInsert, node = this.root) {
116-
let length = wordToInsert.length;
117-
if (length == 0) return;
115+
insert(wordToInsert, node = this.root) {
116+
let length = wordToInsert.length;
117+
if (length == 0) return;
118118

119-
const letters = wordToInsert.split("");
119+
const letters = wordToInsert.split("");
120120

121-
const foundNode = node.children.get(wordToInsert[0]);
121+
const foundNode = node.children.get(wordToInsert[0]);
122122

123-
if (foundNode) {
124-
this.insert(letters.slice(1).join(""), foundNode);
125-
} else {
126-
let insertedNode = node.add(letters[0], length == 1);
127-
this.insert(letters.slice(1).join(""), insertedNode);
128-
}
123+
if (foundNode) {
124+
this.insert(letters.slice(1).join(""), foundNode);
125+
} else {
126+
let insertedNode = node.add(letters[0], length == 1);
127+
this.insert(letters.slice(1).join(""), insertedNode);
129128
}
129+
}
130130
}
131131

132132
class TrieNode {
133-
constructor() {
134-
/**
135-
* Children will be Map<key(String), node(TrieNode)>
136-
*/
137-
this.isEndOfWord = false;
138-
this.children = new Map();
139-
}
140-
141-
add(letter, _isLastCharacter) {
142-
let newNode = new TrieNode();
143-
this.children.set(letter, newNode);
144-
145-
if (_isLastCharacter) newNode.isEndOfWord = true;
146-
return newNode;
147-
}
133+
constructor() {
134+
/**
135+
* Children will be Map<key(String), node(TrieNode)>
136+
*/
137+
this.isEndOfWord = false;
138+
this.children = new Map();
139+
}
140+
141+
add(letter, _isLastCharacter) {
142+
let newNode = new TrieNode();
143+
this.children.set(letter, newNode);
144+
145+
if (_isLastCharacter) newNode.isEndOfWord = true;
146+
return newNode;
147+
}
148148
}
149149

150150
const trie = new Trie();
@@ -157,17 +157,17 @@ Let's take a look at the code:
157157

158158
```js
159159
class TrieNode {
160-
constructor() {
161-
this.isEndOfWord = false;
162-
this.children = new Map();
163-
}
160+
constructor() {
161+
this.isEndOfWord = false;
162+
this.children = new Map();
163+
}
164164
}
165165
```
166166

167167
Initializes an instance of the `TrieNode` class. A TrieNode has two properties:
168168

169-
- `isEndOfWord`: A boolean flag that denotes whether the node is the last character of a word in the Trie. Initially set to `false`.
170-
- `children`: A Map to store the children nodes. The keys are letters, and the values are TrieNode objects.
169+
- `isEndOfWord`: A boolean flag that denotes whether the node is the last character of a word in the Trie. Initially set to `false`.
170+
- `children`: A Map to store the children nodes. The keys are letters, and the values are TrieNode objects.
171171

172172
```js
173173
add(letter, _isLastCharacter) {
@@ -183,35 +183,85 @@ I've created a utility method on `TrieNode` to extract some logic from the `Trie
183183

184184
```js
185185
class Trie {
186-
insert(wordToInsert, node = this.root) {
187-
let length = wordToInsert.length;
186+
insert(wordToInsert, node = this.root) {
187+
let length = wordToInsert.length;
188+
189+
// Exit condition: If the word to insert is empty, terminate the recursion.
190+
if (length == 0) return;
191+
192+
// Convert the string into an array of its individual characters.
193+
const letters = wordToInsert.split("");
194+
195+
// Attempt to retrieve the TrieNode corresponding to the first letter
196+
// of the word from the children of the current node.
197+
const foundNode = node.children.get(wordToInsert[0]);
198+
199+
if (foundNode) {
200+
// The first letter already exists as a child of the current node.
201+
// Continue inserting the remaining substring (sans the first letter)
202+
// starting from this found node.
203+
this.insert(letters.slice(1).join(""), foundNode);
204+
} else {
205+
// The first letter doesn't exist in the children of the current node.
206+
// Create a new TrieNode for this letter and insert it as a child of the current node.
207+
// Also, set the node's 'isEndOfWord' flag if this is the last character of the word.
208+
let insertedNode = node.add(letters[0], length == 1);
209+
210+
// Continue inserting the remaining substring (without the first letter)
211+
// starting from this new node.
212+
this.insert(letters.slice(1).join(""), insertedNode);
213+
}
214+
}
215+
}
216+
```
188217

189-
// Exit condition: If the word to insert is empty, terminate the recursion.
190-
if (length == 0) return;
218+
The previous code looks fine. However, as our backend library/framework prioritizes performance, we will optimize the code we write in the upcoming exercises to improve efficiency.
191219

192-
// Convert the string into an array of its individual characters.
193-
const letters = wordToInsert.split("");
220+
The code above is acceptable, and not a bad implementation. But we can do better.
194221

195-
// Attempt to retrieve the TrieNode corresponding to the first letter
196-
// of the word from the children of the current node.
197-
const foundNode = node.children.get(wordToInsert[0]);
222+
### Using a `for` loop instead of recursion
198223

199-
if (foundNode) {
200-
// The first letter already exists as a child of the current node.
201-
// Continue inserting the remaining substring (sans the first letter)
202-
// starting from this found node.
203-
this.insert(letters.slice(1).join(""), foundNode);
204-
} else {
205-
// The first letter doesn't exist in the children of the current node.
206-
// Create a new TrieNode for this letter and insert it as a child of the current node.
207-
// Also, set the node's 'isEndOfWord' flag if this is the last character of the word.
208-
let insertedNode = node.add(letters[0], length == 1);
209-
210-
// Continue inserting the remaining substring (without the first letter)
211-
// starting from this new node.
212-
this.insert(letters.slice(1).join(""), insertedNode);
213-
}
224+
I am not a big fan of recursion, and I prefer using loops over recursion most of the time. For loop is much easier to reason about and as someone who's reading the code, it's easier to understand what's going on.
225+
226+
Let's update our submission to use a `for` loop instead of recursion.
227+
228+
```js
229+
/** this class remains identical **/
230+
class TrieNode {
231+
isEndOfWord = false;
232+
children = new Map();
233+
}
234+
235+
class Trie {
236+
constructor() {
237+
// The root node is an empty TrieNode.
238+
this.root = new TrieNode();
239+
}
240+
241+
insert(word, node = this.root) {
242+
const wordLength = word.length;
243+
if (wordLength === 0) return;
244+
245+
// Iterate over each character in the word.
246+
for (let idx = 0; idx < wordLength; idx++) {
247+
// Get the current character.
248+
let char = word[idx];
249+
250+
// Check if the current node has a child node for the current character.
251+
if (!node.children.has(char)) {
252+
// If not, create a new TrieNode for this character and add it to the children of the current node.
253+
node.children.set(char, new TrieNode());
254+
}
255+
256+
// Move to the child node corresponding to the current character.
257+
node = node.children.get(char);
258+
259+
// If this is the last character of the word, mark the node as the end of a word.
260+
if (idx === word.length - 1) {
261+
node.isEndOfWord = true;
262+
}
214263
}
264+
}
215265
}
216266
```
217267

@@ -254,23 +304,23 @@ If you are having trouble or are stuck, here are some hints to help you with the
254304

255305
2. **Character Check**: For each character in the word, check if there's a child node for that character from the current node you're at.
256306

257-
- **If Yes**: Move to that child node.
258-
- **If No**: Return `false`, as the word can't possibly exist in the Trie.
307+
- **If Yes**: Move to that child node.
308+
- **If No**: Return `false`, as the word can't possibly exist in the Trie.
259309

260310
3. **End-of-Word Check**: If you've reached the last character of the word, check the `isEndOfWord` property of the current node. If it's `true`, the word exists in the Trie; otherwise, it doesn't.
261311

262312
4. **Recursion or Loop**: You can choose to implement this method either recursively or iteratively.
263313

264-
- **Recursion**: If you opt for recursion, you might want to include an additional parameter in the `search` method for the current node, similar to how you did it for the `insert` method.
265-
- **Loop**: If you prefer loops, you can use a `for` loop to go through each character in the word, updating your current node as you go along.
314+
- **Recursion**: If you opt for recursion, you might want to include an additional parameter in the `search` method for the current node, similar to how you did it for the `insert` method.
315+
- **Loop**: If you prefer loops, you can use a `for` loop to go through each character in the word, updating your current node as you go along.
266316

267317
5. **Return Value**: Don't forget to return `true` or `false` to indicate whether the word exists in the Trie.
268318

269319
Good luck!
270320

271321
### Solution
272322

273-
I chose to implement tree traversal using a for loop this time, to showcase different ways of doing things. I usually prefer for-loops over recursion most of the time, due to the over head of function calls.
323+
Again, I chose to implement tree traversal using a for loop instead of recursion.
274324

275325
```js
276326
search(word) {

0 commit comments

Comments
 (0)