Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
seanchen1991 committed Oct 5, 2018
0 parents commit abe7cca
Show file tree
Hide file tree
Showing 27 changed files with 2,182 additions and 0 deletions.
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
.vscode/
*.pyc
__pycache__/
.DS_Store
47 changes: 47 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
# Algorithms

Now that we've talked about some strategies for solving algorithmic problems and walked through some examples, it's time to practice applying those strategies!

To quickly recap, when you're confronted with a problem you haven't encountered before, the general strategy is as follows:

1. Understand the question you're being asked. The way everyone will go about doing this may be slightly different, but in general, a couple of good ways to do this are:
* Ask clarifying questions to understand the ins and outs of the problem.
* Based on the problem description, think about some edge cases that may be relevant to the problem. If it isn't clear how those edges cases should be handled by your implementation, ask what the expectations are for that particular edge case.

2. Scribble stuff down. This really helps you to keep track of everything that needs to be handled by your solution.
* It is also often helpful to draw out a diagram or list out the steps of how you expect the problem should be solved. This helps you clarify how the solution needs to work, and will oftentimes also clue you in on edge cases you might not have thought of.

3. Come up with a first pass idea as to how to tackle the problem. It doesn't matter if it's a "bad" idea. At this point, we just need somewhere to start.
* Whatever first comes to your mind when looking at the problem in front of you, run with it if you don't have any better ideas. Iteration and improvement of your implementation comes afterwards.
* If you have multiple ideas, _write them all down_, then decide which one you want to go with initially.
* Once you've implemented a solution, think about what your implementation will output given some test inputs. Does it handle all the expected edge cases? Maybe it does and maybe it doesn't. If it doesn't, we'll come back to improve upon it later.

4. Now that we have a first pass implementation, it's time to think about how we can improve upon it.
* Figure out the runtime and space complexity of your first-pass implementation.
* What is it about your first-pass implementation that causes the runtime or space complexity to suffer?
* Think about some ways in which we can improve it. For example, can we utilize memoization?

5. Implement an improved solution to the problem using the idea(s) you came up with from step 4.

Please don't consider this list of steps to be canonical or definitive. Everyone thinks about problems differently. Treat them only as guidelines.

As you accrue more experience solving these sorts of algorithmic problems, you'll start to encounter problems you'll have seen before, i.e., some problems won't be novel to you anymore. In those cases you'll oftentimes be able to implement a better solution right off the bat, i.e., you'll be able to skip step 3.

## What You'll Be Doing

The contents of this sprint are all about giving you the opportunity to practice applying the guidelines that were introduced.

Each directory contains a separate problem that you'll be tasked with solving. Inside each directory, you'll find instructions for that problem, along with a test file as well as an empty skeleton file.

There isn't an official prescribed order for tackling the problems, though a subjective ranking of the given problems from easiest to hardest might go something like this:

1. `stock_prices`
2. `recipe_batches`
3. `counting_stairs`
4. `rock_paper_scissors`
5. `making_change`
6. [Stretch Problem] `knapsack`

For each problem, `cd` into the directory, read the instructions for the problem, implement your solution in the skeleton file, then test it using the provided test file.

The later problems definitely get progressively more difficult, especially when it comes to deriving a more performant solution. Don't feel bad if you aren't able to figure them out within the timeframe of the sprint. Again, always remember that so long as you put in an earnest effort into attempting to solve these problems, you're learning and getting better. Getting the 'right answer' is just the proverbial cherry on top of the sundae.
29 changes: 29 additions & 0 deletions climbing_stairs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Climbing Stairs

Imagine a small child ascending a staircase that has `n` steps. The child can hop either 1, 2, or 3 steps at a time. Implement a function `climbing_stairs` that counts the number of possible ways in which the child can ascend the staircase.

For example, for a staircase with `n = 3` (the stair has 3 steps), there are 4 possible ways for the child to ascend the staircase:

1. They can jump 3 hops of 1 step
2. They can go up 1 step, followed by a jump of 2 steps
3. They can jump up 2 steps, then go up the last step
4. They can make a single jump of 3 steps

Thus, `climbing_stairs(3)` should return an answer of 4.

## Testing

For this problem, there's a test that tests your implementation with small inputs (n <= 10). There's also a separate test that tests your implementation with large inputs (n >= 50).

You'll find that without implementing performance optimizations into your solution, your solution will likely hang on the large input test.

To run the tests separately, run `python test_climbing_stairs.py -k small` in order to run jsut the small input test. Run `python test_climbing_stairs.py -k large` to execute just the large input test. If you want to run both tests, just run `python test_climbing_stairs.py`.

You can also test your implementation manually by executing `python climbing_stairs.py [n]`.

## Hints

