├── .gitignore ├── README.md ├── entries.json ├── index.js ├── package-lock.json ├── package.json └── prompt.js /.gitignore: -------------------------------------------------------------------------------- 1 | # Dependency directories 2 | node_modules/ 3 | jspm_packages/ 4 | 5 | .DS_Store -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algorithms Study Helper 2 | 3 | TLDR: Automatic scheduler for studying algorithms! 4 | ## Why? 5 | I wrote this app to help me study for Leetcode-style interview questions. While some believe "memorizing" interview questions is not productive, I find that to be untrue. This is because with interview questions, it's never really "rote" memorization. It requires active thinking to recall the key insights of a solution. With more questions mastered in one's toolbox, new problems definitely become easier to solve. e.g. If you can readily implement MergeSort, divide and conquer type problems would require less mental overhead to crack. 6 | 7 | However, while I was trying to study these questions, I noticed I was often either (1) wasting time redoing problems I've already mastered or (2) wasting time having to remaster an old problem that I'd forgotten the key insight to. This app solves that problem by acting as an automatic scheduler. 8 | 9 | **"Why did you make this app when more mature solutions like [Anki](https://apps.ankiweb.net/) exist?"** 10 | I did use Anki at first, but I had a difficult time coming up with settings that were suitable for studying interview type questions. 11 | 12 | ## What? 13 | Enter algorithms you want to study into Algorithms Study Helper, and the app will automatically schedule when you should study an algorithm next! As you successfully review an algorithm, it will be scheduled less and less frequently. 14 | 15 | ## How? 16 | This part explains the algorithm I use to schedule when to study problems. 17 | 18 | **Disclaimer**: It is currently tweaked to my own retention/learning rate. Read on to customize it to your liking! 19 | 20 | **Scheduling Algorithm** 21 | **_nextScheduled_** = (time when you studied it) + 24 hours * (**RETENTION_RATE** ^ **_mastery_**) 22 | 23 | **RETENTION_RATE** = 2 (default) 24 | This is a constant in prompt.js. 25 | -Increase it if you retain new information well. 26 | -Decrease it if you require more practice to retain information. 27 | 28 | **Table: Scheduling Intervals with RETENTION_RATE = 2** 29 | 30 | | Mastery | Interval (days)| 31 | | :-----------: |:--------------:| 32 | | 0 | 1 | 33 | | 1 | 2 | 34 | | 2 | 4 | 35 | | 3 | 8 | 36 | | 4 | 16 | 37 | | 5 | 32 | 38 | 39 | 40 | 41 | Each problem you enter into the app is an entry. It is stored in entries.json. An entry looks like this: 42 | 43 | ```javascript 44 | const entry = { 45 | "name": "dijkstra's shortest path algorithm", 46 | "lastStudied": "Tue Dec 25 2018 22:54:21", 47 | "nextScheduled": "Wed Dec 26 2018 22:54:21", 48 | "mastery": 0, 49 | "subMastery": 3 50 | }; 51 | ``` 52 | After you review a scheduled problem, you will be asked "How difficult was it?" Your answer affects the entry's **_subMastery_** and/or **_mastery_**. 53 | 54 | 1. Easy: **_subMastery_** + 4 55 | 2. Medium: **_subMastery_** + 2 56 | 3. Hard: **_subMastery_** + 1 57 | 4. Buggy Solution: **_subMastery_** + 0 58 | 5. Failed to Complete: **_mastery_** and **_subMastery_** both reset to 0. 59 | 60 | Once you reach **_subMastery_** 4 on an entry, you will gain 1 **_mastery_** (up to 5). **_subMastery_** is reset to 0. 61 | 62 | **EXAMPLE**: Given a completely new entry with **_subMastery_**: 0 and **_mastery_**: 0. Say your 4 study sessions look like the following: 63 | 1. You answer Medium. (**_subMastery_**: 2 and **_mastery_**: 0) 64 | It will be scheduled again in 1 day. 65 | 2. You answer Hard. (**_subMastery_**: 3 and **_mastery_**: 0) 66 | It will be scheduled again in 1 day. 67 | 3. You answer Easy. (**_subMastery_**: 0 and **_mastery_**: 1) 68 | It will be scheduled again in 2 days. 69 | 4. You answer Easy. (**_subMastery_**: 0 and **_mastery_**: 2) 70 | It will be scheduled again in 4 days. 71 | 72 | 73 | ## Installation 74 | 75 | Navigate into Algorithms-Study-Helper and run: 76 | 77 | ```bash 78 | npm install 79 | ``` 80 | 81 | ## Usage 82 | Simply follow the prompts. 83 | 84 | ```bash 85 | node index.js 86 | ``` 87 | 88 | ## Modifying entries.json 89 | Modify manually at your own risk! This file contains all your entries. The entries are in order of nextScheduled date. The file is overwritten when the app exits. Only modify entries.json while the app is not running or else it will be overwritten. 90 | 91 | ## Contributing 92 | You're welcome to fork this repo or provide feedback by opening issues! -------------------------------------------------------------------------------- /entries.json: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bruce-c-liu/Algorithms-Study-Helper/345a5750621657adcecb05cdbe7dbbe080534e96/entries.json -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const prompt = require('./prompt').prompt; 3 | let entries = require('./prompt').entries; 4 | const nodeCleanup = require('node-cleanup'); 5 | 6 | nodeCleanup(function (exitCode, signal) { 7 | entries = JSON.stringify(entries, null, '\t'); 8 | fs.writeFileSync('entries.json', entries); 9 | 10 | if (signal) { 11 | // RECEIVES SIGHUP IF TERMINAL IS CLOSED 12 | console.log('Got signal: ', signal); 13 | console.log('Exiting...'); 14 | nodeCleanup.uninstall(); // don't call cleanup handler again 15 | process.kill(process.pid, signal); 16 | return false; 17 | } else { 18 | // console.log('In cleanup. Exit code is', exitCode); 19 | console.log('Exiting...'); 20 | } 21 | }); 22 | 23 | console.log('Hello! Welcome to Algorithms Study Helper.\nEnter a number to begin...'); 24 | prompt(); 25 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algorithms-study-helper", 3 | "version": "1.0.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "node-cleanup": { 8 | "version": "2.1.2", 9 | "resolved": "https://registry.npmjs.org/node-cleanup/-/node-cleanup-2.1.2.tgz", 10 | "integrity": "sha1-esGavSl+Caf3KnFUXZUbUX5N3iw=" 11 | }, 12 | "readline-sync": { 13 | "version": "1.4.9", 14 | "resolved": "https://registry.npmjs.org/readline-sync/-/readline-sync-1.4.9.tgz", 15 | "integrity": "sha1-PtqOZfI80qF+YTAbHwADOWr17No=" 16 | } 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "algorithms-study-helper", 3 | "version": "1.0.0", 4 | "description": "", 5 | "main": "index.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "author": "", 10 | "license": "ISC", 11 | "dependencies": { 12 | "node-cleanup": "^2.1.2", 13 | "readline-sync": "^1.4.9" 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /prompt.js: -------------------------------------------------------------------------------- 1 | const fs = require('fs'); 2 | const readline = require('readline'); 3 | 4 | const rl = readline.createInterface({ 5 | input: process.stdin, 6 | output: process.stdout 7 | }); 8 | 9 | rl.on('SIGINT', () => { 10 | process.kill(process.pid, 'SIGINT'); 11 | }); 12 | 13 | // Read entries into memory 14 | let entries = JSON.parse(fs.readFileSync('entries.json', 'utf8') || '[]'); 15 | 16 | let entriesDue = 0; 17 | // Find how many entries are due. 18 | function findEntriesDue () { 19 | let now = Date.now(); 20 | for (let i = entries.length - 1 - entriesDue; i >= 0; i--) { 21 | if (entries[i].nextScheduled > now) { 22 | break; 23 | } 24 | entriesDue++; 25 | } 26 | 27 | return entriesDue; 28 | } 29 | 30 | function prompt () { 31 | const PROMPT = 32 | `================================================================================== 33 | [1] Review (${findEntriesDue()} of ${entries.length} Due) | [2] Add Entry | [3] Show Schedule | [4] Exit 34 | > `; 35 | 36 | rl.question(PROMPT, (answer) => { 37 | switch (answer) { 38 | case '1': review(); break; 39 | case '2': promptAndAddEntry(); break; 40 | case '3': printSchedule(); prompt(); break; 41 | case '4': process.exit(0); 42 | default: 43 | console.log('That is not a valid option. Please enter a number.'); 44 | prompt(); 45 | } 46 | }); 47 | } 48 | 49 | function promptAndAddEntry () { 50 | rl.question('What is the entry name?\n> ', (answer) => { 51 | let entry = { 52 | name: answer, 53 | lastStudied: 'Never', 54 | nextScheduled: Date.now(), 55 | mastery: 0, 56 | subMastery: 0 57 | }; 58 | 59 | addEntry(entry); 60 | entriesDue++; 61 | prompt(); 62 | }); 63 | } 64 | 65 | // Inserts entry into schedule based on nextScheduled property 66 | function addEntry (entry) { 67 | if (entries.length === 0) { 68 | entries.push(entry); 69 | } else { 70 | for (var i = entries.length - 1; i >= 0; i--) { 71 | if (entries[i].nextScheduled > entry.nextScheduled) { 72 | break; 73 | } 74 | } 75 | entries.splice(i + 1, 0, entry); 76 | } 77 | } 78 | 79 | const ONE_DAY_IN_MILLISECONDS = 86400000; // 1000*60*60*24 80 | 81 | // Higher retention rate = longer interval between studying algorithms 82 | const RETENTION_RATE = 2; 83 | 84 | const QUESTION = 85 | `How difficult was it? 86 | [1] Easy | [2] Medium | [3] Hard | [4] Buggy Solution | [5] Failed to Complete 87 | > `; 88 | 89 | function review () { 90 | if (entries.length === 0) { 91 | console.log('You have no algorithms in your library. Please add an entry.'); 92 | prompt(); 93 | } else { 94 | let entry = entries[entries.length - 1]; 95 | 96 | if (entry.nextScheduled <= Date.now()) { 97 | console.log('=================================================================================='); 98 | console.log(`Implement the following: [${entry.name}].`); 99 | rl.question(QUESTION, (answer) => { 100 | let subMasteryPoints = 0; 101 | 102 | switch (answer) { 103 | case '1': subMasteryPoints = 4; break; 104 | case '2': subMasteryPoints = 2; break; 105 | case '3': subMasteryPoints = 1; break; 106 | case '4': subMasteryPoints = 0; break; 107 | case '5': 108 | entry.mastery = 0; 109 | entry.subMastery = 0; 110 | break; 111 | default: 112 | console.log('That is not a valid option. Please enter a number.'); 113 | review(); 114 | return; 115 | } 116 | 117 | entry.subMastery += subMasteryPoints; 118 | if (entry.subMastery >= 4) { 119 | entry.subMastery = 0; 120 | if (entry.mastery < 5) { 121 | entry.mastery += 1; 122 | } 123 | } 124 | entry.lastStudied = Date.now(); 125 | entry.nextScheduled = Date.now() + ONE_DAY_IN_MILLISECONDS * Math.pow(RETENTION_RATE, entry.mastery); 126 | // entry.nextScheduled = Date.now() + ONE_DAY_IN_MILLISECONDS * (entry.mastery * RETENTION_RATE + 1); 127 | 128 | entries.pop(); 129 | entriesDue--; 130 | addEntry(entry); 131 | 132 | prompt(); 133 | }); 134 | } else { 135 | console.log('You have finished reviewing all scheduled algorithms.\nGood job! Come back later. :)'); 136 | prompt(); 137 | } 138 | } 139 | } 140 | 141 | const NUM_ENTRIES_TO_DISPLAY = 10; 142 | function printSchedule () { 143 | if (entries.length === 0) { 144 | console.log('You have no algorithms in your library. Please add some.'); 145 | } else { 146 | let tempLastStudied; 147 | let tempNextScheduled; 148 | 149 | let i = entries.length >= NUM_ENTRIES_TO_DISPLAY ? entries.length - NUM_ENTRIES_TO_DISPLAY : 0; 150 | for (; i < entries.length; i++) { 151 | let entry = entries[i]; 152 | tempLastStudied = entry.lastStudied; 153 | tempNextScheduled = entry.nextScheduled; 154 | entry.lastStudied = entry.lastStudied === 'Never' ? 'Never' : new Date(entry.lastStudied).toString(); 155 | entry.nextScheduled = new Date(entry.nextScheduled).toString(); 156 | console.log(entry); 157 | entry.lastStudied = tempLastStudied; 158 | entry.nextScheduled = tempNextScheduled; 159 | } 160 | 161 | console.log('Showing next 10 scheduled entries.\nEntry with closest next scheduled date is at the bottom.'); 162 | } 163 | } 164 | 165 | // Advance mastery when submastery >= 4 166 | // failed to complete: Drop back to mastery 0 167 | // Buggy solution: Add 0 to sub-mastery. 168 | // Hard: Add 1 to sub-mastery. 169 | // Medium: Add 2 to sub-mastery. 170 | // Easy: Add 4 to sub-mastery. 171 | 172 | // mastery 0: every 1 day 173 | // mastery 1: every 2 days 174 | // mastery 2: every 4 days 175 | // mastery 3: every 8 days 176 | // mastery 4: every 16 days 177 | // mastery 5: every 32 days 178 | 179 | module.exports = { 180 | prompt: prompt, 181 | entries: entries 182 | }; 183 | --------------------------------------------------------------------------------