├── README.md ├── .gitignore ├── assignments ├── [4] regular-expression-engine.md ├── [4] version-control-system.md ├── [3] web-systems-architecture.md ├── [5] parser-generator.md ├── [1] command-line-options-parser.md ├── [2] web-server-and-client.md ├── README.md └── [2] build-automation-tool.md └── solutions └── README.md /README.md: -------------------------------------------------------------------------------- 1 | # Project Omega 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | *.pyc 3 | -------------------------------------------------------------------------------- /assignments/[4] regular-expression-engine.md: -------------------------------------------------------------------------------- 1 | 2 | ## Topics 3 | 4 | - If you're not already familiar with Regular Expressions, learn about them. 5 | 6 | 7 | ## Practice Problems 8 | 9 | - Write regexes to validate basic strings like email addresses, dates, etc. 10 | 11 | 12 | ## Graded Assignment 13 | 14 | Implement a regular expression engine. It should at least be able to support the following cases: 15 | 16 | ``` 17 | regex.check("abcde", "abcde") // true 18 | regex.check("a\d?\w+\s*e", "abcde") // true 19 | regex.check("^a[b-k]{2}$", "abcde") // false 20 | ``` 21 | -------------------------------------------------------------------------------- /assignments/[4] version-control-system.md: -------------------------------------------------------------------------------- 1 | 2 | ## Resources 3 | 4 | All large software projects use a version control system. 5 | Read the [Git Parable](http://tom.preston-werner.com/2009/05/19/the-git-parable.html) and the [Pro Git book](https://git-scm.com/book/en/v2). 6 | 7 | ## Graded Assignment 8 | 9 | And now, without looking at the code for any existing VCS, implement your own. 10 | Assuming that the executable is called `vcs`, support at least the following commands: 11 | 12 | ``` 13 | vcs init 14 | vcs status # Show the list of changed files. 15 | vcs diff # Show the diffs of changed files. 16 | vcs commit # Asks for a commit message, and then creates it. 17 | vcs reset # Discard all changes since the last commit. 18 | vcs log # Show commit message + metadata 19 | vcs checkout # Load the specified commit. 20 | ``` 21 | 22 | Note: For the sake of simplicity, do not bother with the concept of a staging area. 23 | -------------------------------------------------------------------------------- /solutions/README.md: -------------------------------------------------------------------------------- 1 | ## General Instructions 2 | 3 | - This directory will contain the results of the assignments submitted by the various participants of this exercise. 4 | - If a subdirectory with your name does not exist, create one using the naming scheme: lower-case-separated-by-dashes. 5 | - Within your subdirectory, please use a reasonable naming scheme, like "assignment-1.py" or "assignment-2/index.js" that makes it easy to figure out which assignment your code is for. Please use a separate pull-request for your solution to each assignment. 6 | - Your assignment is considered complete when your pull request successfully passes code review. At that time, the pull request will be closed, but your code will NOT be merged into the main repository. 7 | - While I have no way of enforcing this, I ask that you do not look at other people's work for a given assignment, unless you've completed it yourself. This will ensure that your own learning experience is unaffected. -------------------------------------------------------------------------------- /assignments/[3] web-systems-architecture.md: -------------------------------------------------------------------------------- 1 | 2 | ## Practice Problem 1 3 | 4 | - Take a screenshot of your Facebook newsfeed. Reproduce it from scratch using only HTML & CSS (no JavaScript). 5 | - Do not use any external libraries / stylesheets. It doesn't have to be pixel-perfect, since there is a point of diminishing returns, but the goal here is to teach you enough HTML such that you can understand the page source for a website when you need to. 6 | 7 | ## Practice Problem 2 8 | 9 | - Install [MySQL](https://www.mysql.com/) and [Memcached](https://memcached.org/) on your system. 10 | - Connect to these services using client libraries in the language of your choice. 11 | 12 | ## Graded Assignment 13 | 14 | - Create a basic website that allows you to manage a to-do list. 15 | - The user should be able to create an account. The username, password and other information should be stored in a MySQL database. 16 | - The user should be able to login, and logout. The current login status should be stored in a cookie. The server validates the cookie by checking it against information stored in Memcache. 17 | - While logged in, the user can add items to the to-do list. These items are also stored in the database. 18 | -------------------------------------------------------------------------------- /assignments/[5] parser-generator.md: -------------------------------------------------------------------------------- 1 | 2 | ## Resources 3 | 4 | - Warning: You should complete the Regular Expression Engine assignment before attempting this one. 5 | - Watch this series of lectures on YouTube about [Compiler Design](https://www.youtube.com/watch?v=Qkwj65l_96I&index=1&list=PLEbnTDJUr_IcPtUXFy2b1sGRPsLFMghhS). For what it's worth, this is one of the best teachers available online. 6 | 7 | ## Graded Assignment 8 | 9 | Write a program that can take the following grammar as input: 10 | ``` 11 | expression = term ('+' expression)?; 12 | term = factor ('*' term)?; 13 | factor = /[0-9]+/ | '(' expression ')'; 14 | ``` 15 | And based on that, generate an SLR(1) parser that can translate `2*3+(4+5)*6` into a JSON structure like the following: 16 | ``` 17 | { 18 | "name": "expression", 19 | "children": [ 20 | { 21 | "name": "term", 22 | "children": [ 23 | { 24 | "name": "factor", 25 | "children": [{"value": "2"}] 26 | }, 27 | { 28 | "value": "*" 29 | }, 30 | { 31 | "name": "factor", 32 | "children": [{"value": "3"}] 33 | } 34 | ] 35 | }, 36 | { 37 | "value": "+", 38 | }, 39 | ... 40 | ] 41 | } 42 | ``` 43 | 44 | For bonus points, generate an LALR(1) parser in a language **different** from the one used to analyse the input grammar. 45 | -------------------------------------------------------------------------------- /assignments/[1] command-line-options-parser.md: -------------------------------------------------------------------------------- 1 | 2 | ## Graded Assignment 3 | 4 | - Write a library to parse command line options, and print them as JSON. It should at least support the following inputs: 5 | 6 | ``` 7 | # Basic Usage 8 | ./test --key=12345 --name=kaustubh 9 | {"key": "value", "name": "kaustubh"} 10 | 11 | # Validation 12 | ./test --key=cat 13 | Error: The value for the '--key' argument must be a positive integer. 14 | ./test 15 | Error: The '--key' argument is required, but missing from input. 16 | ./test --local --remote 17 | Error: The "--local" and "--remote" arguments cannot be used together. 18 | ``` 19 | 20 | - Do not use an existing library (such as those mentioned below) to do the heavy lifting for you, since it would defeat the purpose of this exercise. 21 | 22 | ## Topics 23 | 24 | - Once you have completed your implementation, then I'd recommend reading about the APIs provided by [Python's argparse module](https://docs.python.org/3/library/argparse.html), [JavaScript's commander module](https://www.npmjs.com/package/commander), etc. Your goal here is to be confident that you can implement all the features listed there, even if you don't actually write the code for them (although doing so is a bonus). This way, you have knowledge about how different languages deal with this problem. Additionally, if something exists in one language, but not in another, you would know that it is a valid usecase. In fact you could be the one to build it in your language! 25 | -------------------------------------------------------------------------------- /assignments/[2] web-server-and-client.md: -------------------------------------------------------------------------------- 1 | 2 | ## Practice Problem 3 | 4 | Using the basic socket library for your preferred language, create a TCP server & client. 5 | 6 | - The server listens on a given port, waiting for a client to connect. 7 | - The client connects to the server via the above port, and sends 2 numbers, separated by a space. 8 | - The server receives the message, waits for 2 seconds, and then responds with the sum of the 2 numbers. 9 | - The client gets the result, verifies that it is correct, and then ends. 10 | - The server continues waiting for other clients. 11 | 12 | ## Graded Assignment 1 13 | 14 | Using the same basic socket library as the practice problem, write a program that takes a URL as input, and downloads the file into the current directory. 15 | 16 | Since you're not allowed to use Python's `urllib` module, JavaScript's `http` module, or any other similar ones, you will need to study the [HTTP protocol](https://www.httpwatch.com/httpgallery/introduction/), and figure how to make the necessary request. 17 | 18 | ## Graded Assignment 2 19 | 20 | Using the same basic socket library as the practice problem, implement a webserver that can handle basic `GET` requests. 21 | - If the requested path is a directory, check to see if there is an `index.html` file in that directory, and return that. If not, generate a list of files and return those instead. 22 | - If the requested path is a file, check to see if it actually exists in a given directory. If yes, provide that, or else return a `404` error. 23 | -------------------------------------------------------------------------------- /assignments/README.md: -------------------------------------------------------------------------------- 1 | ## General Instructions 2 | 3 | * Submit your solutions as pull-requests to the `solutions` subdirectory in this repository. Make sure you check out the instructions specified there too. 4 | * The number in the square brackets (ranging from 1-5) indicates my estimate of the difficulty of the problem. 5 | * If you are concerned that you don't know a particular technology / language required to solve a problem, good. Use this opportunity to learn about it. If you can prove that a problem is too easy because of past experience, let me know, and I'll give you a harder problem. 6 | * I recommend using Python or JavaScript (since I'm most familiar with them right now). However, you are free to choose any language you wish, as long as it is not something that makes code deliberately hard to read. If I don't know it, I will find someone who can review your code in my place, or else study the language and then review it myself. 7 | 8 | ## Coding Instructions 9 | 10 | 11 | 1. If I do not provide sample input/output in the problem, make reasonable assumptions. If I do, they are still just guidelines, so feel free to make reasonable changes. 12 | * Your goal isn't to pass some automated test-case checker: instead, it is to build something that will be __intuitively useful__ to other people, and so use your best judgement. 13 | 2. Please abide by the [style guide for your preferred language](https://google.github.io/styleguide/). 14 | * Why? Code is written only once, but read many many times, and so we want to optimize for that. If everyone follows the same conventions while writing code, it makes other people's code look like your own, and therefore, easier to understand. 15 | * A good analogy is that writing code without following a style guide is like using texting language in professional communication ("whr r u", instead of "Where are you?"). No one will take you seriously. Which is why this needs to become a habit ASAP. 16 | * Feel free to use a linter program that will ensure that your code meets the stylistic expectations. 17 | 3. Please create useful variable / function names. 18 | * When you're working with larger codebases, no one has the time to read all the code. Which means that you have to infer reasonable behavior based purely on names. And using proper descriptive variable / function names is the first step in making that possible. Not doing so would mean that other engineers cannot trust your code, which is not a situation you want to put yourself in. 19 | * Ask yourself: Is it possible to figure out what this variable contains based on just the name? If no, then it means that your variable doesn't have a good name. Avoid abbreviations if possible, since they introduce ambiguity, forcing the reader to figure out what you meant based on context (eg - `cmp` could mean `compare`, `comparator`, `compulsory`, etc). In almost all cases, `temp` (along with all its variants) is a bad name, because it tells me nothing about what it contains. 20 | * Similarly: Given just the function name and appropriate context, if you ask someone else to implement this function, will their code match yours (adjusting for minor differences)? If no, then your function doesn't have a good name. One important observation here is to never do something inside a function that isn't expected by just reading the name (eg - your `check` function should never print anything). 21 | 4. Please write well-structured code with clean abstractions. 22 | * What does that mean? The person reading your code should know how it works based purely on an understanding of the problem, and the API you provide. The fact that one does not have to read the internals of your code to understand it, is nice consequence of making the reasonable choice at every point. And this is extremely valuable while debugging complex systems. 23 | * Additionally, make sure that your code is resuable: that it solves more than just the exact problem you are working on right now. In real life, requirements are always changing, and you want to make sure you can handle that with minimal effort. To simulate that, I will always evaluate your code at a higher standard than the problem statement given to you. 24 | 5. In order to verify that the code works as intended, write comprehensive unit tests. 25 | * When I review your code, the tests are the first thing I look at. Without them, I have no reason to believe that the rest of your code works. 26 | -------------------------------------------------------------------------------- /assignments/[2] build-automation-tool.md: -------------------------------------------------------------------------------- 1 | ## Introduction 2 | 3 | When working on large projects, the process of building and testing code might be fairly complex and involve making assumptions about the environment, running multiple commands in a specific sequence, etc. In order to ensure consistency in the process used by different developers, all of this build/test logic is expressed in "build configuration files" (one of many names for this concept), and then triggered by humans when needed during development. 4 | 5 | Another benefit of a "build automation tool" is that it can be triggered by machines. In case of small projects, a pre-commit-hook could be configured to run all the tests, while for larger projects (where this would take too long) we could have a separate set of machines responsible for periodically building and testing everything. 6 | 7 | Examples = [Make](http://matt.might.net/articles/intro-to-make/), [Buck](https://buck.build/), [NPM](https://docs.npmjs.com/misc/scripts). 8 | 9 | ## Part 1 10 | 11 | You are going to create your own build automation tool. 12 | What follows is a description of how it will work. 13 | 14 | Consider a project with the following file structure: 15 | ``` 16 | algorithms/ 17 | build.json 18 | sort_bubble.cpp 19 | sort_merge.cpp 20 | sort_quick.cpp 21 | build.json 22 | test.cpp 23 | ``` 24 | The `test.cpp` file declares the method: 25 | ``` 26 | void sort(vector &list); 27 | ``` 28 | but does not implement it (ie - it does not #include them, so we need to manually link the object files). The cpp files in the `algorithms` directory provide 3 different implementations for that method, and the desired one is specified during the linking phase. 29 | ``` 30 | > cat algorithms/build.json 31 | [ 32 | { 33 | "name": "clean", 34 | "command": "rm -f *.o" 35 | }, 36 | { 37 | "name": "sort_bubble", 38 | "command": "g++ -c sort_bubble.cpp" 39 | }, 40 | { 41 | "name": "sort_merge", 42 | "command": "g++ -c sort_merge.cpp" 43 | }, 44 | { 45 | "name": "sort_quick", 46 | "command": "g++ -c sort_quick.cpp" 47 | } 48 | ] 49 | 50 | > cat build.json 51 | [ 52 | { 53 | "name": "clean", 54 | "deps": ["algorithms/clean"], 55 | "command": "rm -f test.o && rm -f test_*.exe" 56 | }, 57 | { 58 | "name": "test", 59 | "command": "g++ -std=c++11 -c test.cpp" 60 | }, 61 | { 62 | "name": "test_sort_bubble", 63 | "deps": ["test", "algorithms/sort_bubble"], 64 | "command": "g++ test.o algorithms/sort_bubble.o -o test_sort_bubble.exe && ./test_sort_bubble.exe" 65 | }, 66 | { 67 | "name": "test_sort_merge", 68 | "deps": ["test", "algorithms/sort_merge"], 69 | "command": "g++ test.o algorithms/sort_merge.o -o test_sort_merge.exe && ./test_sort_merge.exe" 70 | }, 71 | { 72 | "name": "test_sort_quick", 73 | "deps": ["test", "algorithms/sort_quick"], 74 | "command": "g++ test.o algorithms/sort_quick.o -o test_sort_quick.exe && ./test_sort_quick.exe" 75 | }, 76 | { 77 | "name": "test_all", 78 | "deps": ["test_sort_bubble", "test_sort_merge", "test_sort_quick"] 79 | } 80 | ] 81 | ``` 82 | The "build.json" files contain a list of "actions". The build automation tool should be able to explore the given directory structure, create the "action graph", and be capable of executing actions like: 83 | ``` 84 | alias bat="python3 build_automation_tool.py" 85 | 86 | > bat test_all # compiles, links and runs the code 87 | Bubble Sort Latency = 1276817 88 | Merge Sort Latency = 93676 89 | Quick Sort Latency = 3105 90 | 91 | > bat clean # deletes all generated files 92 | ``` 93 | The execution of an action is defined as the execution of all dependency actions, and only then running the associated command. 94 | 95 | ## Part 2 96 | 97 | Instead of performing the actions one-at-a-time in some sequence, your tool should: 98 | * Avoid duplication of work (eg - executing "test_all" naively will perform "test" thrice, but it should only happen once). 99 | * Perform independent actions in parallel when possible (eg - the "test_*" actions can be performed simultaneously). 100 | 101 | Do not use multi-threading here: you can poll the status of multiple processes from a single thread. 102 | 103 | ## Part 3 104 | 105 | Until now, the developer was forced to manually trigger the tests. The final step is to do it automatically, whenever any source file is modified ([example](https://webpack.js.org/configuration/watch/)). 106 | ``` 107 | bat test_all --watch 108 | ``` 109 | If the `algorithms/sort_merge.cpp` files was updated by a text editor, your tool should detect this (without using external libraries like [this one](https://github.com/emcrisostomo/fswatch)), recompile only `algorithms/sort_merge.o` and `test.o` (because the other object files will not change), link everything together and finally run only that test. Feel free to add support for new attributes in the action data structure. 110 | --------------------------------------------------------------------------------