Skip to content

Fix typos inside nine md files #25

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 9 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion lessons/arraylist.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ This is actually a bit of a moot point for JavaScript developers: we have normal

## ArrayList

Let's pretend for a moment that JavaScript has no array type. No more `const x = []`. We only have one thing: objects. So we'd need to implement the array numbering ourselves. But not just that, we'd have to implment adding numbers, removing numbers, getting numbers, etc. It's a lot of work!
Let's pretend for a moment that JavaScript has no array type. No more `const x = []`. We only have one thing: objects. So we'd need to implement the array numbering ourselves. But not just that, we'd have to implement adding numbers, removing numbers, getting numbers, etc. It's a lot of work!

I'm borrowing the Java terms for these, by the way.

Expand Down
4 changes: 2 additions & 2 deletions lessons/big-o.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,7 @@ For some people, it's helpful to use a graph to visualize what we're talking abo

![graph of y = 1, y = x + 1, and y = x^2 + 1](./images/graph.png)

Here we see a graph that represents the more items we put in a array, how long does it take for the function to complete. The red graph represnts O(1) like our `getMiddleOfArary` function. You can throw an array of 1,000,000 at it and it still takes the same amount of time as if the array was 10 big.
Here we see a graph that represents the more items we put in a array, how long does it take for the function to complete. The red graph represents O(1) like our `getMiddleOfArary` function. You can throw an array of 1,000,000 at it and it still takes the same amount of time as if the array was 10 big.

The blue line represents a function that takes longer based on how many items are in the array similar to `crossAdd` or `find` and it grows a steady rate. If it takes 10ms to run a function with a 100 items in it, we could reasonably expect it would take 10 times longer-ish (remember, these are broad strokes, not precise figures) if we had 10 times the amount of things in the array.

Expand All @@ -89,6 +89,6 @@ This sort of analysis is useful for taking a high level view. It's a useful tool

A good example would be if we were designing a comment system for a site and it had a sorting and filtering ability. If this is for a school and there would only ever be a few comments at a time, we probably don't need to do too much Big O analysis because it's such a small set of people that a computer can overcome just about any computational inefficiency we have. In this case I'd value human time over computer time and just go with the simplest solution and not worry about the Big O unless performance because a problem later.

Okay, now, if we're designing a comment system but it's for Reddit.com, our needs change _dramatically_. We're now talking about pipelines of millions of users making billions of comments. Our performance targets need to change to address such volume. A O(n²) alogrithm would crash the site.
Okay, now, if we're designing a comment system but it's for Reddit.com, our needs change _dramatically_. We're now talking about pipelines of millions of users making billions of comments. Our performance targets need to change to address such volume. A O(n²) algorithm would crash the site.

This is absolutely essential to know about Big O analysis. It's useless without context. If you're asked is in a situation if a O(n) or a O(n²) your answer should be "it depends" or "I need more context". If the O(n) algorithm is extremely difficult to comprehend and the O(n²) algorithm is dramatically easier to understand and performance is a total non-issue, then the O(n²) is a much better choice. It's similar to asking a carpenter if they want a hammer or a sledgehammer without any context. Their answer will be "what do you need me to do?".
6 changes: 3 additions & 3 deletions lessons/graphs.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ description: ""
icon: "map-signs"
---

Let's chat about a datastructure that is extremely useful, you probably interact with many on a daily basis, but you may not use them in your code on a day-to-day: graphs. Graphs are all about modeling relations between many items. For example, think of Facebook's Social Graph. I'm friends with you and you're friends with me. But you're also friends with six hundred other people which is about five hundred fifty too many. Those people in turn also have too friends. But many of my friends are your friends, so the connections aren't linear, they're … well, they're graph-like.
Let's chat about a data structure that is extremely useful, you probably interact with many on a daily basis, but you may not use them in your code on a day-to-day: graphs. Graphs are all about modeling relations between many items. For example, think of Facebook's Social Graph. I'm friends with you and you're friends with me. But you're also friends with six hundred other people which is about five hundred fifty too many. Those people in turn also have too friends. But many of my friends are your friends, so the connections aren't linear, they're … well, they're graph-like.

In the Facebook example, each person would be a node. A node represents some entity, much like a row in an SQL database. Every so-called "friendship" would be called an edge. An edge represents some connection between two items. In this case, our Facebook friendship is bidirectional: if I'm friends with you then you're friends with me. Twitter would be an example of a unidirectional edge: just because I follow you doesn't mean you follow me.

Expand All @@ -25,7 +25,7 @@ me Alice

In this case, let's say I'm looking for what the job titles are for the people within my second degree network: my connections and their connections, or no more than two edges away from me. If hop first to Bob, then I'll count Sally and Alice in his connections. If I hop to Maria, then I'll count Alice in her connections … for the second time. This is where graphs differ a bit: since there's no clear parent-child relationship you need to be aware there will be cycles (e.g. A points to B, B points to C, C points to A, circles in graphs) and other more difficult patterns to deal with. In this case, I'll just keep track of users I've crawled before and not add them to my total the second time.

