From b3ac7259857f8463d6bd7ea88d8eb6ce97f122e6 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 4 Aug 2021 10:13:13 -0300 Subject: [PATCH 1/9] fix typo in arraylist.md --- lessons/arraylist.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lessons/arraylist.md b/lessons/arraylist.md index f49624d..61ec8e3 100644 --- a/lessons/arraylist.md +++ b/lessons/arraylist.md @@ -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. From 39bca29a4e06b639c4cafa0bcef4fa3067c4d33c Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 4 Aug 2021 10:14:44 -0300 Subject: [PATCH 2/9] fix typos in big-o.md --- lessons/big-o.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lessons/big-o.md b/lessons/big-o.md index def72bc..c267d01 100644 --- a/lessons/big-o.md +++ b/lessons/big-o.md @@ -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. @@ -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?". From aee22496e57227829e5ee38c6caa85681cbb20ce Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 4 Aug 2021 10:16:59 -0300 Subject: [PATCH 3/9] fix typos in graphs.md --- lessons/graphs.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lessons/graphs.md b/lessons/graphs.md index 723f653..f58d3cb 100644 --- a/lessons/graphs.md +++ b/lessons/graphs.md @@ -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. @@ -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 @@ -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 From 0c3aadcd0d0c7619ab6d0106288c3cef0dfada6e Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 4 Aug 2021 10:18:16 -0300 Subject: [PATCH 4/9] fix typo in heap-sort.md --- lessons/heap-sort.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lessons/heap-sort.md b/lessons/heap-sort.md index 19386b4..c50d895 100644 --- a/lessons/heap-sort.md +++ b/lessons/heap-sort.md @@ -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. From 8dd130746902c0033ca5c7698c7d9611f8e25c20 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 4 Aug 2021 10:22:18 -0300 Subject: [PATCH 5/9] fix typos in merge-sort.md --- lessons/merge-sort.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lessons/merge-sort.md b/lessons/merge-sort.md index 007cc6d..fe5bd42 100644 --- a/lessons/merge-sort.md +++ b/lessons/merge-sort.md @@ -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 From 299546c183a3b14bb5d213d412e47933bb651ca2 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 4 Aug 2021 10:23:37 -0300 Subject: [PATCH 6/9] fix typo in radix-sort.md --- lessons/radix-sort.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lessons/radix-sort.md b/lessons/radix-sort.md index ace343a..5dafd3e 100644 --- a/lessons/radix-sort.md +++ b/lessons/radix-sort.md @@ -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. From 9d1540b4ad0e10c6bcb577607e28dfcfde9725c7 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 4 Aug 2021 10:23:52 -0300 Subject: [PATCH 7/9] fix typo in recursion.md --- lessons/recursion.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lessons/recursion.md b/lessons/recursion.md index cedc6db..0fdc1a2 100644 --- a/lessons/recursion.md +++ b/lessons/recursion.md @@ -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. From b24c47d953e5d0ce07b575c3f2167726be782877 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 4 Aug 2021 10:24:21 -0300 Subject: [PATCH 8/9] fix typos in spatial-complexity.md --- lessons/spatial-complexity.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lessons/spatial-complexity.md b/lessons/spatial-complexity.md index 750c111..a7213a6 100644 --- a/lessons/spatial-complexity.md +++ b/lessons/spatial-complexity.md @@ -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). @@ -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. From 3cfcef7fab2abc3249e8405e8c5f34269d9496c9 Mon Sep 17 00:00:00 2001 From: Dylan Buchi Date: Wed, 4 Aug 2021 10:24:31 -0300 Subject: [PATCH 9/9] fix typos in wrap-up.md --- lessons/wrap-up.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lessons/wrap-up.md b/lessons/wrap-up.md index bb731b3..b7ee217 100644 --- a/lessons/wrap-up.md +++ b/lessons/wrap-up.md @@ -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!!