* Since this question is asking you to generate a bunch of possible permutations, you'll probably want to use recursion for this.
* Think about base cases that we would want our recursive function to stop recursing on. How many ways are there to climb 0 stairs? What about a negative number of stairs?
* Once we've established some base cases, how can we recursively call our function such that we move towards one or more of these base cases?
* As far as performance optimizations go, caching/memoization might be one avenue we could go down? How should we make a cache available to our recursive function through multiple recursive calls?
14 changes: 14 additions & 0 deletions climbing_stairs/climbing_stairs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
#!/usr/bin/python

import sys

def climbing_stairs(n, cache=None):
pass


if __name__ == "__main__":
if len(sys.argv) > 1:
num_stairs = int(sys.argv[1])
print("There are {ways} ways for a child to jump {n} stairs.".format(ways=climbing_stairs(num_stairs), n=num_stairs))
else:
print('Usage: climbing_stairs.py [num_stairs]')
22 changes: 22 additions & 0 deletions climbing_stairs/test_climbing_stairs.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import unittest
from climbing_stairs import climbing_stairs

class Test(unittest.TestCase):

def test_climbing_stairs_small_n(self):
self.assertEqual(climbing_stairs(0), 1)
self.assertEqual(climbing_stairs(1), 1)
self.assertEqual(climbing_stairs(2), 2)
self.assertEqual(climbing_stairs(5), 13)
self.assertEqual(climbing_stairs(10), 274)

def test_climbing_stairs_large_n(self):
self.assertEqual(climbing_stairs(50, [0 for i in range(51)]), 10562230626642)
self.assertEqual(climbing_stairs(100, [0 for i in range(101)]), 180396380815100901214157639)
self.assertEqual(climbing_stairs(500, [0 for i in range(501)]), 1306186569702186634983475450062372018715120191391192207156664343051610913971927959744519676992404852130396504615663042713312314219527)


if __name__ == '__main__':
unittest.main()


49 changes: 49 additions & 0 deletions knapsack/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# The Knapsack Problem

Suppose you are Indiana Jones, and you have found the secret entrance to the Temple of Doom. Before you is a multitude of artifacts and treasures - pots, gemstones, works of art, and more. These belong in a museum! But there are soldiers hot on your heels, and you can only carry so much...

You, brave explorer, are facing the knapsack problem - maximizing the value of a set of items you select that are constrained by total size/weight. The size and the value of an item need not be correlated - the most precious item may be a tiny gemstone. But it turns out it's pretty tricky to get a truly optimal solution, and that a bruteforce approach really doesn't scale.

A bit more motivation - this is a very general optimization problem that can be applied in a multitude of situations, from resource selection and allocation to stuffing stolen goods in knapsacks.

![xkcd "NP-Complete"](https://imgs.xkcd.com/comics/np_complete.png "General solutions get you a 50% tip.")

The specific goal of this exercise is to write a program that takes input files that look like this:

```
1 42 81
2 42 42
3 68 56
4 68 25
5 77 14
6 57 63
7 17 75
8 19 41
9 94 19
10 34 12
```

The first row number is just a row/observation number, to facilitate reading and referring to items. The second number is the size/cost of the item, i.e. the cost of putting it in your knapsack. The third number is the value, i.e. the utility/payoff you get for selecting that item. The program should also take as input a total size, which can just be a number passed from the command line. So execution may look like this: `python knapsack.py input.txt 100`.

The goal is to select a subset of the items to maximize the payoff such that the cost is below some threshold. That is, the output should be a set of items (identified by number) that solves the Knapsack problem. It's also worth outputting the total cost and value of these items. This can all just be printed and may look something like below.

This is *not* a solution, just an example:

```
Items to select: 2, 8, 10
Total cost: 98
Total value: 117
```

## Testing
For this problem, there are tests that test your implementation with small (10 items to consider), medium (200 items to consider), and large inputs (1000 items to consider).

You can run all of the tests with `python test_knapsack.py`, or run the tests individually with `python test_knapsack.py -k small`, `python test_knapsack.py -k medium`, or `python test_knapsack.py -k large`.

## Hints
1. Base cases you might want to consider for a naive recursive implementation of this problem are:
* What if there are no items left to consider?
* What if the item I'm considering is too large to fit in my bag's remaining capacity?
2. In order to move towards one of our base cases, we'll pick up an item from the pile to consider, and add it to a copy of our bag. Now we have two versions of our bag, one with the item we're considering, and one without. All we have to do now is pick the bag that yields us a higher value.
3. As far as caching for this problem is concerned, a simple hash table or array is not going to suffice, because each solution now depends upon two parameters: the number of items in our pile to consider, as well as the capacity of our bag. So we'll need a 2x2 matrix in order to cache our answers adequately.
4. Here's another way we might consider tackling this problem: what if we iterated through every single element in our pile and assign each one a value given by its value/weight ratio. Then we can sort all of the items based on this assigned value such that those items with a higher value/weight ratio are at the top of our sorted list of items. From there, we can just grab off the items at the top of our list until our bag is full. What would be the runtime complexity of this scheme? Would it work in every single scenario for any pile of items?
Loading

0 comments on commit abe7cca

Please sign in to comment.