So traversing algorithm fits best here? We're analysizing everything in a limited depth of a sub-tree and breadth-first is well equipped to do that. Instead of letting breadth-first traversal run to completion, we'll just limit how many times that outer loop runs, effectively limiting how many levels down it goes, or how many degrees of separation!
So traversing algorithm fits best here? We're analyzing everything in a limited depth of a sub-tree and breadth-first is well equipped to do that. Instead of letting breadth-first traversal run to completion, we'll just limit how many times that outer loop runs, effectively limiting how many levels down it goes, or how many degrees of separation!

So let's see how you'd do it with out little graph

Expand All @@ -50,7 +50,7 @@ me Alice
-> Finish first iteration, one degree of separation
```

Now if we did another degree of separation, we'd add Alice and Sally's jobs to the tallys. So we have an outter for loop that goes for each degree of separation. Then you have an inner loop (probably a while loop) that dequeues items from your queue and processes them. You'll then queue up their connections to be processed for the next iteration.
Now if we did another degree of separation, we'd add Alice and Sally's jobs to the tallys. So we have an outer for loop that goes for each degree of separation. Then you have an inner loop (probably a while loop) that dequeues items from your queue and processes them. You'll then queue up their connections to be processed for the next iteration.

## Databases

Expand Down
2 changes: 1 addition & 1 deletion lessons/heap-sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ We're going to be talking about binary heaps today but know there are others. A
- Similar to the above point, if you do an in-order traversal of a BST, you'll get a sorted list. This does not work in a binary heap.
- A binary heap is a "complete binary tree." This means it's as compact as possible. All the children of each node are as full as they can be and left children are filled out first. This is not true of a BST: they can be sparse.

Binary heaps come in two falvors: max heaps and min heaps. We'll be dealing with max heaps (which help you find the greatest number in the heap) but you can imagine that if you flip all the comparisons you'd have a min heap (which helps you find the smallest number.)
Binary heaps come in two flavors: max heaps and min heaps. We'll be dealing with max heaps (which help you find the greatest number in the heap) but you can imagine that if you flip all the comparisons you'd have a min heap (which helps you find the smallest number.)

So this is why a priority queue is often a binary heap: it's very easy to tell the largest number in a binary heap. None of the other is guaranteed, but once you dequeue (or take the next element off) it's easy to find the next item in the queue. In fact, that's how heapsort works: you construct an internal priority queue and then remove an item at a time and stick it at the end and then find the next largest item in the priority queue; rinse and repeat.

Expand Down
2 changes: 1 addition & 1 deletion lessons/merge-sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,7 +115,7 @@ Notice that the 1 in our example never gets compared to the 7 in the array. How?

So let's combine our two terms together. This sort's computational complexity is O(n log n). And that's the best average/worst case for a general purpose sort that we're going to get. This will definitely be significantly faster than the O(n²) we've been seeing so far on larger lists.

What about spatial complexity? Notice we're creating and throwing away a lot of array. This isn't free, and on a large list can be a problem. Merge sort is among the worst because we'll create an array for every item in the array (plus a few more which would just be a coefficent so Big O wouldn't care) so the spatial complexity is O(n).
What about spatial complexity? Notice we're creating and throwing away a lot of array. This isn't free, and on a large list can be a problem. Merge sort is among the worst because we'll create an array for every item in the array (plus a few more which would just be a coefficient so Big O wouldn't care) so the spatial complexity is O(n).

## Exercises

Expand Down
2 changes: 1 addition & 1 deletion lessons/radix-sort.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ So what's the mechanism of doing this? Buckets! For base doing a positive intege

## Big O

So, this is way different than what we've been doing. So far we've just had `n` as a variable which represents how many items there are to sort. In radix sort (and other ones too) we have multiple variables. `n` is still important but now we have another variable in play here (usually called `k` or `w`. Let's go with `k` for our purposes.) `k` is going to represents the "maximum key length", or `d` as we referred to it as above. The more buckets we need, the larger the complexity. So intead of being O(n²) or O(n _ n), it ends up being O(n _ k). So is it better or worse than O(n log n) sorts? It depends! If you have a lot of numbers with lots of varied lengths that will bucket into a good distribution it can be very effective. If you numbers [1, 10, 100, 1000, 10000, 100000] etc it ends up being the worst sort. It ends up being O(n²) at that point.
So, this is way different than what we've been doing. So far we've just had `n` as a variable which represents how many items there are to sort. In radix sort (and other ones too) we have multiple variables. `n` is still important but now we have another variable in play here (usually called `k` or `w`. Let's go with `k` for our purposes.) `k` is going to represents the "maximum key length", or `d` as we referred to it as above. The more buckets we need, the larger the complexity. So instead of being O(n²) or O(n _ n), it ends up being O(n _ k). So is it better or worse than O(n log n) sorts? It depends! If you have a lot of numbers with lots of varied lengths that will bucket into a good distribution it can be very effective. If you numbers [1, 10, 100, 1000, 10000, 100000] etc it ends up being the worst sort. It ends up being O(n²) at that point.

What about the spatial complexity? It ends up being O(n + k) and this why radix sort is really only used in very specific circumstances: it's not great in terms of how much space it takes.

Expand Down
2 changes: 1 addition & 1 deletion lessons/recursion.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ A little mind-melting, right? Let's break it down really quick.

Let's break this down call-by-call. Since recursive functions will break down into further recursive calls, all of them eventually eventually end in a base case. In the `fibonacci(5)` call, you can see eventually all of them end up in n either equalling 2 or 1, our base case. So to get our final answer of 5 (which is the correct answer) we end up adding 1 to itself 5 times to get 5. That seems silly, right?

So what if we call `fibonacci(30)`? The answer is 832040. You guessed it, we add 1 to itelf, 832040 times. What if we call `fibonacci(200)`? Chances are you'll get a stack overflow.
So what if we call `fibonacci(30)`? The answer is 832040. You guessed it, we add 1 to itself, 832040 times. What if we call `fibonacci(200)`? Chances are you'll get a stack overflow.

Not very efficient here, but very elegant code. Here you'd need to trade off having readable code versus a relatively poor performance profile. If your use case says you'll only need to call no more than with n = 10, yeah, this is probably okay. If you need to call it with n = 200, you need to rewrite it to something different.

Expand Down
4 changes: 2 additions & 2 deletions lessons/spatial-complexity.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ So far we've just talked about _computational complexity_. In general if someone

Let's say we have an algorithm that for every item in the array, it needs to create another array in the process of sorting it. So for an array of length 10, our algorithm will create 10 arrays. For an array of 100, it'd create 100 extra arrays (or something close, remember these are broad strokes, not exact.) This would be O(n) in terms of its spatial complexity. We'll do some sorts that do this.

## Logrithmic
## Logarithmic

What about another for every item in the array, it needed to create a diminishing amount of extra arrays. For example: for an array of length 10, it'd create 7 arrays. For an array of 100, it'd create 12 arrays. For an array of 1000, it'd created 20 arrays. This would be O(log n).

Expand All @@ -34,7 +34,7 @@ I will say O(n²) in spatial complexity is pretty rare and a big red flag.

## Okay, sure, but why

As before, this is just a tool to make sure your design fits your needs. One isn't necessarily better than the other. And very frequently you need to make the trade off of computational complexity vs spatial. Some algoriths eat a lot of memory but go fast and there are lots that eat zero memory but go slow. It just depends on what your needs are.
As before, this is just a tool to make sure your design fits your needs. One isn't necessarily better than the other. And very frequently you need to make the trade off of computational complexity vs spatial. Some algorithms eat a lot of memory but go fast and there are lots that eat zero memory but go slow. It just depends on what your needs are.

Here's an example: let's say you're writing code that's going to be run a PlayStation 3 and it needs to sort 1000 TV shows according to what show you think the customer is going to want to see. PS3s have a decent processor but very little memory available to apps. In this case, we'd want to trade off in favor of spatial complexity and trade off against computational complexity: the processor can do more work so we can save memory.

Expand Down
2 changes: 1 addition & 1 deletion lessons/wrap-up.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ icon: "flag-checkered"

Congratulations! This course is a long one and definitely not one to take lightly. Don't expect all of this to sink in instantly, and certainly don't expect to have perfect recall of these. Even though I've learned these concepts many times, taught them many time, and even taught this very course a few times I _still_ have to look them up when I use them. The point isn't to have a perfect recall of them, it's to learn new ways of solving problems.

More than anything, I hope you took away the science of trading off. Sometimes you want go for raw performance, sometimes you want to use as little memory as possible, and frequently it's somewhere in the middle. More than anything, I hope you choose to write clean, readable code. If you take away nothing else, it's that: readable code is almost always the goal. Performance is secondary to that. Human time is nearly always more valuable than computer time. Spend time trying to solve problems for your clients and customers and writing code that you can maintain and less on pointless optimizations that likely won't make any difference to your users. Only when you need to get raw performance should you trade off the needs of maintainence and customers.
More than anything, I hope you took away the science of trading off. Sometimes you want go for raw performance, sometimes you want to use as little memory as possible, and frequently it's somewhere in the middle. More than anything, I hope you choose to write clean, readable code. If you take away nothing else, it's that: readable code is almost always the goal. Performance is secondary to that. Human time is nearly always more valuable than computer time. Spend time trying to solve problems for your clients and customers and writing code that you can maintain and less on pointless optimizations that likely won't make any difference to your users. Only when you need to get raw performance should you trade off the needs of maintenance and customers.

Thank you for sticking with me! I hope you enjoyed the course and I will catch you again soon!!

Expand Down