diff --git a/00-welcome/00-welcome-to-technical-interview-preparation/README.md b/00-welcome/00-welcome-to-technical-interview-preparation/README.md new file mode 100644 index 00000000..fbc10575 --- /dev/null +++ b/00-welcome/00-welcome-to-technical-interview-preparation/README.md @@ -0,0 +1,49 @@ +# Welcome to Technical Interview Preparation + +In addition to preparing a portfolio of awesome projects to show off to your +future employer, you’ll also need to be prepared for technical interviews. In +this course, we’ll help you prepare by asking you to solve a series of problems +on algorithms and/or data structures. You may find some problems easy and others +incredibly difficult. Don’t worry - this is normal. It takes time to get used to +solving these types of problems. + +Before we get into how to approach progressing through this section, let’s talk +about the two types of technical interviews. + +## Two Types of Technical Interviews + +Your technical interview may be held asynchronously or synchronously. During an +asynchronous technical interview, you may be asked to solve algorithmic problems +in a timed environment by yourself. For a synchronous interview, you will likely +be asked to whiteboard and solve the problem in front of your interviewer. + +Async technical interviews typically require candidates to problem solve alone. +Once the solution is submitted, the hiring team will review your solution and +determine whether you should move forward in the hiring process. At this point, +they’ll likely require that most or all problems be solved and will also +consider the quality of the solution/s. + +During synchronous interviews, the interviewer is likely looking to see how you: + +- Work in a team +- Handle feedback +- Talk technically +- Handle obstacles +- Approach solving a problem / think +- Evaluate different approaches to solving a problem and make decisions + +At this stage, you may be able to move forward in the hiring process if you +demonstrate good communication skills, logical thinking, perseverance, calm +under pressure, and graciousness towards your interviewer’s feedback even if you +are unable to solve the problem. Asking good questions to fully understand the +problem or get unstuck are normally welcome. Interviewers also typically expect +you to come up with your own test cases to ensure the problem is actually +solved. This portion of the interview might require you to largely solve the +problem alone on a whiteboard or IDE, or through pair programming where the +interviewer is either the driver or navigator. Many interviewers are forgiving +of syntax errors, and they generally don’t expect you to have every piece of the +core language memorized. + +In this course, we will be preparing you for both types of interviews, so it’s +important to take the time to solve as many problems as you can alone and to +complete the paired assignments, if applicable. diff --git a/00-welcome/01-algorithmic-problem-solving/README.md b/00-welcome/01-algorithmic-problem-solving/README.md new file mode 100644 index 00000000..f3f34872 --- /dev/null +++ b/00-welcome/01-algorithmic-problem-solving/README.md @@ -0,0 +1,124 @@ +# Algorithmic Problem Solving + +An algorithm is just a procedure that solves a problem. If you’re wondering if +that’s any different from the work you’ve already been doing, we’ve got good +news: It’s not! + +In this reading, we’ll go over the importance of solving such problems and how +to approach coming up with solutions. + +## Why Solve Algorithm Problems? + +Working through these challenges will not only help you during the technical +interview process, it will also help you become a better programmer. Learning +how to break apart a problem into pieces you can understand and then code a +solution for is a vital skill, as is learning to think about the different types +of inputs - or test cases - your solution must account for. + +As you grow as a developer, you’ll find yourself leaning on these skills more +and more, especially as your work becomes more challenging. These skills will +truly help you in any and every project you take on. + +## How to Solve a Problem + +Two mistakes many programmers make are to jump into code too fast or to start +thinking about code optimization too early. Both of these mistakes can greatly +increase the amount of time it takes to solve a problem and increase +frustration. To avoid this, here are the steps we recommend taking: + +### 1. Spend Time Understanding the Problem + +Before you dive into solving the problem, take the time to read it and describe +it in your own words. You might find it useful to rewrite the problem before +moving on. + +If you have been given test cases, look at each one, apply your understanding of +the problem to them to determine what the answer is, and then check if your +answer matches the actual answer (e.g. work it out on paper or in your head, no +code necessary here). If your answer doesn’t match, you need to spend more time +understanding the problem. + +### 2. Write Your Own Test Cases + +Now that you understand the problem and why the answers to the test cases are +what they are, you’re ready to write your own test cases! We are not +recommending that you write test suites in Rspec or Jest. Instead, you simply +print the result of calling your solution method and compare it to the answer +you expected. + +Be aware that algorithm problem descriptions rarely provide all of the test +cases you need to account for, so it’s incredibly important that you also come +up with your own. This is true when using online platforms, such as Leetcode and +HackerRank, as well as during interviews. + +### 3. Pseudocode + +Remember how we asked you to check your understanding of the problem by going +through the test cases and then writing your own tests? Congratulations! This +means that on some level, you know how to solve the problem. Before you start +coding, write pseudocode, which is just a plain description of how to solve the +problem. For example, the pseudocode for copying only numbers from one array to +another might look like this: + +``` +initialize empty array called result + +iterate over each item in the input array: + if element is a number: + push item onto result + +return result +``` + +Note that different people write pseudocode differently. The key is to make it +easy to understand yourself and explain to others - this is the map to the code +you’re about to write! I often paste my pseudocode into my workspace as +comments, and then code each piece alongside the matching comment. + +You can also test this procedure against the test cases before writing any code. +Validating and rewriting pseudocode will likely save you time. You might also +wish to think about additional solutions: there’s always more than one way to +solve a problem. + +### 4. Code! + +Now that you have a map, convert it to code! + +At this point, the goal is to make it work: pass those test cases! If you’re +having a hard time getting all of the test cases working, check that your +pseudocode actually solves for all of those cases and then check that your code +does what the pseudocode says it should. + +### 5. Make It Clean and Readable + +Once your solution is well, a solution, it’s time to refactor your code so that +it’s easy to read, not just for you but also for others. Use well-named +variables and convert blocks of code to methods when necessary. If you find any +unneeded or redundant code, delete it. + +Don’t forget to test your code again! + +### 6. Optimize? + +Don’t optimize your code for time or space complexity (e.g. how long it takes to +run or how much memory it’s using) unless you absolutely need to. There are +three major situations that call for optimization: + +- The solution is hanging on certain test cases, and therefore cannot pass since it’s taking too long +- You were asked to do so +- You think it would be fun to try + +### Conclusion + +We hope these steps help you solve the problems you’re about to encounter. +Remember, they can be applied to all types of problems, including building web +apps. Don’t be afraid to spend more time thinking and planning than coding. Take +it from those of us who have been coding for years: we often spend more time +thinking, talking, and writing than we do coding. + +Before we go, we’d like to leave you with some final tips: + +- Talk to yourself while you code: think out loud +- Consider recording your screen and voice as you solve a problem so you can + review your performance +- Take your time and be patient with yourself! diff --git a/00-welcome/02-a-note-on-testing/README.md b/00-welcome/02-a-note-on-testing/README.md new file mode 100644 index 00000000..b2161e5c --- /dev/null +++ b/00-welcome/02-a-note-on-testing/README.md @@ -0,0 +1,16 @@ +# A Note on Testing + +If we have made a test suite available for a problem, please refrain from +looking at or running those tests until you have: + +- Passed the tests given in the README +- Passed the tests you wrote yourself + +This will give you a chance to think through the problem and write your own test +cases, which is a skill you’ll need when interviewing. + +After you run our tests, you might notice that you missed some test cases: this +is a good thing - it’s a chance for you to learn and grow as a programmer. You +won’t always think of all of the necessary test cases, but as time goes by, +you’ll get better and better at thinking of all of the possible inputs that need +to be solved for before a solution can be considered complete. diff --git a/00-welcome/03-problem-solving-tips/README.md b/00-welcome/03-problem-solving-tips/README.md new file mode 100644 index 00000000..1858eb23 --- /dev/null +++ b/00-welcome/03-problem-solving-tips/README.md @@ -0,0 +1,54 @@ +# Problem-Solving Tips + +If you’re stuck, ask yourself these questions to see if they help you get +unstuck: + +- Have I solved a similar problem before? How can I use that knowledge to solve + this one? +- Can I break this problem down into smaller problems that are easier to solve? +- Would it help to draw a picture or diagram of the problem? +- Which inputs might this method receive? Do my test cases cover the edge cases? +- Should I draw a table that maps inputs to outputs? Your test cases will then + mirror that table! The process of drawing the table might also reveal a + pattern, which you can then use to solve the problem. + + - Example table for a problem where an Array of distinct values must be returned: + + | Input | Output | + | ------------------- | ------------- | + | [1, 2, 2, 3] | [1, 2, 3] | + | [] | [] | + | [4] | [4] | + | [3, 2, 2, 10, 2, 7] | [3, 2, 10, 7] | + +- Do I really understand the problem? You can prove this to yourself by solving + it on paper without thinking about code. +- Can I talk to someone about this and explain my thinking? + - Sometimes the act of talking to someone even if they don’t say anything + helps you get unstuck. It forces you to explain the problem in more + understandable language. +- What information is available to me in this problem? What additional + information can I derive? + - If you have experience writing or deriving mathematical equations, this is a + similar process: list which variables and mathematical operations are + available and then the additional information that can be derived from that. + - If you have experience creating artwork, this is similar to the process of + understanding what tools you have available to you and how you can use them, + e.g. what colors are in my palette and how can I mix them? Or I have a + string, tape, and a pencil, and I need to draw a circle - how do I do that? + - Example: For a problem where an Array of distinct values must be returned, + some of the information available includes: the Array itself, each element + in the Array, its length, and the index of each item. You can find a list of + Array operations available by looking at the documentation. +- Can or should I create additional data to help me solve the problem? What do I + need to keep track of for my solution to work? + - Consider adding variables and ask yourself which data structures are + required and how they might help. + - Example: For a problem where an Array of distinct values must be returned, + you might think about using an additional Array or a Hash (aka POJO/Object + in JavaScript or Dictionary in Python) or a Set. +- Have I taken care of myself today? Do I need to eat? Am I hydrated? Am I + rested? Do I need to move my body? + - You’ll be surprised how much better your brain works after eating a good + meal, drinking water, resting and/or exercising! Don’t let yourself get + [hangry](https://www.merriam-webster.com/dictionary/hangry)! diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/.gitignore b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/README.md b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/README.md new file mode 100644 index 00000000..2c45cd81 --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/README.md @@ -0,0 +1,53 @@ +# Day 1: Reverse a String + +For this task, you'll need to reverse a string. Your method will receive one argument, a string, and be expected to output that string with its letters in reverse order. + +``` +Input: "hi" +Output: "ih" + +Input: "catbaby" +Output: "ybabtac" +``` + +**Do not call any type of built-in reverse method!** + +Please solve the problem using iteration. + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/javascript/package.json b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/javascript/package.json new file mode 100644 index 00000000..df161685 --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "reverse_string", + "version": "1.0.0", + "description": "reverse a string", + "main": "reverse_string.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} \ No newline at end of file diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/javascript/reverse_string.js b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/javascript/reverse_string.js new file mode 100644 index 00000000..9eda6e9f --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/javascript/reverse_string.js @@ -0,0 +1,19 @@ +function reverseString(str) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 'ih'"); + console.log("=>", reverseString("hi")); + + console.log(""); + + console.log("Expecting: 'ybabtac'"); + console.log("=>", reverseString("catbaby")); +} + +module.exports = reverseString; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/javascript/tests/reverse_string.test.js b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/javascript/tests/reverse_string.test.js new file mode 100644 index 00000000..9b2e65cd --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/javascript/tests/reverse_string.test.js @@ -0,0 +1,21 @@ +const reverseString = require('../reverse_string'); + +test("can handle an empty string", () => { + expect(reverseString("")).toBe(""); +}); + +test("can handle a single character", () => { + expect(reverseString("a")).toBe("a"); +}); + +test("can handle two characters", () => { + expect(reverseString("ab")).toBe("ba"); +}); + +test("can handle three characters", () => { + expect(reverseString("cat")).toBe("tac"); +}); + +test("can handle many characters", () => { + expect(reverseString("sham-meow")).toBe("sham-meow".split("").reverse().join("")); +}); diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/.rspec b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/Gemfile b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/reverse_string.rb b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/reverse_string.rb new file mode 100644 index 00000000..99487efc --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/reverse_string.rb @@ -0,0 +1,18 @@ +def reverse_string(str) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 'ih'" + puts "=>", reverse_string('hi') + + puts + + puts "Expecting: 'ybabtac'" + puts "=>", reverse_string('catbaby') + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution \ No newline at end of file diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/spec/reverse_string_spec.rb b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/spec/reverse_string_spec.rb new file mode 100644 index 00000000..f906e168 --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/spec/reverse_string_spec.rb @@ -0,0 +1,23 @@ +require './reverse_string' + +RSpec.describe '#reverse_string' do + it "can handle an empty string" do + expect(reverse_string('')).to eq('') + end + + it "can handle a single character" do + expect(reverse_string('a')).to eq('a') + end + + it "can handle two characters" do + expect(reverse_string('ab')).to eq('ba') + end + + it "can handle three characters" do + expect(reverse_string('cat')).to eq('tac') + end + + it "can handle many characters" do + expect(reverse_string('sham-meow')).to eq('sham-meow'.reverse) + end +end \ No newline at end of file diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/spec/spec_helper.rb b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/solutions/reverse_string.js b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/solutions/reverse_string.js new file mode 100644 index 00000000..01400d4b --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/solutions/reverse_string.js @@ -0,0 +1,47 @@ +function reverseString(str) { + let reversed = ""; + + for (let i = str.length - 1; i > -1; --i) { + reversed = reversed + str[i]; + } + + return reversed; +} + +console.log("Expecting: 'ih'"); +console.log(reverseString('hi')); + +console.log(""); + +console.log("Expecting: 'ybabtac'"); +console.log(reverseString('catbaby')); + +console.log(""); + +console.log("Expecting: 'a'"); +console.log(reverseString('a')); + +console.log(""); + +console.log("Expecting: '' (empty string)"); +console.log(reverseString('')); + +// Please add your pseudocode to this file +/************************************************** + * initialize a variable called reversed with an empty string + * + * iterate backwards through the input string: + * set reversed to reversed + current character + * + * return reversed + * ************************************************/ + + +// And a written explanation of your solution +/************************************************** + * If I iterate over the input string backwards, I should be able + * to add whichever character I'm currently iterating over onto + * my result string (""). For example, if the string is "ab", I'll iterate + * over "b" first, add that to my result ("b"), and then iterate over + * "a", and add that to my result next ("ba"). + * ************************************************/ diff --git a/01-week-1--starter-algorithms/00-day-1--reverse-a-string/solutions/reverse_string.rb b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/solutions/reverse_string.rb new file mode 100644 index 00000000..14962243 --- /dev/null +++ b/01-week-1--starter-algorithms/00-day-1--reverse-a-string/solutions/reverse_string.rb @@ -0,0 +1,49 @@ +def reverse_string(str) + reversed_str = "" + + str.chars.each do |char| + reversed_str = char + reversed_str + end + + reversed_str +end + +puts "Expecting: 'ih'" +puts reverse_string('hi') + +puts + +puts "Expecting: 'ybabtac'" +puts reverse_string('catbaby') + +puts + +puts "Expecting: '' (empty string)" +puts reverse_string('') + +puts + +puts "Expecting: 'a'" +puts reverse_string('a') + +############################################################################ +# Please add your pseudocode to this file: + +# initialize reversed_str to store empty string + +# iterate over each letter in the input string: +# place character before previous characters and store result in reversed_str + +# return reversed_str +############################################################################ + +############################################################################ +# And a written explanation of your solution: + +# The simplest way I can think of reversing a string is to start by thinking of a really +# simple example: "ab" + +# To solve for "ab", you take "a", store it in a variable, and then take the next character "b", +# and place it before "a" in that same variable. If we do this for each character, we should be +# able to reverse the string. +############################################################################ \ No newline at end of file diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/.gitignore b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/README.md b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/README.md new file mode 100644 index 00000000..4ecd5f51 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/README.md @@ -0,0 +1,49 @@ +# Day 2: Find First Duplicate + +Given an Array, find the first duplicate value that occurs. If there are no duplicates, return -1. + +``` +Input: [2, 1, 3, 3, 2] +Output: 3 + +Input: [1, 2, 3, 4] +Output: -1 +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/javascript/find_first_duplicate.js b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/javascript/find_first_duplicate.js new file mode 100644 index 00000000..ff277623 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/javascript/find_first_duplicate.js @@ -0,0 +1,19 @@ +function findFirstDuplicate(arr) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 3"); + console.log("=>", findFirstDuplicate([2, 1, 3, 3, 2])); + + console.log(""); + + console.log("Expecting: -1"); + console.log("=>", findFirstDuplicate([1, 2, 3, 4])); +} + +module.exports = findFirstDuplicate; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/javascript/package.json b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/javascript/package.json new file mode 100644 index 00000000..94217e3e --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "find_first_duplicate", + "version": "1.0.0", + "description": "find_first_duplicate", + "main": "find_first_duplicate.js", + "dependencies": {}, + "devDependencies": { + "jest": "^26.6.3" + }, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/javascript/tests/find_first_duplicate.test.js b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/javascript/tests/find_first_duplicate.test.js new file mode 100644 index 00000000..b409c830 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/javascript/tests/find_first_duplicate.test.js @@ -0,0 +1,17 @@ +const findFirstDuplicate = require('../find_first_duplicate'); + +test('can handle an empty array', () => { + expect(findFirstDuplicate([])).toBe(-1); +}); + +test('can handle an array containing one element', () => { + expect(findFirstDuplicate([4])).toBe(-1); +}); + +test('finds the first duplicate when there is only one duplicate', () => { + expect(findFirstDuplicate([2, 2])).toBe(2); +}); + +test('finds the first duplicate in an Array containing multiple duplicates', () => { + expect(findFirstDuplicate([1, 2, 3, 3, 2, 1])).toBe(3); +}); diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/.rspec b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/Gemfile b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/find_first_duplicate.rb b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/find_first_duplicate.rb new file mode 100644 index 00000000..10ee1758 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/find_first_duplicate.rb @@ -0,0 +1,18 @@ +def find_first_duplicate(arr) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 3" + puts "=>", find_first_duplicate([2, 1, 3, 3, 2]) + + puts + + puts "Expecting: -1" + puts "=>", find_first_duplicate([1, 2, 3, 4]) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/spec/find_first_duplicate_spec.rb b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/spec/find_first_duplicate_spec.rb new file mode 100644 index 00000000..2e9f0b61 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/spec/find_first_duplicate_spec.rb @@ -0,0 +1,19 @@ +require './find_first_duplicate' + +RSpec.describe '#find_first_duplicate' do + it 'can handle an empty array' do + expect(find_first_duplicate([])).to eq(-1) + end + + it 'can handle an array containing one element' do + expect(find_first_duplicate([4])).to eq(-1) + end + + it 'finds the first duplicate when there is only one duplicate' do + expect(find_first_duplicate([2, 2])).to eq(2) + end + + it 'finds the first duplicate in an Array containing multiple duplicates' do + expect(find_first_duplicate([1, 2, 3, 3, 2, 1])).to eq(3) + end +end \ No newline at end of file diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/spec/spec_helper.rb b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/solutions/find_first_duplicate.js b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/solutions/find_first_duplicate.js new file mode 100644 index 00000000..48ed48b0 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/solutions/find_first_duplicate.js @@ -0,0 +1,62 @@ +function findFirstDuplicate(arr) { + const uniques = new Set(); + + for (const value of arr) { + if (uniques.has(value)) { + return value; + } + + uniques.add(value); + } + + return -1; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 3"); + console.log(findFirstDuplicate([2, 1, 3, 3, 2])); + + console.log(""); + + console.log("Expecting: -1"); + console.log(findFirstDuplicate([1, 2, 3, 4])); + + console.log(""); + + console.log("Expecting: -1"); + console.log(findFirstDuplicate([])); + + console.log(""); + + console.log("Expecting: -1"); + console.log(findFirstDuplicate([5])); + + console.log(""); + + console.log("Expecting: 7"); + console.log(findFirstDuplicate([7, 1, 2, 3, 7])); +} + +module.exports = findFirstDuplicate; + +// Please add your pseudocode to this file +/****************************************************************** + * initialize an empty set called uniques + * + * iterate through the input array: + * if the value is in uniques, return the value + * else add the value to the set + * + * return -1 if no duplicate found during iteration + * ***************************************************************/ + +// And a written explanation of your solution +/******************************************************************* + * A Set is a data structure that contains only unique objects/values. + * If I check if a value is in a Set before adding it, I'll know if there's + * a duplicate. If there's a duplicate, I'll just return that value right + * away because that'll be the first duplicate in the input array. If we + * exit iteration without returning anything, that means there's no duplicate, + * so we'll return -1 + * *******************************************************************/ diff --git a/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/solutions/find_first_duplicate.rb b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/solutions/find_first_duplicate.rb new file mode 100644 index 00000000..4c01b486 --- /dev/null +++ b/01-week-1--starter-algorithms/01-day-2--find-first-duplicate/solutions/find_first_duplicate.rb @@ -0,0 +1,59 @@ +require 'set' + +def find_first_duplicate(arr) + uniques = Set.new + + arr.each do |value| + return value if uniques.include?(value) + + uniques.add(value) + end + + -1 +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 3" + puts find_first_duplicate([2, 1, 3, 3, 2]) + + puts + + puts "Expecting: -1" + puts find_first_duplicate([1, 2, 3, 4]) + + puts + + puts "Expecting: -1" + puts find_first_duplicate([]) + + puts + + puts "Expecting: -1" + puts find_first_duplicate([4]) + + puts + + puts "Expecting: 7" + puts find_first_duplicate([7, 1, 2, 3, 7]) +end + +# Please add your pseudocode to this file +#################################################################### + # initialize an empty set called uniques + # + # iterate through the input array: + # if the value is in uniques, return the value + # else add the value to the set + # + # return -1 if no duplicate found during iteration +##################################################################### + +# And a written explanation of your solution +#################################################################### + # A Set is a data structure that contains only unique objects/values. + # If I check if a value is in a Set before adding it, I'll know if there's + # a duplicate. If there's a duplicate, I'll just return that value right + # away because that'll be the first duplicate in the input array. If we + # exit iteration without returning anything, that means there's no duplicate, + # so we'll return -1 + #################################################################### diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/.gitignore b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/README.md b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/README.md new file mode 100644 index 00000000..2520e998 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/README.md @@ -0,0 +1,54 @@ +# Day 3: Fibonacci Series + +Find the nth element in the Fibonacci series. The Fibonacci sequence starts with a 0 followed by a 1. After that, every value is the sum of the two values preceding it. Here are the first seven values as an example: 0, 1, 1, 2, 3, 5, 8. + +``` +Input: 0 +Output: 0 + +Input: 2 +Output: 1 + +Input: 10 +Output: 55 +``` + +Note that we are using zero-indexing for the series. + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/javascript/fibonacci.js b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/javascript/fibonacci.js new file mode 100644 index 00000000..d10cae73 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/javascript/fibonacci.js @@ -0,0 +1,24 @@ +function fibonacci(num) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 0"); + console.log("=>", fibonacci(0)); + + console.log(""); + + console.log("Expecting: 1"); + console.log("=>", fibonacci(2)); + + console.log(""); + + console.log("Expecting: 55"); + console.log("=>", fibonacci(10)); +} + +module.exports = fibonacci; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/javascript/package.json b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/javascript/package.json new file mode 100644 index 00000000..c0b37698 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "fibonacci", + "version": "1.0.0", + "description": "find nth value in fibo sequence", + "main": "fibonacci.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} \ No newline at end of file diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/javascript/tests/fibonacci.test.js b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/javascript/tests/fibonacci.test.js new file mode 100644 index 00000000..2a46c7a7 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/javascript/tests/fibonacci.test.js @@ -0,0 +1,13 @@ +const fibonacci = require('../fibonacci'); + +const fibo = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811]; + +for (let i = 0; i < 10; ++i) { + test(`outputs the correct number in the sequence at index ${i}`, () => { + expect(fibonacci(i)).toBe(fibo[i]); + }); +} + +test('outputs the correct number in the sequence at index 28', () => { + expect(fibonacci(28)).toBe(fibo[28]); +}); diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/.rspec b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/Gemfile b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/fibonacci.rb b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/fibonacci.rb new file mode 100644 index 00000000..2837ba84 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/fibonacci.rb @@ -0,0 +1,23 @@ +def fibonacci(num) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 0" + puts "=>", fibonacci(0) + + puts + + puts "Expecting: 1" + puts "=>", fibonacci(2) + + puts + + puts "Expecting: 55" + puts "=>", fibonacci(10) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/spec/fibonacci_spec.rb b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/spec/fibonacci_spec.rb new file mode 100644 index 00000000..93dd94b0 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/spec/fibonacci_spec.rb @@ -0,0 +1,15 @@ +require './fibonacci' + +RSpec.describe '#fibonacci' do + fibo = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811] + + 10.times do |n| + it "outputs the correct number in the sequence at index #{n}" do + expect(fibonacci(n)).to eq(fibo[n]) + end + end + + it "outputs the correct number at index 28" do + expect(fibonacci(28)).to eq(fibo[28]) + end +end \ No newline at end of file diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/spec/spec_helper.rb b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/solutions/fibonacci.js b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/solutions/fibonacci.js new file mode 100644 index 00000000..ae251bea --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/solutions/fibonacci.js @@ -0,0 +1,67 @@ +function fibonacci(num) { + if (num < 2) { + return num; + } + + let lastTwo = [0, 1]; + + for (let i = 0; i < num - 1; ++i) { + const sum = lastTwo[0] + lastTwo[1]; + lastTwo = [lastTwo[1], sum]; + } + + return lastTwo[1]; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 0"); + console.log(fibonacci(0)); + + console.log(""); + + console.log("Expecting: 1"); + console.log(fibonacci(2)); + + console.log(""); + + console.log("Expecting: 55"); + console.log(fibonacci(10)); + + console.log(""); + + console.log("Expecting: 1"); + console.log(fibonacci(1)); + + console.log(""); + + console.log("Expecting: 6765"); + console.log(fibonacci(20)); +} + +module.exports = fibonacci; + +// Please add your pseudocode to this file +/******************************************************************** + * if the input is 0 or 1, return the input + * + * initialize an Array called lastTwo with the first two values from the sequence + * + * loop input - 1 times: + * store the sum of the values in lastTwo in a variable called sum + * remove the first element from lastTwo + * push sum onto lastTwo + * + * return the last value in lastTwo + * *****************************************************************/ + +// And a written explanation of your solution +/******************************************************************** + * Since the fibonacci sequence is always the same, I can initialize an Array + * with the first two values of the sequence. I only need to store two values + * because the next value is the sum of the two values that came before it. By + * adding up the two values I'm storing, I'll always get the next value. If I do + * that enough times (num - 1), once I'm done adding up the values, the last value + * will be the nth value in the sequence. Every time I add up the two values, I need + * to remove the 0th value I'm storing and then push the sum onto that array. +***********************************************************************/ \ No newline at end of file diff --git a/01-week-1--starter-algorithms/02-day-3--fibonacci-series/solutions/fibonacci.rb b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/solutions/fibonacci.rb new file mode 100644 index 00000000..50f92dd2 --- /dev/null +++ b/01-week-1--starter-algorithms/02-day-3--fibonacci-series/solutions/fibonacci.rb @@ -0,0 +1,63 @@ +def fibonacci(num) + last_two = [0, 1] + + return last_two[num] if num < 2 + + (num - 1).times do + sum = last_two[0] + last_two[1] + last_two = [last_two[1], sum] + end + + last_two.last +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 0" + puts fibonacci(0) + + puts + + puts "Expecting: 1" + puts fibonacci(2) + + puts + + puts "Expecting: 55" + puts fibonacci(10) + + # Don't forget to add your own! + puts + + puts "Expecting: 1" + puts fibonacci(1) + + puts + + puts "Expecting: 6765" + puts fibonacci(20) +end + +# Please add your pseudocode to this file +######################################################################### +# initialize an Array called last_two with the first two values from the sequence +# +# if the input is 0 or 1, return the value at that index from last_two +# +# loop input - 1 times: +# store the sum of the values in last_two in a variable called sum +# remove the first element from last_two +# push sum onto last_two +# +# return the last value in last_two +######################################################################### + +# And a written explanation of your solution +######################################################################### +# Since the fibonacci sequence is always the same, I can initialize an Array +# with the first two values of the sequence. I only need to store two values +# because the next value is the sum of the two values that came before it. By +# adding up the two values I'm storing, I'll always get the next value. If I do +# that enough times (num - 1), once I'm done adding up the values, the last value +# will be the nth value in the sequence. Every time I add up the two values, I need +# to remove the 0th value I'm storing and then push the sum onto that array. +######################################################################### diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/.gitignore b/01-week-1--starter-algorithms/03-day-4--selection-sort/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/README.md b/01-week-1--starter-algorithms/03-day-4--selection-sort/README.md new file mode 100644 index 00000000..82a50293 --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/README.md @@ -0,0 +1,64 @@ +# Day 4: Selection Sort + +Sort an Array of numbers using selection sort. The selection sort algorithm sorts an array by repeatedly finding the minimum element (lowest value) in the input Array, and then putting it at the correct location in the sorted Array. + +``` +Input: [3, -1, 5, 2] +Output: [-1, 2, 3, 5] +``` + +**Benchmarking** + +For this task, we are also asking you to calculate the average runtime of your solution. In other words, you run it a bunch of times and then divide the total time it took for the solution to run by the number of times it ran. + +Here is the pseudocode for creating your own basic benchmarking procedure: + +``` +store the current time in a variable called start time + +loop 1000 times: + execute the method using a small input, e.g. three items if input is an Array + execute the method using a larger input, e.g. 100 items if input is an Array + +average runtime = (current time - start time) / 2000 +``` + +We have provided you with the long input to use for benchmarking. + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/javascript/package.json b/01-week-1--starter-algorithms/03-day-4--selection-sort/javascript/package.json new file mode 100644 index 00000000..7ac9270d --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "selection-sort", + "version": "1.0.0", + "description": "sort using selection sort", + "main": "selection_sort.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/javascript/selection_sort.js b/01-week-1--starter-algorithms/03-day-4--selection-sort/javascript/selection_sort.js new file mode 100644 index 00000000..230c8030 --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/javascript/selection_sort.js @@ -0,0 +1,23 @@ +function selectionSort(arr) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [-1, 2, 3, 5]"); + console.log("=>", selectionSort([3, -1, 5, 2])); + + console.log(""); + + // BENCHMARK HERE, and print the average runtime + const longInput = []; + + for (let i = 0; i < 100; ++i) { + longInput.push(Math.random()); + } +} + +module.exports = selectionSort; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/javascript/tests/selection_sort.test.js b/01-week-1--starter-algorithms/03-day-4--selection-sort/javascript/tests/selection_sort.test.js new file mode 100644 index 00000000..7a4574ba --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/javascript/tests/selection_sort.test.js @@ -0,0 +1,26 @@ +const selectionSort = require('../selection_sort'); + +test('can handle an empty array', () => { + expect(selectionSort([])).toEqual([]); +}); + +test('can sort one element', () => { + expect(selectionSort([5])).toEqual([5]); +}); + +test('can sort two elements', () => { + expect(selectionSort([3, 1])).toEqual([1, 3]); +}); + +test('can sort several elements', () => { + expect(selectionSort([10, 4, 3, 2, 1, 5])).toEqual([1, 2, 3, 4, 5, 10]); +}); + +test('can sort negative and positive values', () => { + expect(selectionSort([-1, -2, 4, 2])).toEqual([-2, -1, 2, 4]); +}); + +test('can sort an array containing repeating values', () => { + expect(selectionSort([1, 4, 2, 1, 2, 4, 20, -2])).toEqual([1, 4, 2, 1, 2, 4, 20, -2].sort((a, b) => a - b)); +}); + diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/.rspec b/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/Gemfile b/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/selection_sort.rb b/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/selection_sort.rb new file mode 100644 index 00000000..d9134f52 --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/selection_sort.rb @@ -0,0 +1,21 @@ +def selection_sort(arr) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [-1, 2, 3, 5]" + print "=> " + print selection_sort([3, -1, 5, 2]) + + puts + + # Don't forget to add your own! + + # BENCHMARK HERE, you can print the average runtime + long_input = [] + + 100.times { long_input << rand } +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/spec/selection_sort_spec.rb b/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/spec/selection_sort_spec.rb new file mode 100644 index 00000000..58a629b5 --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/spec/selection_sort_spec.rb @@ -0,0 +1,27 @@ +require './selection_sort' + +RSpec.describe '#selection_sort' do + it 'can handle an empty array' do + expect(selection_sort([])).to eq([]) + end + + it 'can sort one element' do + expect(selection_sort([5])).to eq([5]) + end + + it 'can sort two elements' do + expect(selection_sort([3, 1])).to eq([1, 3]) + end + + it 'can sort several elements' do + expect(selection_sort([10, 4, 3, 2, 1, 5])).to eq([1, 2, 3, 4, 5, 10]) + end + + it 'can sort negative and positive values' do + expect(selection_sort([-1, -2, 4, 2])).to eq([-2, -1, 2, 4]) + end + + it 'can sort an array containing repeating values' do + expect(selection_sort([1, 4, 2, 1, 2, 4, 20, -2])).to eq([1, 4, 2, 1, 2, 4, 20, -2].sort) + end +end \ No newline at end of file diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/spec/spec_helper.rb b/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/solutions/selection_sort.js b/01-week-1--starter-algorithms/03-day-4--selection-sort/solutions/selection_sort.js new file mode 100644 index 00000000..92774be4 --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/solutions/selection_sort.js @@ -0,0 +1,84 @@ +function selectionSort(arr) { + const sorted = []; + + while (arr.length > 0) { + const min = Math.min(...arr); + const idx = arr.indexOf(min); + + sorted.push(min); + arr.splice(idx, 1); + } + + return sorted; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [-1, 2, 3, 5]"); + console.log(selectionSort([3, -1, 5, 2])); + + console.log(""); + + console.log("Expecting: []"); + console.log(selectionSort([])); + + console.log(""); + + console.log("Expecting: [-1]"); + console.log(selectionSort([-1])); + + console.log(""); + + console.log("Expecting: [-10, 2, 2, 3, 7]"); + console.log(selectionSort([3, 2, 2, 7, -10])); + + console.log(""); + + console.log("Expecting: [100, 200]"); + console.log(selectionSort([100, 200])); + + console.log(""); + + // BENCHMARK HERE, and print the average runtime + const longInput = []; + + for (let i = 0; i < 100; ++i) { + longInput.push(Math.random()); + } + + const startTime = Date.now(); + + for (let i = 0; i < 1000; ++i) { + selectionSort([2, 1]); + selectionSort(longInput); + } + + const avgTime = (Date.now() - startTime) / 2000; + + console.log(avgTime); +} + +module.exports = selectionSort; + +// Please add your pseudocode to this file +/*************************************************************************** + * initialize an empty Array called sorted + * + * loop array length times: + * store minimum value in array in min + * remove minimum value from input array + * push min onto sorted + * + * return sorted +****************************************************************************/ + +// And a written explanation of your solution +/**************************************************************************** + * The selection sort algorithm states that we need to find the minimum value + * of the input array and place it in another array until all of the values have + * been placed. This means I can find the minimum in the input, remove it from the + * input, and push it onto another array in order to sort the values. The first time + * I find the minimum in the input, that's the lowest value, the second time I find + * the minimum, that's the second lowest value, and so on. Once the input array is + * empty, the sorting is complete. + * **************************************************************************/ \ No newline at end of file diff --git a/01-week-1--starter-algorithms/03-day-4--selection-sort/solutions/selection_sort.rb b/01-week-1--starter-algorithms/03-day-4--selection-sort/solutions/selection_sort.rb new file mode 100644 index 00000000..ab7aa6eb --- /dev/null +++ b/01-week-1--starter-algorithms/03-day-4--selection-sort/solutions/selection_sort.rb @@ -0,0 +1,78 @@ +def selection_sort(arr) + sorted = [] + + until arr.length == 0 + min = arr.min + idx = arr.index(min) + sorted << min + arr.delete_at(idx) + end + + sorted +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [-1, 2, 3, 5]" + print selection_sort([3, -1, 5, 2]) + + puts + + puts "Expecting: []" + print selection_sort([]) + + puts + + puts "Expecting: [-1]" + print selection_sort([-1]) + + puts + + puts "Expecting: [-10, 2, 2, 3, 7]" + print selection_sort([3, 2, 2, 7, -10]) + + puts + + puts "Expecting: [100, 200]" + print selection_sort([100, 200]) + + puts + # Don't forget to add your own! + + # BENCHMARK HERE, you can print the average runtime + long_input = [] + + 100.times { long_input << rand } + + start_time = Time.now + + 1000.times do + selection_sort([2, 1]) + selection_sort(long_input) + end + + avg_time = (Time.now - start_time) / 2000 + puts avg_time +end + +# Please add your pseudocode to this file +############################################################################ + # initialize an empty Array called sorted + # + # loop array length times: + # store minimum value in array in min + # remove minimum value from input array + # push min onto sorted + # + # return sorted +############################################################################ + +# And a written explanation of your solution +############################################################################ + # The selection sort algorithm states that we need to find the minimum value + # of the input array and place it in another array until all of the values have + # been placed. This means I can find the minimum in the input, remove it from the + # input, and push it onto another array in order to sort the values. The first time + # I find the minimum in the input, that's the lowest value, the second time I find + # the minimum, that's the second lowest value, and so on. Once the input array is + # empty, the sorting is complete. +############################################################################ diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/.gitignore b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/README.md b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/README.md new file mode 100644 index 00000000..439b0601 --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/README.md @@ -0,0 +1,68 @@ +# Day 5: Find Shortest String + +Given an Array of strings, return the shortest string. If there is more than one string of that length, return the string that comes first in the list. The Array will have a minimum length of 1. + +``` +Input: ['aaa', 'a', 'bb', 'ccc'] +Output: 'a' + +Input: ['cat', 'hi', 'dog', 'an'] +Output: 'hi' + +Input: ['flower', 'juniper', 'lily', 'dandelion'] +Output: 'lily' +``` + +**Benchmarking** + +For this task, we are also asking you to calculate the average runtime of your solution. In other words, you run it a bunch of times and then divide the total time it took for the solution to run by the number of times it ran. + +Here is the pseudocode for creating your own basic benchmarking procedure: + +``` +store the current time in a variable called start time + +loop 1000 times: + execute the method using a small input, e.g. three items if input is an Array + execute the method using a larger input, e.g. 100 items if input is an Array + +average runtime = (current time - start time) / 2000 +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/javascript/find_shortest_string.js b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/javascript/find_shortest_string.js new file mode 100644 index 00000000..0e2d09db --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/javascript/find_shortest_string.js @@ -0,0 +1,26 @@ +function findShortestString(arr) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 'a'"); + console.log("=>", findShortestString(['aaa', 'a', 'bb', 'ccc'])); + + console.log(""); + + console.log("Expecting: 'hi'"); + console.log("=>", findShortestString(['cat', 'hi', 'dog', 'an'])); + + console.log(""); + + console.log("Expecting: 'lily'"); + console.log("=>", findShortestString(['flower', 'juniper', 'lily', 'dadelion'])); + + // BENCHMARK HERE +} + +module.exports = findShortestString; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/javascript/package.json b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/javascript/package.json new file mode 100644 index 00000000..3872e051 --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "find_shortest_string", + "version": "1.0.0", + "description": "find shortest string in a list", + "main": "find_shortest_string.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/javascript/tests/find_shortest_string.test.js b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/javascript/tests/find_shortest_string.test.js new file mode 100644 index 00000000..0bf84bbf --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/javascript/tests/find_shortest_string.test.js @@ -0,0 +1,17 @@ +const findShortestString = require('../find_shortest_string'); + +test('can handle an array containing one string', () => { + expect(findShortestString(['cat'])).toBe('cat'); +}); + +test('returns the shortest string when there is only one', () => { + expect(findShortestString(['dogbaby', 'cat', 'jammy', 'hamtaro'])).toBe('cat'); +}); + +test('returns the first occurrence of the shortest string when there are several', () => { + expect(findShortestString(['stuff', 'a', 'things', 'b', 'two'])).toBe('a'); +}); + +test('returns the empty string', () => { + expect(findShortestString(['things', 'crabapple', '', 'stuff'])).toBe(''); +}); diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/.rspec b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/Gemfile b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/find_shortest_string.rb b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/find_shortest_string.rb new file mode 100644 index 00000000..a6734404 --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/find_shortest_string.rb @@ -0,0 +1,25 @@ +def find_shortest_string(arr) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 'a'" + puts "=>", find_shortest_string(['aaa', 'a', 'bb', 'ccc']) + + puts + + puts "Expecting: 'hi'" + puts "=>", find_shortest_string(['cat', 'hi', 'dog', 'an']) + + puts + + puts "Expecting: 'lily'" + puts "=>", find_shortest_string(['flower', 'juniper', 'lily', 'dadelion']) + + # Don't forget to add your own! + + # BENCHMARK HERE +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/spec/find_shortest_string_spec.rb b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/spec/find_shortest_string_spec.rb new file mode 100644 index 00000000..ecb8c62b --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/spec/find_shortest_string_spec.rb @@ -0,0 +1,19 @@ +require './find_shortest_string' + +RSpec.describe '#find_shortest_string' do + it 'can handle an array containing one string' do + expect(find_shortest_string(['cat'])).to eq('cat') + end + + it 'returns the shortest string when there is only one' do + expect(find_shortest_string(['dogbaby', 'cat', 'jammy', 'hamtaro'])).to eq('cat') + end + + it 'returns the first occurrence of the shortest string when there are several' do + expect(find_shortest_string(['stuff', 'a', 'things', 'b', 'two'])).to eq('a') + end + + it 'returns the empty string' do + expect(find_shortest_string(['things', 'crabapple', '', 'stuff'])).to eq('') + end +end diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/spec/spec_helper.rb b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/solutions/find_shortest_string.js b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/solutions/find_shortest_string.js new file mode 100644 index 00000000..5e7e61c6 --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/solutions/find_shortest_string.js @@ -0,0 +1,70 @@ +function findShortestString(arr) { + // THE LONGER WAY OF DOING THE EXACT SAME THING THAT'S ON LINES 13-14 + // let shortest = arr[0]; + + // arr.forEach(string => { + // if (string.length < shortest.length) { + // shortest = string; + // } + // }); + + // return shortest; + + return arr.reduce((shortest, string) => + string.length < shortest.length ? string : shortest); +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 'a'"); + console.log(findShortestString(['aaa', 'a', 'bb', 'ccc'])); + + console.log(""); + + console.log("Expecting: 'hi'"); + console.log(findShortestString(['cat', 'hi', 'dog', 'an'])); + + console.log(""); + + console.log("Expecting: 'lily'"); + console.log(findShortestString(['flower', 'juniper', 'lily', 'dadelion'])); + + console.log(""); + + console.log("Expecting: 'cat'"); + console.log(findShortestString(['cat'])); + + // BENCHMARK HERE + const startTime = Date.now(); + + for (let i = 0; i < 1000; ++i) { + findShortestString(['flower', 'juniper', 'lily', 'dadelion']); + } + + const avgTime = (Date.now() - startTime) / 1000; + console.log(avgTime); +} + +module.exports = findShortestString; + +// Please add your pseudocode to this file +/**************************************************************************** + * store the first string from the array in a variable called shortest + * + * iterate over the array: + * if the length of the current string < shortest: + * shortest = current string + * + * return shortest +******************************************************************************/ + +// And a written explanation of your solution +/**************************************************************************** + * We can get the first occurrence of the shortest string by storing the first + * string in the input array in a variable. As we iterate over the array, we + * check if the current string is shorter than the shortest string. If it is, + * we store the current string as the shortest string. Since we are checking if + * subsequent strings are shorter than the shortest string, we will always return + * the first occurrence of the shorest string. If we iterated over a string of + * equal length to the shortest string, the value stored wouldn't change. + ******************************************************************************/ \ No newline at end of file diff --git a/01-week-1--starter-algorithms/04-day-5--find-shortest-string/solutions/find_shortest_string.rb b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/solutions/find_shortest_string.rb new file mode 100644 index 00000000..bf4d4c92 --- /dev/null +++ b/01-week-1--starter-algorithms/04-day-5--find-shortest-string/solutions/find_shortest_string.rb @@ -0,0 +1,70 @@ +def find_shortest_string(arr) + # THIS IS THE LONGER WAY OF DOING THE SAME THING ON LINES 13-15 + # shortest = arr[0] + + # arr.each do |string| + # if string.length < shortest.length + # shortest = string + # end + # end + + # return shortest + + arr.reduce do |shortest, string| + string.length < shortest.length ? string : shortest + end +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 'a'" + puts find_shortest_string(['aaa', 'a', 'bb', 'ccc']) + + puts + + puts "Expecting: 'hi'" + puts find_shortest_string(['cat', 'hi', 'dog', 'an']) + + puts + + puts "Expecting: 'lily'" + puts find_shortest_string(['flower', 'juniper', 'lily', 'dadelion']) + + puts + + puts "Expecting: 'cat'" + puts find_shortest_string(['cat']) + + # Don't forget to add your own! + + # BENCHMARK HERE + start_time = Time.now + + 1000.times do + find_shortest_string(['flower', 'juniper', 'lily', 'dadelion']) + end + + avg_time = (Time.now - start_time) / 1000.0 + puts avg_time +end + +# Please add your pseudocode to this file +#################################################################################### + # store the first string from the array in a variable called shortest + # + # iterate over the array: + # if the length of the current string < shortest: + # shortest = current string + # + # return shortest +#################################################################################### + +# And a written explanation of your solution +#################################################################################### + # We can get the first occurrence of the shortest string by storing the first + # string in the input array in a variable. As we iterate over the array, we + # check if the current string is shorter than the shortest string. If it is, + # we store the current string as the shortest string. Since we are checking if + # subsequent strings are shorter than the shortest string, we will always return + # the first occurrence of the shorest string. If we iterated over a string of + # equal length to the shortest string, the value stored wouldn't change. +#################################################################################### diff --git a/02-week-2--recursion/00-introduction-to-recursion/README.md b/02-week-2--recursion/00-introduction-to-recursion/README.md new file mode 100644 index 00000000..dafece5f --- /dev/null +++ b/02-week-2--recursion/00-introduction-to-recursion/README.md @@ -0,0 +1,120 @@ +# Introduction to Recursion + +Recursion is similar to a loop: a procedure is run over and over again until it reaches a stopping point. Recursive methods must call themselves to be considered recursive. We can use recursion anywhere we use a loop, and vice versa. Sometimes, it’s a lot easier to solve a problem using recursion, as opposed to a loop, and sometimes it’s easier to use a loop. It takes time and practice to figure out when to choose one over the other. + +## Example of Never-Ending Recursion + +``` +def talk_to_myself(n) + talk_to_myself(n) +end +``` + +In the above code, the method `talk_to_myself` is recursive because it calls itself. It, however, has a gigantic problem: there is no stopping point! + +## Base Case/s (aka the stopping point/s) + +The base case (or cases) tells the recursive method when to stop running. It is often an if statement, though it doesn’t have to be (it depends on the method and what it needs to do). + +``` +def talk_to_myself(n) + return if n <= 0.5 + + talk_to_myself(n / 2) +end +``` + +Notice how the base case comes before the recursive call to the method. If the base case came after, it would be unreachable and we’d have the same exact problem as before: there would be no stopping point and we’d hit a stack overflow. + +## Stack Overflow + +When we run a while loop where the terminating condition is never reached, we get an infinite loop. A stack overflow is similar. However, code that would eventually terminate can also cause a stack overflow if it adds too many frames to the stack. A frame is like a snapshot of all of the variables and other necessary information required to finish running the process. The stack is a data structure that stores frames. Frames are removed from the stack in last-in-first-out (LIFO) order, similar to how we eat a stack of pancakes (the last pancake is put on the stack last, and we eat that one first). + +## Depth-First Completion (LIFO) + +With recursive methods, the last recursive call will complete its execution first. Once that completes, the second to last recursive call will complete, and so on until only the first call to the method remains. Let’s go back to our `talk_to_myself` method and illustrate each frame: + +``` +def talk_to_myself(n) + return if n <= 0.5 + + talk_to_myself(n / 2) +end +``` + +- Initial Call (execution incomplete, paused on recursive call): + - `talk_to_myself(4)` +- Recursive Call 1 (execution incomplete, paused on recursive call): + - `talk_to_myself(2)` +- Recursive Call 2 (execution incomplete, paused on recursive call): + - `talk_to_myself(1)` +- Recursive Call 3 (execution incomplete, paused on recursive call): + - `talk_to_myself(0.5)` +- => Base case is hit because n <= 0.5, no more recursion! +- Recursive Call 3 completes +- Recursive Call 2 completes +- Recursive Call 1 completes +- Initial Call completes + +You can walk through and visualize this process [here](http://pythontutor.com/visualize.html#code=def%20talk_to_myself%28n%29%0A%20%20return%20if%20n%20%3C%3D%200.5%0A%0A%20%20talk_to_myself%28n%20/%202%29%0Aend%0A%0Atalk_to_myself%284%29&cumulative=false&heapPrimitives=nevernest&mode=edit&origin=opt-frontend.js&py=ruby&rawInputLstJSON=%5B%5D&textReferences=false). The frames and their data are visualized on the right side of the screen and the arrows on the left inside the IDE show you which line is being executed. Notice that the arrow pauses on the recursive call if the base case is not hit. When a recursive call finally completes execution and returns up the stack, the previous call will then continue to run from that line onward (the line where the recursion was triggered). + +## Dealing With Return Values + +Let’s go back to our code example and modify it to return the string ‘done’: + +``` +def talk_to_myself(n) + return 'done' if n <= 0.5 + + # this is where our method pauses + # it's also where our return values return to + talk_to_myself(n / 2) +end +``` + +That one small change will cause the method to return the string 'done' from every recursive call and the initial call. But how? + +Let's illustrate how using stack frames again: + +- Initial Call (execution incomplete, paused on recursive call): + - `talk_to_myself(1)` # pauses on the line of the recursive call +- Recursive Call 1 (execution incomplete, paused on recursive call): + - `talk_to_myself(0.5)` # pauses on the line of the recursive call +- => Base case is hit because `n <= 0.5`, no more recursion! 'done' is returned to the previous frame +- Recursive Call 1 receives 'done', and then returns 'done' up the stack +- Initial Call receives 'done', and then returns 'done' + +But what if we added a line of code after the recursive call? What would happen then? + +``` +def talk_to_myself(n) + return 'done' if n <= 0.5 + + # this is where our method pauses + # it's also where our return values return to + talk_to_myself(n / 2) + 'The sheep goes baaaaaahhhh' +end +``` + +- Initial Call (execution incomplete, paused on recursive call): + - `talk_to_myself(1)` # pauses on the line of the recursive call +- Recursive Call 1 (execution incomplete, paused on recursive call): + - `talk_to_myself(0.5)` # pauses on the line of the recursive call +- => Base case is hit because n <= 0.5, no more recursion! 'done' is returned to the previous frame +- Recursive Call 1 receives 'done' and returns 'The sheep goes baaaaaahhhh' up the stack +- Initial Call receives 'The sheep goes baaaaaahhhh', and then returns 'The sheep goes baaaaaahhhh' + +You can see a visualization of the code [here](http://pythontutor.com/visualize.html#code=def%20talk_to_myself%28n%29%0A%20%20%20%20return%20'done'%20if%20n%20%3C%3D%200.5%0A%0A%20%20%20%20%23%20this%20is%20where%20our%20method%20pauses%0A%20%20%20%20%23%20it's%20also%20where%20our%20return%20values%20return%20to%0A%20%20%20%20talk_to_myself%28n%20/%202%29%0A%20%20%20%20'The%20sheep%20goes%20baaaaaahhhh'%0Aend%0A%0Atalk_to_myself%281%29&cumulative=false&curInstr=10&heapPrimitives=nevernest&mode=display&origin=opt-frontend.js&py=ruby&rawInputLstJSON=%5B%5D&textReferences=false). + +## Conclusion + +Don't worry if this hasn't all sunk in yet. We'll get you started slowly. If you find yourself having trouble with recursion, ask yourself these questions: + +- What is/are the base case/s? Many people add those first. +- If you're getting a stack overflow: Why isn't my base case being triggered? +- What should the recursive call return? And how should I use that value? + - Remember the return value goes up the stack to the line where the recursive call was made. +- What should the method return once it has completed execution? + +You can also try drawing out the frames to trace what's happening or use this [tool](http://pythontutor.com/visualize.html#mode=edit). Start small when mapping out what's happening, e.g. in the code examples above we used the values 2 or 4, but never 20! diff --git a/02-week-2--recursion/01-day-1--recursive-counting/.gitignore b/02-week-2--recursion/01-day-1--recursive-counting/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/02-week-2--recursion/01-day-1--recursive-counting/README.md b/02-week-2--recursion/01-day-1--recursive-counting/README.md new file mode 100644 index 00000000..fd08d084 --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/README.md @@ -0,0 +1,57 @@ +# Day 1: Recursive Counting + +Let's ease into recursion. For this challenge, we'd like you to convert the following while loop that counts to a recursive method that counts. We've included while loops for Ruby and JS below. Your method is successful if it prints numbers 0-9. Remember: it must call itself! + +```ruby +count = 0 + +while count < 10 + puts count + count += 1 +end +``` + +```js +let count = 0; + +while (count < 10) { + console.log(count); + ++count; +} +``` + +If you get a stack overflow, just breathe. We all get them - the trick is to get used to them and carry on solving the problem. + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +You may wish to optionally write pseudocode and an explanation of your solution for this problem. + +Feel free to run our tests whenever you like. + +## How to run your code + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/02-week-2--recursion/01-day-1--recursive-counting/javascript/package.json b/02-week-2--recursion/01-day-1--recursive-counting/javascript/package.json new file mode 100644 index 00000000..95399263 --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "recursive_count", + "version": "1.0.0", + "description": "recursively count", + "main": "recursive_count.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/02-week-2--recursion/01-day-1--recursive-counting/javascript/recursive_count.js b/02-week-2--recursion/01-day-1--recursive-counting/javascript/recursive_count.js new file mode 100644 index 00000000..d995b9c7 --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/javascript/recursive_count.js @@ -0,0 +1,13 @@ +function recursiveCount(num = 0) { + // type your code here +} + +if (require.main === module) { + recursiveCount(); +} + +module.exports = recursiveCount; + +// OPTIONAL +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/02-week-2--recursion/01-day-1--recursive-counting/javascript/tests/recursive_count.test.js b/02-week-2--recursion/01-day-1--recursive-counting/javascript/tests/recursive_count.test.js new file mode 100644 index 00000000..35cac8fa --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/javascript/tests/recursive_count.test.js @@ -0,0 +1,14 @@ +const recursiveCount = require('../recursive_count'); + +test('logs numbers 0 to 9', () => { + console.log = jest.fn(); + recursiveCount(); + + for (let i = 0; i < 10; ++i) { + expect(console.log).toHaveBeenCalledWith(i); + } +}); + +test('returns undefined', () => { + expect(recursiveCount()).toBe(undefined); +}); \ No newline at end of file diff --git a/02-week-2--recursion/01-day-1--recursive-counting/ruby/.rspec b/02-week-2--recursion/01-day-1--recursive-counting/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/02-week-2--recursion/01-day-1--recursive-counting/ruby/Gemfile b/02-week-2--recursion/01-day-1--recursive-counting/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/02-week-2--recursion/01-day-1--recursive-counting/ruby/recursive_count.rb b/02-week-2--recursion/01-day-1--recursive-counting/ruby/recursive_count.rb new file mode 100644 index 00000000..039678f7 --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/ruby/recursive_count.rb @@ -0,0 +1,11 @@ +def recursive_count(num = 0) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + recursive_count +end + +# OPTIONAL +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/02-week-2--recursion/01-day-1--recursive-counting/ruby/spec/recursive_count_spec.rb b/02-week-2--recursion/01-day-1--recursive-counting/ruby/spec/recursive_count_spec.rb new file mode 100644 index 00000000..2d7b875a --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/ruby/spec/recursive_count_spec.rb @@ -0,0 +1,11 @@ +require './recursive_count' + +RSpec.describe '#recursive_count' do + it 'outputs numbers 0 to 9' do + expect { recursive_count }.to output("0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n").to_stdout + end + + it 'returns nil' do + expect(recursive_count).to be_nil + end +end \ No newline at end of file diff --git a/02-week-2--recursion/01-day-1--recursive-counting/ruby/spec/spec_helper.rb b/02-week-2--recursion/01-day-1--recursive-counting/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/02-week-2--recursion/01-day-1--recursive-counting/solutions/recursive_count.js b/02-week-2--recursion/01-day-1--recursive-counting/solutions/recursive_count.js new file mode 100644 index 00000000..d08d827b --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/solutions/recursive_count.js @@ -0,0 +1,27 @@ +function recursiveCount(num = 0) { + if (num >= 10) { + return; + } + + console.log(num); + recursiveCount(num + 1); + + // THIS IS ALSO A VALID OPTION INSTEAD OF THE ABOVE + // recursiveCount(++num); + + // THIS WILL CAUSE A STACK OVERFLOW. WHY? + // recursiveCount(num++); + + // IF YOU LOG NUM BELOW, THE NUMBERS PRINT BACKWARDS FROM 9 TO 0. WHY? + // console.log(num); +} + +if (require.main === module) { + recursiveCount(); +} + +module.exports = recursiveCount; + +// OPTIONAL +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/02-week-2--recursion/01-day-1--recursive-counting/solutions/recursive_count.rb b/02-week-2--recursion/01-day-1--recursive-counting/solutions/recursive_count.rb new file mode 100644 index 00000000..8c6c4b49 --- /dev/null +++ b/02-week-2--recursion/01-day-1--recursive-counting/solutions/recursive_count.rb @@ -0,0 +1,17 @@ +def recursive_count(num = 0) + # BASE CASE + return if num >= 10 + + puts num + recursive_count(num + 1) + # IF YOU PUTS NUM BELOW, THE NUMBERS PRINT BACKWARDS FROM 9 TO 0. WHY? + # puts num +end + +if __FILE__ == $PROGRAM_NAME + recursive_count +end + +# OPTIONAL +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/02-week-2--recursion/02-day-2--recursive-search/.gitignore b/02-week-2--recursion/02-day-2--recursive-search/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/02-week-2--recursion/02-day-2--recursive-search/README.md b/02-week-2--recursion/02-day-2--recursive-search/README.md new file mode 100644 index 00000000..f4b474b5 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/README.md @@ -0,0 +1,77 @@ +# Day 2: Recursive Search + +Given an Array of values, use recursion to find the target value. Return `true` if found, otherwise `false`. + +``` +Input: [1, 2, 3], 2 +Output: true + +Input: [3, 2, 1], 4 +Output: false +``` + +Sometimes it can help to solve the problem iteratively first, and then convert that to the recursive version. Here are two possible iterative solutions: one in Ruby and one in JS. + +```ruby +def iterative_search(arr, target) + arr.each do |value| + return true if value == target + end + + false +end +``` + +```js +function iterativeSearch(arr, target) { + for (const value of arr) { + if (value === target) { + return true; + } + } + + return false; +} +``` + +There are many ways to solve this problem. We suggest starting with the bases cases. What are they? + +Feeling stuck? Have a hint! Do you need to pass the whole array with every recursive call or just part of it? + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/02-week-2--recursion/02-day-2--recursive-search/javascript/package.json b/02-week-2--recursion/02-day-2--recursive-search/javascript/package.json new file mode 100644 index 00000000..a6709944 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "recursive_search", + "version": "1.0.0", + "description": "recursive search", + "main": "recursive_search.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/02-week-2--recursion/02-day-2--recursive-search/javascript/recursive_search.js b/02-week-2--recursion/02-day-2--recursive-search/javascript/recursive_search.js new file mode 100644 index 00000000..7ff60062 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/javascript/recursive_search.js @@ -0,0 +1,19 @@ +function recursiveSearch(arr, target) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: true"); + console.log("=>", recursiveSearch([1, 2, 3], 2)); + + console.log(""); + + console.log("Expecting: false"); + console.log("=>", recursiveSearch([3, 2, 1], 4)); +} + +module.exports = recursiveSearch; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/02-week-2--recursion/02-day-2--recursive-search/javascript/tests/recursive_search.test.js b/02-week-2--recursion/02-day-2--recursive-search/javascript/tests/recursive_search.test.js new file mode 100644 index 00000000..88ad0c60 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/javascript/tests/recursive_search.test.js @@ -0,0 +1,17 @@ +const recursiveSearch = require('../recursive_search'); + +test('returns false when given an empty array', () => { + expect(recursiveSearch([], 7)).toBe(false); +}); + +test('returns true when the target is in the array', () => { + expect(recursiveSearch([7], 7)).toBe(true); + expect(recursiveSearch([1, 2, 3], 2)).toBe(true); + expect(recursiveSearch([1, 2, 3], 3)).toBe(true); +}); + +test('returns false when the target is not in the array', () => { + expect(recursiveSearch([3], 7)).toBe(false); + expect(recursiveSearch([1, 2, 3], 5)).toBe(false); +}); + diff --git a/02-week-2--recursion/02-day-2--recursive-search/ruby/.rspec b/02-week-2--recursion/02-day-2--recursive-search/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/02-week-2--recursion/02-day-2--recursive-search/ruby/Gemfile b/02-week-2--recursion/02-day-2--recursive-search/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/02-week-2--recursion/02-day-2--recursive-search/ruby/recursive_search.rb b/02-week-2--recursion/02-day-2--recursive-search/ruby/recursive_search.rb new file mode 100644 index 00000000..8df0d269 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/ruby/recursive_search.rb @@ -0,0 +1,18 @@ +def recursive_search(arr, target) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: true" + puts "=>", recursive_search([1, 2, 3], 2) + + puts + + puts "Expecting: false" + puts "=>", recursive_search([3, 2, 1], 4) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/02-week-2--recursion/02-day-2--recursive-search/ruby/spec/recursive_search_spec.rb b/02-week-2--recursion/02-day-2--recursive-search/ruby/spec/recursive_search_spec.rb new file mode 100644 index 00000000..7f6b5d54 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/ruby/spec/recursive_search_spec.rb @@ -0,0 +1,18 @@ +require './recursive_search' + +RSpec.describe '#recursive_search' do + it 'returns false when given an empty array' do + expect(recursive_search([], 7)).to be false + end + + it 'returns true when the target is in the array' do + expect(recursive_search([7], 7)).to be true + expect(recursive_search([1, 2, 3], 2)).to be true + expect(recursive_search([1, 2, 3], 3)).to be true + end + + it 'returns false when the target is not in the array' do + expect(recursive_search([3], 7)).to be false + expect(recursive_search([1, 2, 3], 5)).to be false + end +end \ No newline at end of file diff --git a/02-week-2--recursion/02-day-2--recursive-search/ruby/spec/spec_helper.rb b/02-week-2--recursion/02-day-2--recursive-search/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/02-week-2--recursion/02-day-2--recursive-search/solutions/recursive_search.js b/02-week-2--recursion/02-day-2--recursive-search/solutions/recursive_search.js new file mode 100644 index 00000000..d8f56af4 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/solutions/recursive_search.js @@ -0,0 +1,52 @@ +function recursiveSearch(arr, target) { + if (arr.length === 0) { + return false; + } + + if (arr[0] === target) { + return true; + } + + return recursiveSearch(arr.slice(1), target); +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: true"); + console.log(recursiveSearch([1, 2, 3], 2)); + + console.log(""); + + console.log("Expecting: false"); + console.log(recursiveSearch([3, 2, 1], 4)); + + console.log(""); + + console.log("Expecting: false"); + console.log(recursiveSearch([], 7)); + + console.log(""); + + console.log("Expecting: true"); + console.log(recursiveSearch([7], 7)); +} + +module.exports = recursiveSearch; + +// Please add your pseudocode to this file +/***************************************************************************************** +* return false if array is empty +* return true if first element of array == target + +* return recursive_search(input array - first element, target) +******************************************************************************************/ + +// And a written explanation of your solution +/***************************************************************************************** +* There are two bases cases for this problem: if we find the target, we should return true +* and stop recursing, and if we go through all of the elements, we should stop recursing +* and return false. This means we need to operate on a subset of the array every time we +* recurse. To do this, we can pass the array minus the first element to the recursive call, +* and then check if the 0th element in the array is the target. The array will shrink by +* one element on every invocation. +******************************************************************************************/ diff --git a/02-week-2--recursion/02-day-2--recursive-search/solutions/recursive_search.rb b/02-week-2--recursion/02-day-2--recursive-search/solutions/recursive_search.rb new file mode 100644 index 00000000..07bbe687 --- /dev/null +++ b/02-week-2--recursion/02-day-2--recursive-search/solutions/recursive_search.rb @@ -0,0 +1,43 @@ +def recursive_search(arr, target) + return false if arr.empty? + return true if arr.first == target + + recursive_search(arr[1..-1], target) +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: true" + puts recursive_search([1, 2, 3], 2) + + puts + + puts "Expecting: false" + puts recursive_search([3, 2, 1], 4) + + # Don't forget to add your own! + puts + puts "Expecting: false" + puts recursive_search([], 7) + + puts + puts "Expecting: true" + puts recursive_search([7], 7) +end + +# Please add your pseudocode to this file +############################################################################ +# return false if array is empty +# return true if first element of array == target + +# return recursive_search(input array - first element, target) +############################################################################ + +# And a written explanation of your solution +############################################################################ +# There are two bases cases for this problem: if we find the target, we should return true +# and stop recursing, and if we go through all of the elements, we should stop recursing +# and return false. This means we need to operate on a subset of the array every time we +# recurse. To do this, we can pass the array minus the first element to the recursive call, +# and then check if the 0th element in the array is the target. The array will shrink by +# one element on every invocation. +############################################################################ diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/.gitignore b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/README.md b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/README.md new file mode 100644 index 00000000..7c1ac57a --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/README.md @@ -0,0 +1,90 @@ +# Day 3: Recursive Fibonacci Series + +Find the nth element in the Fibonacci series. The Fibonacci sequence starts with a 0 followed by a 1. After that, every value is the sum of the two values preceding it. Here are the first seven values as an example: 0, 1, 1, 2, 3, 5, 8. + +``` +Input: 0 +Output: 0 + +Input: 2 +Output: 1 + +Input: 10 +Output: 55 +``` + +If you solved this problem before iteratively, you may wish to convert that solution to a recursive version. Here are two iterative solutions - one in Ruby and one in JS: + +```ruby +def fibonacci(n) + return n if n < 2 + + values = [0, 1] + + (n - 1).times do + values << values[-1] + values[-2] + end + + values.last +end +``` + +```javascript +function fibonacci(n) { + if (n < 2) { + return n; + } + + const values = [0, 1]; + + for (let i = 0; i < n - 1; ++i) { + values.push(values[values.length - 1] + values[values.length - 2]); + } + + return values[values.length - 1]; +} +``` + +Hint: Code the base cases first. + +Hint: You may wish to look up how the fibonacci sequence is expressed as a formula. + +Hint: Start small. What needs to happen if n is 1 or n is 2? + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/javascript/fibonacci_recursive.js b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/javascript/fibonacci_recursive.js new file mode 100644 index 00000000..7d118da6 --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/javascript/fibonacci_recursive.js @@ -0,0 +1,24 @@ +function fibonacci(n) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 0"); + console.log("=>", fibonacci(0)); + + console.log(""); + + console.log("Expecting: 1"); + console.log("=>", fibonacci(2)); + + console.log(""); + + console.log("Expecting: 55"); + console.log("=>", fibonacci(10)); +} + +module.exports = fibonacci; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/javascript/package.json b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/javascript/package.json new file mode 100644 index 00000000..55ca5050 --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "fibonacci_recursive", + "version": "1.0.0", + "description": "recursive fibo", + "main": "fibonacci_recursive.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} \ No newline at end of file diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/javascript/tests/fibonacci_recursive.test.js b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/javascript/tests/fibonacci_recursive.test.js new file mode 100644 index 00000000..7d9453eb --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/javascript/tests/fibonacci_recursive.test.js @@ -0,0 +1,13 @@ +const fibonacci = require('../fibonacci_recursive'); + +const fibo = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811]; + +for (let i = 0; i < 10; ++i) { + test(`outputs the correct number in the sequence at index ${i}`, () => { + expect(fibonacci(i)).toBe(fibo[i]); + }); +} + +test('outputs the correct number in the sequence at index 28', () => { + expect(fibonacci(28)).toBe(fibo[28]); +}); diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/.rspec b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/Gemfile b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/fibonacci_recursive.rb b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/fibonacci_recursive.rb new file mode 100644 index 00000000..9e894774 --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/fibonacci_recursive.rb @@ -0,0 +1,23 @@ +def fibonacci(n) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 0" + puts "=>", fibonacci(0) + + puts + + puts "Expecting: 1" + puts "=>", fibonacci(2) + + puts + + puts "Expecting: 55" + puts "=>", fibonacci(10) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/spec/fibonacci_recursive_spec.rb b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/spec/fibonacci_recursive_spec.rb new file mode 100644 index 00000000..388eb491 --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/spec/fibonacci_recursive_spec.rb @@ -0,0 +1,15 @@ +require './fibonacci_recursive' + +RSpec.describe '#fibonacci' do + fibo = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811] + + 10.times do |n| + it "outputs the correct number in the sequence at index #{n}" do + expect(fibonacci(n)).to eq(fibo[n]) + end + end + + it "outputs the correct number at index 28" do + expect(fibonacci(28)).to eq(fibo[28]) + end +end \ No newline at end of file diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/spec/spec_helper.rb b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/solutions/fibonacci_recursive.js b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/solutions/fibonacci_recursive.js new file mode 100644 index 00000000..5501440d --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/solutions/fibonacci_recursive.js @@ -0,0 +1,53 @@ +function fibonacci(n) { + if (n < 2) { + return n; + } + + return fibonacci(n - 1) + fibonacci(n - 2); +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 0"); + console.log(fibonacci(0)); + + console.log(""); + + console.log("Expecting: 1"); + console.log(fibonacci(2)); + + console.log(""); + + console.log("Expecting: 55"); + console.log(fibonacci(10)); + + console.log(""); + + console.log("Expecting: 1"); + console.log(fibonacci(1)); + + console.log(""); + + console.log("Expecting: 6765"); + console.log(fibonacci(20)); +} + +module.exports = fibonacci; + +// Please add your pseudocode to this file +/************************************************************************************** +* if n is less than 2 return n +* +* return last value in sequence + second to last value in sequence +***************************************************************************************/ + +// And a written explanation of your solution +/************************************************************************************** +* We can use the same base case as the iterative version: if n is less than 2, just +* return n, since 0 and 1 are the first values in the series. After that we need to +* calculate the next value by adding up the two previous values. If we recurse until +* n equals 0 or 1, we'll hit the base case and start returning values, which can then +* be added together. For example, if we start with n = 2, our recursive call will be +* fibonacci(1) + fibonacci(0). Both sides of the equation will hit the base case. The +* left side will return 1 and the right side will return 0, resulting in a total of 1. +***************************************************************************************/ diff --git a/02-week-2--recursion/03-day-3--recursive-fibonacci-series/solutions/fibonacci_recursive.rb b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/solutions/fibonacci_recursive.rb new file mode 100644 index 00000000..56e9d1f3 --- /dev/null +++ b/02-week-2--recursion/03-day-3--recursive-fibonacci-series/solutions/fibonacci_recursive.rb @@ -0,0 +1,49 @@ +def fibonacci(n) + return n if n < 2 + + fibonacci(n - 1) + fibonacci(n - 2) +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 0" + puts fibonacci(0) + + puts + + puts "Expecting: 1" + puts fibonacci(2) + + puts + + puts "Expecting: 55" + puts fibonacci(10) + + # Don't forget to add your own! + puts + + puts "Expecting: 6765" + puts fibonacci(20) + + puts + + puts "Expecting: 1" + puts fibonacci(1) +end + +# Please add your pseudocode to this file +################################################################################ +# if n is less than 2 return n +# +# return last value in sequence + second to last value in sequence +################################################################################ + +# And a written explanation of your solution +################################################################################ +# We can use the same base case as the iterative version: if n is less than 2, just +# return n, since 0 and 1 are the first values in the series. After that we need to +# calculate the next value by adding up the two previous values. If we recurse until +# n equals 0 or 1, we'll hit the base case and start returning values, which can then +# be added together. For example, if we start with n = 2, our recursive call will be +# fibonacci(1) + fibonacci(0). Both sides of the equation will hit the base case. The +# left side will return 1 and the right side will return 0, resulting in a total of 1. +################################################################################ diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/.gitignore b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/README.md b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/README.md new file mode 100644 index 00000000..3f87daaf --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/README.md @@ -0,0 +1,72 @@ +# Day 4: Recursive Find Shortest String + +Given an Array of strings, return the shortest string. If there is more than one string of that length, return the string that comes first in the list. The Array will have a minimum length of 1. + +Once you're done solving the problem, calculate the average run time and compare it to the average run time for the iterative version. + +``` +Input: ['aaa', 'a', 'bb', 'ccc'] +Output: 'a' + +Input: ['cat', 'hi', 'dog', 'an'] +Output: 'hi' + +Input: ['flower', 'juniper', 'lily', 'dandelion'] +Output: 'lily' +``` + +You may wish to convert your iterative solution to a recursive one. We've included our old solutions in Ruby and JavaScript below: + +```ruby +def find_shortest_string(arr) + arr.reduce do |shortest, string| + string.length < shortest.length ? string : shortest + end +end +``` + +```javascript +function findShortestString(arr) { + return arr.reduce((shortest, string) => + string.length < shortest.length ? string : shortest + ); +} +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/benchmark/benchmark.js b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/benchmark/benchmark.js new file mode 100644 index 00000000..eb5d2152 --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/benchmark/benchmark.js @@ -0,0 +1,17 @@ +// Paste your iterative and recursive solutions in this file +// And then calculate their average run times to compare them. + + + + + + +function benchmark(callback) { + const startTime = Date.now(); + + for (let i = 0; i < 1000; ++i) { + callback(); + } + + return (Date.now() - startTime) / 1000; +} diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/benchmark/benchmark.rb b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/benchmark/benchmark.rb new file mode 100644 index 00000000..0e29561d --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/benchmark/benchmark.rb @@ -0,0 +1,14 @@ +# Paste your iterative and recursive solutions in this file +# And then calculate their average run times to compare them. + + + +def benchmark + start_time = Time.now + + 1000.times do + yield + end + + (Time.now - start_time) / 1000 +end diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/javascript/find_shortest_string_recursive.js b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/javascript/find_shortest_string_recursive.js new file mode 100644 index 00000000..92c47e89 --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/javascript/find_shortest_string_recursive.js @@ -0,0 +1,24 @@ +function findShortestStringRecursive(arr) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 'a'"); + console.log("=>", findShortestStringRecursive(['aaa', 'a', 'bb', 'ccc'])); + + console.log(""); + + console.log("Expecting: 'hi'"); + console.log("=>", findShortestStringRecursive(['cat', 'hi', 'dog', 'an'])); + + console.log(""); + + console.log("Expecting: 'lily'"); + console.log("=>", findShortestStringRecursive(['flower', 'juniper', 'lily', 'dandelion'])); +} + +module.exports = findShortestStringRecursive; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/javascript/package.json b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/javascript/package.json new file mode 100644 index 00000000..4680358c --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "find_shortest_string_recursive", + "version": "1.0.0", + "description": "find_shortest_string_recursive", + "main": "find_shortest_string_recursive.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/javascript/tests/find_shortest_string_recursive.test.js b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/javascript/tests/find_shortest_string_recursive.test.js new file mode 100644 index 00000000..7e60834e --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/javascript/tests/find_shortest_string_recursive.test.js @@ -0,0 +1,17 @@ +const findShortestStringRecursive = require('../find_shortest_string_recursive'); + +test('can handle an array containing one string', () => { + expect(findShortestStringRecursive(['cat'])).toBe('cat'); +}); + +test('returns the shortest string when there is only one', () => { + expect(findShortestStringRecursive(['dogbaby', 'cat', 'jammy', 'hamtaro'])).toBe('cat'); +}); + +test('returns the first occurrence of the shortest string when there are several', () => { + expect(findShortestStringRecursive(['stuff', 'a', 'things', 'b', 'two'])).toBe('a'); +}); + +test('returns the empty string', () => { + expect(findShortestStringRecursive(['things', 'crabapple', '', 'stuff'])).toBe(''); +}); diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/.rspec b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/Gemfile b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/find_shortest_string_recursive.rb b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/find_shortest_string_recursive.rb new file mode 100644 index 00000000..a0878be4 --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/find_shortest_string_recursive.rb @@ -0,0 +1,23 @@ +def find_shortest_string_recursive(arr) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 'a'" + puts "=>", find_shortest_string_recursive(['aaa', 'a', 'bb', 'ccc']) + + puts + + puts "Expecting: 'hi'" + puts "=>", find_shortest_string_recursive(['cat', 'hi', 'dog', 'an']) + + puts + + puts "Expecting: 'lily'" + puts "=>", find_shortest_string_recursive(['flower', 'juniper', 'lily', 'dandelion']) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/spec/find_shortest_string_spec.rb b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/spec/find_shortest_string_spec.rb new file mode 100644 index 00000000..babc354b --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/spec/find_shortest_string_spec.rb @@ -0,0 +1,19 @@ +require './find_shortest_string_recursive' + +RSpec.describe '#find_shortest_string' do + it 'can handle an array containing one string' do + expect(find_shortest_string_recursive(['cat'])).to eq('cat') + end + + it 'returns the shortest string when there is only one' do + expect(find_shortest_string_recursive(['dogbaby', 'cat', 'jammy', 'hamtaro'])).to eq('cat') + end + + it 'returns the first occurrence of the shortest string when there are several' do + expect(find_shortest_string_recursive(['stuff', 'a', 'things', 'b', 'two'])).to eq('a') + end + + it 'returns the empty string' do + expect(find_shortest_string_recursive(['things', 'crabapple', '', 'stuff'])).to eq('') + end +end diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/spec/spec_helper.rb b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/benchmark/benchmark.js b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/benchmark/benchmark.js new file mode 100644 index 00000000..84c31da0 --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/benchmark/benchmark.js @@ -0,0 +1,28 @@ +function findShortestString(arr) { + return arr.reduce((shortest, string) => + string.length < shortest.length ? string : shortest + ); +} + +function findShortestStringRecursive(arr) { + if (arr.length === 1) { + return arr[0]; + } + + const result = findShortestStringRecursive(arr.slice(1)); + + return arr[0].length <= result.length ? arr[0] : result; +} + +function benchmark(callback) { + const startTime = Date.now(); + + for (let i = 0; i < 1000; ++i) { + callback(); + } + + return (Date.now() - startTime) / 1000; +} + +console.log('Iterative:', benchmark(() => findShortestString(['cat', 'dogs', '', 'bats', 'flags']))); +console.log('Recursive:', benchmark(() => findShortestStringRecursive(['cat', 'dogs', '', 'bats', 'flags']))); \ No newline at end of file diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/benchmark/benchmark.rb b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/benchmark/benchmark.rb new file mode 100644 index 00000000..6a4f0a8c --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/benchmark/benchmark.rb @@ -0,0 +1,26 @@ +def find_shortest_string(arr) + arr.reduce do |shortest, string| + string.length < shortest.length ? string : shortest + end +end + +def find_shortest_string_recursive(arr) + return arr.first if arr.length == 1 + + result = find_shortest_string_recursive(arr[1..-1]) + + arr.first.length <= result.length ? arr.first : result +end + +def benchmark + start_time = Time.now + + 1000.times do + yield + end + + (Time.now - start_time) / 1000 +end + +puts "Iterative: #{benchmark { find_shortest_string(['cat', 'dogs', '', 'bats', 'flags']) }}" +puts "Recursive: #{benchmark { find_shortest_string_recursive(['cat', 'dogs', '', 'bats', 'flags']) }}" diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/find_shortest_string_recursive.js b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/find_shortest_string_recursive.js new file mode 100644 index 00000000..cdd31cd8 --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/find_shortest_string_recursive.js @@ -0,0 +1,58 @@ +function findShortestStringRecursive(arr) { + if (arr.length === 1) { + return arr[0]; + } + + const result = findShortestStringRecursive(arr.slice(1)); + + return arr[0].length <= result.length ? arr[0] : result; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 'a'"); + console.log(findShortestStringRecursive(['aaa', 'a', 'bb', 'ccc'])); + + console.log(""); + + console.log("Expecting: 'hi'"); + console.log(findShortestStringRecursive(['cat', 'hi', 'dog', 'an'])); + + console.log(""); + + console.log("Expecting: 'lily'"); + console.log(findShortestStringRecursive(['flower', 'juniper', 'lily', 'dandelion'])); + + console.log(""); + + console.log("Expecting: 'ardvark'"); + console.log(findShortestStringRecursive(['ardvark'])); +} + +module.exports = findShortestStringRecursive; + +// Please add your pseudocode to this file +/************************************************************************************************* +* if array length == 1: +* return first element +* +* recursively traverse the array and store the resulting element in a variable called result +* +* if the first element's length is <= the result's length: +* return the first element +* else: +* return the result +***************************************************************************************************/ + +// And a written explanation of your solution +/************************************************************************************************* +* We can start coding this solution with the base case: if there is only one element in the array +* return it. After that, we need to compare every element in the array to find the shortest. We +* do this by recursively calling the method with a smaller and smaller array. Since we're removing +* the first element from the array each time we recurse, this means we can compare the return value +* of our recursive call to the first element in the array in that frame. The shortest one gets +* returned up the stack. For example, if we had an array of two elements ['cat', 'dogs'], the +* initial call would occur, and then a recursive call would occur with the argument ['dogs']. 'dogs' +* would then be returned up the stack, since the base case would be triggered, so 'dogs' would now +* be stored in result. On the last line 'cat' is compared to 'dogs', and the shorter one is returned. +***************************************************************************************************/ diff --git a/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/find_shortest_string_recursive.rb b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/find_shortest_string_recursive.rb new file mode 100644 index 00000000..b3740bf9 --- /dev/null +++ b/02-week-2--recursion/04-day-4--recursive-find-shortest-string/solutions/find_shortest_string_recursive.rb @@ -0,0 +1,55 @@ +def find_shortest_string_recursive(arr) + return arr.first if arr.length == 1 + + result = find_shortest_string_recursive(arr[1..-1]) + + arr.first.length <= result.length ? arr.first : result +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 'a'" + puts find_shortest_string_recursive(['aaa', 'a', 'bb', 'ccc']) + + puts + + puts "Expecting: 'hi'" + puts find_shortest_string_recursive(['cat', 'hi', 'dog', 'an']) + + puts + + puts "Expecting: 'lily'" + puts find_shortest_string_recursive(['flower', 'juniper', 'lily', 'dandelion']) + + puts + + puts "Expecting: 'ardvark'" + puts find_shortest_string_recursive(['ardvark']) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +############################################################################################# +# if array length == 1: +# return first element +# +# recursively traverse the array and store the resulting element in a variable called result +# +# if the first element's length is <= the result's length: +# return the first element +# else: +# return the result +############################################################################################# + +# And a written explanation of your solution +############################################################################################# +# We can start coding this solution with the base case: if there is only one element in the array +# return it. After that, we need to compare every element in the array to find the shortest. We +# do this by recursively calling the method with a smaller and smaller array. Since we're removing +# the first element from the array each time we recurse, this means we can compare the return value +# of our recursive call to the first element in the array in that frame. The shortest one gets +# returned up the stack. For example, if we had an array of two elements ['cat', 'dogs'], the +# initial call would occur, and then a recursive call would occur with the argument ['dogs']. 'dogs' +# would then be returned up the stack, since the base case would be triggered, so 'dogs' would now +# be stored in result. On the last line 'cat' is compared to 'dogs', and the shorter one is returned. +############################################################################################# diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/.gitignore b/02-week-2--recursion/05-day-5--recursive-selection-sort/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/README.md b/02-week-2--recursion/05-day-5--recursive-selection-sort/README.md new file mode 100644 index 00000000..d5b6bb88 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/README.md @@ -0,0 +1,81 @@ +# Day 5: Recursive Selection Sort + +Sort an Array of numbers using selection sort. The selection sort algorithm sorts an array by repeatedly finding the minimum element (lowest value) in the input Array, and then putting it at the correct location in the sorted Array. + +Once you're done solving the problem, calculate the average run time and compare it to the average run time for the iterative version. + +``` +Input: [3, -1, 5, 2] +Output: [-1, 2, 3, 5] +``` + +You may wish to convert your iterative solution to a recursive one. We've included our old solutions in Ruby and JavaScript below: + +```ruby +def selection_sort(arr) + sorted = [] + + until arr.length == 0 + min = arr.min + idx = arr.index(min) + sorted << min + arr.delete_at(idx) + end + + sorted +end +``` + +```javascript +function selectionSort(arr) { + const sorted = []; + + while (arr.length > 0) { + const min = Math.min(...arr); + const idx = arr.indexOf(min); + + sorted.push(min); + arr.splice(idx, 1); + } + + return sorted; +} +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/benchmark/benchmark.js b/02-week-2--recursion/05-day-5--recursive-selection-sort/benchmark/benchmark.js new file mode 100644 index 00000000..eb5d2152 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/benchmark/benchmark.js @@ -0,0 +1,17 @@ +// Paste your iterative and recursive solutions in this file +// And then calculate their average run times to compare them. + + + + + + +function benchmark(callback) { + const startTime = Date.now(); + + for (let i = 0; i < 1000; ++i) { + callback(); + } + + return (Date.now() - startTime) / 1000; +} diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/benchmark/benchmark.rb b/02-week-2--recursion/05-day-5--recursive-selection-sort/benchmark/benchmark.rb new file mode 100644 index 00000000..0e29561d --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/benchmark/benchmark.rb @@ -0,0 +1,14 @@ +# Paste your iterative and recursive solutions in this file +# And then calculate their average run times to compare them. + + + +def benchmark + start_time = Time.now + + 1000.times do + yield + end + + (Time.now - start_time) / 1000 +end diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/javascript/package.json b/02-week-2--recursion/05-day-5--recursive-selection-sort/javascript/package.json new file mode 100644 index 00000000..cbc19291 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "selection_sort_recursive", + "version": "1.0.0", + "description": "selection_sort_recursive", + "main": "selection_sort_recursive.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/javascript/selection_sort_recursive.js b/02-week-2--recursion/05-day-5--recursive-selection-sort/javascript/selection_sort_recursive.js new file mode 100644 index 00000000..bf1c20b6 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/javascript/selection_sort_recursive.js @@ -0,0 +1,16 @@ +function selectionSortRecursive(arr) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [-1, 2, 3, 5]"); + console.log("=>", selectionSortRecursive([3, -1, 5, 2])); + + console.log(""); +} + +module.exports = selectionSortRecursive; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/javascript/tests/selection_sort_recursive.test.js b/02-week-2--recursion/05-day-5--recursive-selection-sort/javascript/tests/selection_sort_recursive.test.js new file mode 100644 index 00000000..ca0414a1 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/javascript/tests/selection_sort_recursive.test.js @@ -0,0 +1,26 @@ +const selectionSortRecursive = require('../selection_sort_recursive'); + +test('can handle an empty array', () => { + expect(selectionSortRecursive([])).toEqual([]); +}); + +test('can sort one element', () => { + expect(selectionSortRecursive([5])).toEqual([5]); +}); + +test('can sort two elements', () => { + expect(selectionSortRecursive([3, 1])).toEqual([1, 3]); +}); + +test('can sort several elements', () => { + expect(selectionSortRecursive([10, 4, 3, 2, 1, 5])).toEqual([1, 2, 3, 4, 5, 10]); +}); + +test('can sort negative and positive values', () => { + expect(selectionSortRecursive([-1, -2, 4, 2])).toEqual([-2, -1, 2, 4]); +}); + +test('can sort an array containing repeating values', () => { + expect(selectionSortRecursive([1, 4, 2, 1, 2, 4, 20, -2])).toEqual([1, 4, 2, 1, 2, 4, 20, -2].sort((a, b) => a - b)); +}); + diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/.rspec b/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/Gemfile b/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/selection_sort_recursive.rb b/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/selection_sort_recursive.rb new file mode 100644 index 00000000..56200763 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/selection_sort_recursive.rb @@ -0,0 +1,16 @@ +def selection_sort_recursive(arr) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [-1, 2, 3, 5]" + print "=> " + print selection_sort_recursive([3, -1, 5, 2]) + + puts + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/spec/selection_sort_recursive_spec.rb b/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/spec/selection_sort_recursive_spec.rb new file mode 100644 index 00000000..23e599cf --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/spec/selection_sort_recursive_spec.rb @@ -0,0 +1,27 @@ +require './selection_sort_recursive' + +RSpec.describe '#selection_sort_recursive' do + it 'can handle an empty array' do + expect(selection_sort_recursive([])).to eq([]) + end + + it 'can sort one element' do + expect(selection_sort_recursive([5])).to eq([5]) + end + + it 'can sort two elements' do + expect(selection_sort_recursive([3, 1])).to eq([1, 3]) + end + + it 'can sort several elements' do + expect(selection_sort_recursive([10, 4, 3, 2, 1, 5])).to eq([1, 2, 3, 4, 5, 10]) + end + + it 'can sort negative and positive values' do + expect(selection_sort_recursive([-1, -2, 4, 2])).to eq([-2, -1, 2, 4]) + end + + it 'can sort an array containing repeating values' do + expect(selection_sort_recursive([1, 4, 2, 1, 2, 4, 20, -2])).to eq([1, 4, 2, 1, 2, 4, 20, -2].sort) + end +end \ No newline at end of file diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/spec/spec_helper.rb b/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/benchmark/benchmark.js b/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/benchmark/benchmark.js new file mode 100644 index 00000000..9e60eb08 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/benchmark/benchmark.js @@ -0,0 +1,43 @@ +// Paste your iterative and recursive solutions in this file +// And then calculate their average run times to compare them. +function selectionSort(arr) { + const sorted = []; + + while (arr.length > 0) { + const min = Math.min(...arr); + const idx = arr.indexOf(min); + + sorted.push(min); + arr.splice(idx, 1); + } + + return sorted; +} + +function selectionSortRecursive(arr) { + if (arr.length === 0) { + return []; + } + + const min = Math.min(...arr); + const idx = arr.indexOf(min); + arr.splice(idx, 1); + + const result = selectionSortRecursive(arr); + result.unshift(min); + return result; +} + + +function benchmark(callback) { + const startTime = Date.now(); + + for (let i = 0; i < 1000; ++i) { + callback(); + } + + return (Date.now() - startTime) / 1000; +} + +console.log('Iterative:', benchmark(() => selectionSort([2, 3, 4, 1, 4, 56, -2, 20]))); +console.log('Recursive:', benchmark(() => selectionSortRecursive([2, 3, 4, 1, 4, 56, -2, 20]))); diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/benchmark/benchmark.rb b/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/benchmark/benchmark.rb new file mode 100644 index 00000000..edf791ac --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/benchmark/benchmark.rb @@ -0,0 +1,38 @@ +# Paste your iterative and recursive solutions in this file +# And then calculate their average run times to compare them. +def selection_sort(arr) + sorted = [] + + until arr.length == 0 + min = arr.min + idx = arr.index(min) + sorted << min + arr.delete_at(idx) + end + + sorted +end + +def selection_sort_recursive(arr) + return [] if arr.empty? + + min = arr.min + idx = arr.index(min) + arr.delete_at(idx) + + result = selection_sort_recursive(arr) + result.unshift(min) +end + +def benchmark + start_time = Time.now + + 1000.times do + yield + end + + (Time.now - start_time) / 1000 +end + +puts "Iterative: #{benchmark { selection_sort([2, 3, 4, 1, 4, 56, -2, 20]) }}" +puts "Recursive: #{benchmark { selection_sort_recursive([2, 3, 4, 1, 4, 56, -2, 20]) }}" diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/selection_sort_recursive.js b/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/selection_sort_recursive.js new file mode 100644 index 00000000..79bddd5d --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/selection_sort_recursive.js @@ -0,0 +1,68 @@ +function selectionSortRecursive(arr) { + if (arr.length === 0) { + return []; + } + + const min = Math.min(...arr); + const idx = arr.indexOf(min); + arr.splice(idx, 1); + + const result = selectionSortRecursive(arr); + result.unshift(min); + return result; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [-1, 2, 3, 5]"); + console.log(selectionSortRecursive([3, -1, 5, 2])); + + console.log(""); + + console.log("Expecting: [5]"); + console.log(selectionSortRecursive([5])); + + console.log(""); + + console.log("Expecting: [-1, 2, 2, 3, 3, 5]"); + console.log(selectionSortRecursive([3, 2, -1, 3, 5, 2])); + + console.log(""); + + console.log("Expecting: [3, 5]"); + console.log(selectionSortRecursive([5, 3])); + + console.log(""); +} + +module.exports = selectionSortRecursive; + +// Please add your pseudocode to this file +/***************************************************************************************** +* return empty array if array is empty +* +* find smallest value in array and store it in min +* find index of smallest value and store it in idx +* remove the smallest value from the array +* +* recurse through the array and store the result in result +* place the min from each frame at the front of the array +* return result +*****************************************************************************************/ + +// And a written explanation of your solution +/***************************************************************************************** +* Once again, it's easier to start with the base case: if the array is empty, return +* an empty array. We can then add items to this returned empty array in the previous +* frames. To figure out what to add, we need to find the minimum value in the array +* and its index. Next, we remove that value from the array and pass the array to the +* recursive call. This means that as we go deeper and deeper into the stack, the +* array gets smaller, until it's empty. Since frames are completed in last in first out +* order, we need to add the minimum value from each frame to the front of the result +* array. For example, if the array is [2, 1], the minimum in the first frame is 1, and +* the recusrive call is made with the argument [2]. In this frame the minimum is 2, so +* the recursive call is now made with the argument []. This hits the base case, which +* returns []. result is now an empty array in the previous frame. On the next line, the +* minimum, which is 2, gets unshifted on. [2] is returned up the stack, and stored in +* result. Here, the minimum is 1, which gets unshifted on, resulting in [1, 2]. +*****************************************************************************************/ diff --git a/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/selection_sort_recursive.rb b/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/selection_sort_recursive.rb new file mode 100644 index 00000000..a7b327d3 --- /dev/null +++ b/02-week-2--recursion/05-day-5--recursive-selection-sort/solutions/selection_sort_recursive.rb @@ -0,0 +1,64 @@ +def selection_sort_recursive(arr) + return [] if arr.empty? + + min = arr.min + idx = arr.index(min) + arr.delete_at(idx) + + result = selection_sort_recursive(arr) + result.unshift(min) +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [-1, 2, 3, 5]" + print selection_sort_recursive([3, -1, 5, 2]) + + puts + + puts "Expecting: [5]" + print selection_sort_recursive([5]) + + puts + + puts "Expecting: [-1, 2, 2, 3, 3, 5]" + print selection_sort_recursive([3, 2, -1, 3, 5, 2]) + + puts + + puts "Expecting: [3, 5]" + print selection_sort_recursive([5, 3]) + + puts + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +################################################################################# +# return empty array if array is empty +# +# find smallest value in array and store it in min +# find index of smallest value and store it in idx +# remove the smallest value from the array +# +# recurse through the array and store the result in result +# place the min from each frame at the front of the array +# return result +################################################################################# + +# And a written explanation of your solution +################################################################################# +# Once again, it's easier to start with the base case: if the array is empty, return +# an empty array. We can then add items to this returned empty array in the previous +# frames. To figure out what to add, we need to find the minimum value in the array +# and its index. Next, we remove that value from the array and pass the array to the +# recursive call. This means that as we go deeper and deeper into the stack, the +# array gets smaller, until it's empty. Since frames are completed in last in first out +# order, we need to add the minimum value from each frame to the front of the result +# array. For example, if the array is [2, 1], the minimum in the first frame is 1, and +# the recusrive call is made with the argument [2]. In this frame the minimum is 2, so +# the recursive call is now made with the argument []. This hits the base case, which +# returns []. result is now an empty array in the previous frame. On the next line, the +# minimum, which is 2, gets unshifted on. [2] is returned up the stack, and stored in +# result. Here, the minimum is 1, which gets unshifted on, resulting in [1, 2]. +################################################################################# \ No newline at end of file diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/.gitignore b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/README.md b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/README.md new file mode 100644 index 00000000..3ad0f36b --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/README.md @@ -0,0 +1,52 @@ +# Bonus 1: Balancing Parentheses + +For parentheses to be considered balanced, there must an opening parenthesis followed by a matching closing parenthesis. Given a string containing only parentheses, return the number of additional parentheses needed for the string to be considered balanced. The input string will have a minimum length of 1. + +``` +Input: '(()())' +Output: 0 + +Input: '()))' +Output: 2 + +Input: ')' +Output: 1 +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/javascript/balancing_parentheses.js b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/javascript/balancing_parentheses.js new file mode 100644 index 00000000..b05af5c8 --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/javascript/balancing_parentheses.js @@ -0,0 +1,24 @@ +function balancingParentheses(string) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 0"); + console.log(balancingParentheses('(()())')); + + console.log(""); + + console.log("Expecting: 2"); + console.log(balancingParentheses('()))')); + + console.log(""); + + console.log("Expecting: 1"); + console.log(balancingParentheses(')')); +} + +module.exports = balancingParentheses; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/javascript/package.json b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/javascript/package.json new file mode 100644 index 00000000..bf4287e8 --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "balancing_parentheses", + "version": "1.0.0", + "description": "balancing_parentheses", + "main": "balancing_parentheses.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/javascript/tests/balancing_parentheses.test.js b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/javascript/tests/balancing_parentheses.test.js new file mode 100644 index 00000000..2903e183 --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/javascript/tests/balancing_parentheses.test.js @@ -0,0 +1,25 @@ +const balancingParentheses = require('../balancing_parentheses'); + +test('can balance parentheses when only one type of parenthesis is in the string', () => { + expect(balancingParentheses(')')).toBe(1); + expect(balancingParentheses('(')).toBe(1); + expect(balancingParentheses(')))')).toBe(3); + expect(balancingParentheses('(((')).toBe(3); +}); + +test('accounts for strings that start with a closing parenthesis or end with an opening one', () => { + expect(balancingParentheses(')(')).toBe(2); + expect(balancingParentheses(')()')).toBe(1); + expect(balancingParentheses(')((((()))((())))()(()))(')).toBe(2); +}); + +test('returns 0 when the parentheses are balanced', () => { + expect(balancingParentheses('(()())')).toBe(0); + expect(balancingParentheses('()')).toBe(0); +}); + + +test('returns the correct number when the string starts with an opening parenthesis and ends with a closing one', () => { + expect(balancingParentheses('()))')).toBe(2); +}); + \ No newline at end of file diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/.rspec b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/Gemfile b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/balancing_parentheses.rb b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/balancing_parentheses.rb new file mode 100644 index 00000000..e941fdbf --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/balancing_parentheses.rb @@ -0,0 +1,23 @@ +def balancing_parentheses(string) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 0" + puts balancing_parentheses('(()())') + + puts + + puts "Expecting: 2" + puts balancing_parentheses('()))') + + puts + + puts "Expecting: 1" + puts balancing_parentheses(')') + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/spec/balancing_parentheses_spec.rb b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/spec/balancing_parentheses_spec.rb new file mode 100644 index 00000000..8b90a590 --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/spec/balancing_parentheses_spec.rb @@ -0,0 +1,25 @@ +require './balancing_parentheses' + +RSpec.describe '#balancing_parentheses' do + it 'can balance parentheses when only one type of parenthesis is in the string' do + expect(balancing_parentheses(')')).to eq(1) + expect(balancing_parentheses('(')).to eq(1) + expect(balancing_parentheses(')))')).to eq(3) + expect(balancing_parentheses('(((')).to eq(3) + end + + it 'accounts for strings that start with a closing parenthesis or end with an opening one' do + expect(balancing_parentheses(')(')).to eq(2) + expect(balancing_parentheses(')()')).to eq(1) + expect(balancing_parentheses(')((((()))((())))()(()))(')).to eq(2) + end + + it 'returns 0 when the parentheses are balanced' do + expect(balancing_parentheses('(()())')).to eq(0) + expect(balancing_parentheses('()')).to eq(0) + end + + it 'returns the correct number when the string starts with an opening parenthesis and ends with a closing one' do + expect(balancing_parentheses('()))')).to eq(2) + end +end diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/spec/spec_helper.rb b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/solutions/balancing_parentheses.js b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/solutions/balancing_parentheses.js new file mode 100644 index 00000000..f6affcdb --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/solutions/balancing_parentheses.js @@ -0,0 +1,100 @@ +function balancingParentheses(string) { + let missing = 0; + let openings = 0; + + for (let i = 0; i < string.length; ++i) { + if (string[i] === '(') { + ++openings; + continue; + } + + if (openings > 0) { + --openings; + } else { + ++missing; + } + } + + return missing + openings; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 0"); + console.log(balancingParentheses('(()())')); + + console.log(""); + + console.log("Expecting: 2"); + console.log(balancingParentheses('()))')); + + console.log(""); + + console.log("Expecting: 1"); + console.log(balancingParentheses(')')); + + console.log(""); + + console.log("Expecting: 0"); + console.log(balancingParentheses('()')); + + console.log(""); + + console.log("Expecting: 1"); + console.log(balancingParentheses('(')); + + console.log(""); + + console.log("Expecting: 2"); + console.log(balancingParentheses(')(')); + + console.log(""); + + console.log("Expecting: 1"); + console.log(balancingParentheses(')()')); + + console.log(""); + + console.log("Expecting: 2"); + console.log(balancingParentheses(')((((()))((())))()(()))(')); + + console.log(""); + + console.log("Expecting: 3"); + console.log(balancingParentheses(')))')); + + console.log(""); + + console.log("Expecting: 3"); + console.log(balancingParentheses('(((')); +} + +module.exports = balancingParentheses; + +// Please add your pseudocode to this file +/************************************************************************************** +* initialize missing to 0 (will store unmatched closing parens count) +* initialize openings to 0 (will store opening parens count) +* +* iterate over string: +* if char == '(': +* increment openings +* else: +* if openings is 0: +* increment missing +* else: +* decrement openings +* +* return missing + openings +***************************************************************************************/ + +// And a written explanation of your solution +/************************************************************************************** +* We can calculate the number of parentheses needed by counting the number of opening +* parentheses that occur in the string and decrementing that count any time a closing +* parenthesis is encountered after that. If we encounter a closing parenthesis and there +* are no opening parentheses (openings = 0), we add to missing. Once we've iterated +* over the whole string, we just need to add the missing count with the openings count, +* since the openings count will track any opening parentheses for which there were no +* matching closing ones. +***************************************************************************************/ diff --git a/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/solutions/balancing_parentheses.rb b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/solutions/balancing_parentheses.rb new file mode 100644 index 00000000..4583dcfb --- /dev/null +++ b/03-week-3--additional-practice/00-bonus-1--balancing-parenetheses/solutions/balancing_parentheses.rb @@ -0,0 +1,99 @@ +def balancing_parentheses(string) + missing = 0 + openings = 0 + + string.chars.each do |char| + if char == '(' + openings += 1 + next + end + + if openings > 0 + openings -= 1 + else + missing += 1 + end + end + + missing + openings +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 0" + puts balancing_parentheses('(()())') + + puts + + puts "Expecting: 2" + puts balancing_parentheses('()))') + + puts + + puts "Expecting: 1" + puts balancing_parentheses(')') + + # Don't forget to add your own! + + puts + + puts "Expecting: 0" + puts balancing_parentheses('()') + + puts + + puts "Expecting: 1" + puts balancing_parentheses('(') + + puts + + puts "Expecting: 2" + puts balancing_parentheses(')(') + + puts + + puts "Expecting: 1" + puts balancing_parentheses(')()') + + puts + + puts "Expecting: 2" + puts balancing_parentheses(')((((()))((())))()(()))(') + + puts + + puts "Expecting: 3" + puts balancing_parentheses(')))') + + puts + + puts "Expecting: 3" + puts balancing_parentheses('(((') +end + +# Please add your pseudocode to this file +################################################################################## +# initialize missing to 0 (will store unmatched closing parens count) +# initialize openings to 0 (will store opening parens count) +# +# iterate over string: +# if char == '(': +# increment openings +# else: +# if openings is 0: +# increment missing +# else: +# decrement openings +# +# return missing + openings +################################################################################## + +# And a written explanation of your solution +################################################################################## +# We can calculate the number of parentheses needed by counting the number of opening +# parentheses that occur in the string and decrementing that count any time a closing +# parenthesis is encountered after that. If we encounter a closing parenthesis and there +# are no opening parentheses (openings = 0), we add to missing. Once we've iterated +# over the whole string, we just need to add the missing count with the openings count, +# since the openings count will track any opening parentheses for which there were no +# matching closing ones. +################################################################################## diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/.gitignore b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/README.md b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/README.md new file mode 100644 index 00000000..a4ca2f46 --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/README.md @@ -0,0 +1,54 @@ +# Bonus 2: Roman Numeral to Integer + +Convert the provided Roman numeral (a String) to an Integer. For more information on Roman numerals, please go to [Math Is Fun](https://www.mathsisfun.com/roman-numerals.html). + +The String input will always consist of uppercase letters with a minimum length of 1. We will only be covering numbers below 4000, so you can safely ignore the "Really Big Numbers" section on the Math is Fun page we linked to. + +``` +Input: 'I' +Output: 1 + +Input: 'IX' +Output: 9 + +Input: 'CDII' +Output: 402 +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/javascript/package.json b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/javascript/package.json new file mode 100644 index 00000000..4fc75de0 --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "roman_numeral", + "version": "1.0.0", + "description": "roman_numeral", + "main": "roman_numeral.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/javascript/roman_numeral.js b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/javascript/roman_numeral.js new file mode 100644 index 00000000..f227e61e --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/javascript/roman_numeral.js @@ -0,0 +1,24 @@ +function romanNumeral(string) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 1"); + console.log(romanNumeral('I')); + + console.log(""); + + console.log("Expecting: 9"); + console.log(romanNumeral('IX')); + + console.log(""); + + console.log("Expecting: 402"); + console.log(romanNumeral('CDII')); +} + +module.exports = romanNumeral; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/javascript/tests/roman_numeral.test.js b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/javascript/tests/roman_numeral.test.js new file mode 100644 index 00000000..a3d7ac43 --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/javascript/tests/roman_numeral.test.js @@ -0,0 +1,19 @@ +const romanNumeral = require('../roman_numeral'); + +test('handles small roman numerals', () => { + expect(romanNumeral('I')).toBe(1); + expect(romanNumeral('III')).toBe(3); +}); + +test('handles instances where the smaller numeral comes before the larger one', () => { + expect(romanNumeral('IX')).toBe(9); + expect(romanNumeral('MCM')).toBe(1900); + expect(romanNumeral('MCMXCIX')).toBe(1999); + expect(romanNumeral('CDII')).toBe(402); + expect(romanNumeral('XLIV')).toBe(44); +}); + +test('handles numbers in the hundreds and low thousands', () => { + expect(romanNumeral('CCXXIII')).toBe(223); + expect(romanNumeral('MMMDCCCXLVIII')).toBe(3848); +}); diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/.rspec b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/Gemfile b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/roman_numeral.rb b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/roman_numeral.rb new file mode 100644 index 00000000..c2d32e8b --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/roman_numeral.rb @@ -0,0 +1,23 @@ +def roman_numeral(string) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 1" + puts roman_numeral('I') + + puts + + puts "Expecting: 9" + puts roman_numeral('IX') + + puts + + puts "Expecting: 402" + puts roman_numeral('CDII') + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/spec/roman_numeral_spec.rb b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/spec/roman_numeral_spec.rb new file mode 100644 index 00000000..6a56a9f1 --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/spec/roman_numeral_spec.rb @@ -0,0 +1,21 @@ +require './roman_numeral' + +RSpec.describe '#roman_numeral' do + it 'handles small roman numerals' do + expect(roman_numeral('I')).to eq(1) + expect(roman_numeral('III')).to eq(3) + end + + it 'handles instances where the smaller numeral comes before the larger one' do + expect(roman_numeral('IX')).to eq(9) + expect(roman_numeral('MCM')).to eq(1900) + expect(roman_numeral('MCMXCIX')).to eq(1999) + expect(roman_numeral('CDII')).to eq(402) + expect(roman_numeral('XLIV')).to eq(44) + end + + it 'handles numbers in the hundreds and low thousands' do + expect(roman_numeral('CCXXIII')).to eq(223) + expect(roman_numeral('MMMDCCCXLVIII')).to eq(3848) + end +end diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/spec/spec_helper.rb b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/solutions/roman_numeral.js b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/solutions/roman_numeral.js new file mode 100644 index 00000000..cfada0ec --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/solutions/roman_numeral.js @@ -0,0 +1,109 @@ +function romanNumeral(string) { + const romans = { + I: 1, + IV: 4, + V: 5, + IX: 9, + X: 10, + XL: 40, + L: 50, + XC: 90, + C: 100, + CD: 400, + D: 500, + CM: 900, + M: 1000 + }; + let total = 0; + let idx = 0; + + while (idx < string.length) { + const twoChar = string[idx] + (string[idx + 1] || ''); + + if (romans[twoChar] !== undefined) { + total += romans[twoChar]; + idx += 2; + } else { + total += romans[string[idx]]; + ++idx; + } + } + + return total; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 1"); + console.log(romanNumeral('I')); + + console.log(""); + + console.log("Expecting: 9"); + console.log(romanNumeral('IX')); + + console.log(""); + + console.log("Expecting: 402"); + console.log(romanNumeral('CDII')); + + console.log(""); + + console.log("Expecting: 3"); + console.log(romanNumeral('III')); + + console.log(""); + + console.log("Expecting: 1900"); + console.log(romanNumeral('MCM')); + + console.log(""); + + console.log("Expecting: 1999"); + console.log(romanNumeral('MCMXCIX')); + + console.log(""); + + console.log("Expecting: 44"); + console.log(romanNumeral('XLIV')); + + console.log(""); + + console.log("Expecting: 223"); + console.log(romanNumeral('CCXXIII')); + + console.log(""); + + console.log("Expecting: 3848"); + console.log(romanNumeral('MMMDCCCXLVIII')); +} + +module.exports = romanNumeral; + +// Please add your pseudocode to this file +/**************************************************************************** + * store important roman numerals in an Object called romans + * initialize total to 0 + * + * iterate over string: + * if current character + next character is a key in romans: + * add that value to total + * skip over next character in iteration + * else: + * get value where current character is a key in romans + * add value to total + * + * return total + * *************************************************************************/ + + // And a written explanation of your solution +/**************************************************************************** + * We can store the unique Roman numerals in an Object (key-value pairs), including + * those where the small Roman numeral comes before the larger Roman numeral (e.g. 'IX'). + * When we iterate over the string, we first check if the current character plus the next + * character is a key in the Object. If it is, we add the value associated with that key + * to the total. We'll then need to increment our index in the string by 2. This takes care + * of those numerals where the small number comes before the big one. If the two characters + * together aren't a key, we add the value at the single-character key to the total and + * increment the index by one. + * *************************************************************************/ \ No newline at end of file diff --git a/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/solutions/roman_numeral.rb b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/solutions/roman_numeral.rb new file mode 100644 index 00000000..3dff73e4 --- /dev/null +++ b/03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer/solutions/roman_numeral.rb @@ -0,0 +1,108 @@ +def roman_numeral(string) + romans = { + I: 1, + IV: 4, + V: 5, + IX: 9, + X: 10, + XL: 40, + L: 50, + XC: 90, + C: 100, + CD: 400, + D: 500, + CM: 900, + M: 1000 + } + total = 0 + idx = 0 + + while idx < string.length + twoChar = (string[idx] + (string[idx + 1] || '')).to_sym + + if !romans[twoChar].nil? + total += romans[twoChar] + idx += 2 + else + total += romans[string[idx].to_sym] + idx += 1 + end + end + + total +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 1" + puts roman_numeral('I') + + puts + + puts "Expecting: 9" + puts roman_numeral('IX') + + puts + + puts "Expecting: 402" + puts roman_numeral('CDII') + + # Don't forget to add your own! + puts + + puts "Expecting: 3" + puts roman_numeral('III') + + puts + + puts "Expecting: 1900" + puts roman_numeral('MCM') + + puts + + puts "Expecting: 1999" + puts roman_numeral('MCMXCIX') + + puts + + puts "Expecting: 44" + puts roman_numeral('XLIV') + + puts + + puts "Expecting: 223" + puts roman_numeral('CCXXIII') + + puts + + puts "Expecting: 3848" + puts roman_numeral('MMMDCCCXLVIII') +end + +# Please add your pseudocode to this file +############################################################################################# + # store important roman numerals in an Object called romans + # initialize total to 0 + # + # iterate over string: + # if current character + next character is a key in romans: + # add that value to total + # skip over next character in iteration + # else: + # get value where current character is a key in romans + # add value to total + # + # return total +############################################################################################# + + # And a written explanation of your solution +############################################################################################# + # We can store the unique Roman numerals in an Object (key-value pairs), including + # those where the small Roman numeral comes before the larger Roman numeral (e.g. 'IX'). + # When we iterate over the string, we first check if the current character plus the next + # character is a key in the Object. If it is, we add the value associated with that key + # to the total. We'll then need to increment our index in the string by 2. This takes care + # of those numerals where the small number comes before the big one. If the two characters + # together aren't a key, we add the value at the single-character key to the total and + # increment the index by one. +############################################################################################# + diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/.gitignore b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/README.md b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/README.md new file mode 100644 index 00000000..500694f9 --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/README.md @@ -0,0 +1,54 @@ +# Bonus 3: Rotate Array Clockwise + +Given an input Array, rotate `k` units clockwise, i.e. shift the values rightward `k` units. + +`k` is an Integer >= 0. + +``` +Input: [1, 2, 3, 4], 1 +Output: [4, 1, 2, 3] + +Input: [1, 2, 3], 2 +Output: [2, 3, 1] + +Input: [1, 2, 3], 3 +Output: [1, 2, 3] +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/javascript/package.json b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/javascript/package.json new file mode 100644 index 00000000..0c6a132b --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "rotate_array", + "version": "1.0.0", + "description": "rotate_array", + "main": "rotate_array.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/javascript/rotate_array.js b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/javascript/rotate_array.js new file mode 100644 index 00000000..2d8f8cda --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/javascript/rotate_array.js @@ -0,0 +1,24 @@ +function rotateArray(arr, k) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [4, 1, 2, 3]"); + console.log("=>", rotateArray([1, 2, 3, 4], 1)); + + console.log(""); + + console.log("Expecting: [2, 3, 1]"); + console.log("=>", rotateArray([1, 2, 3], 2)); + + console.log(""); + + console.log("Expecting: [1, 2, 3]"); + console.log("=>", rotateArray([1, 2, 3], 3)); +} + +module.exports = rotateArray; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/javascript/tests/rotate_array.test.js b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/javascript/tests/rotate_array.test.js new file mode 100644 index 00000000..d3cf608e --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/javascript/tests/rotate_array.test.js @@ -0,0 +1,22 @@ +const rotateArray = require('../rotate_array'); + +test('can handle k values from 1 up to the length of the array', () => { + expect(rotateArray([1, 2, 3, 4], 1)).toEqual([4, 1, 2, 3]); + expect(rotateArray([1, 2, 3], 2)).toEqual([2, 3, 1]); + expect(rotateArray([1, 2, 3], 3)).toEqual([1, 2, 3]); +}); + +test('can handle an empty array', () => { + expect(rotateArray([], 7)).toEqual([]); + expect(rotateArray([], 0)).toEqual([]); +}); + +test('can handle a k value of 0', () => { + expect(rotateArray([1, 2, 3], 0)).toEqual([1, 2, 3]); +}); + +test('can handle k values larger than the array length', () => { + expect(rotateArray([1, 2, 3], 5)).toEqual([2, 3, 1]); + expect(rotateArray([1, 2, 3], 6)).toEqual([1, 2, 3]); + expect(rotateArray([1, 2, 3, 4], 41)).toEqual([4, 1, 2, 3]); +}); diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/.rspec b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/Gemfile b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/rotate_array.rb b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/rotate_array.rb new file mode 100644 index 00000000..182168f1 --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/rotate_array.rb @@ -0,0 +1,25 @@ +def rotate_array(arr, k) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [4, 1, 2, 3]" + print "=> " + print rotate_array([1, 2, 3, 4], 1) + + puts + + puts "Expecting: [2, 3, 1]" + print "=> " + print rotate_array([1, 2, 3], 2) + + puts + + puts "Expecting: [1, 2, 3]" + print "=> " + print rotate_array([1, 2, 3], 3) + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/spec/rotate_array_spec.rb b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/spec/rotate_array_spec.rb new file mode 100644 index 00000000..04f9c2df --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/spec/rotate_array_spec.rb @@ -0,0 +1,24 @@ +require './rotate_array' + +RSpec.describe '#rotate_array' do + it 'can handle k values from 1 up to the length of the array' do + expect(rotate_array([1, 2, 3, 4], 1)).to eq([4, 1, 2, 3]) + expect(rotate_array([1, 2, 3], 2)).to eq([2, 3, 1]) + expect(rotate_array([1, 2, 3], 3)).to eq([1, 2, 3]) + end + + it 'can handle an empty array' do + expect(rotate_array([], 7)).to eq([]) + expect(rotate_array([], 0)).to eq([]) + end + + it 'can handle a k value of 0' do + expect(rotate_array([1, 2, 3], 0)).to eq([1, 2, 3]) + end + + it 'can handle k values larger than the array length' do + expect(rotate_array([1, 2, 3], 5)).to eq([2, 3, 1]) + expect(rotate_array([1, 2, 3], 6)).to eq([1, 2, 3]) + expect(rotate_array([1, 2, 3, 4], 41)).to eq([4, 1, 2, 3]) + end +end diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/spec/spec_helper.rb b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/solutions/rotate_array.js b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/solutions/rotate_array.js new file mode 100644 index 00000000..d6906759 --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/solutions/rotate_array.js @@ -0,0 +1,54 @@ +function rotateArray(arr, k) { + const rotations = k % arr.length; + const removed = arr.splice(arr.length - rotations, rotations); + + return removed.concat(arr); +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [4, 1, 2, 3]"); + console.log(rotateArray([1, 2, 3, 4], 1)); + + console.log(""); + + console.log("Expecting: [2, 3, 1]"); + console.log(rotateArray([1, 2, 3], 2)); + + console.log(""); + + console.log("Expecting: [1, 2, 3]"); + console.log(rotateArray([1, 2, 3], 3)); + + console.log(""); + + console.log("Expecting: [1, 2, 3]"); + console.log(rotateArray([1, 2, 3], 0)); + + console.log(""); + + console.log("Expecting: [2, 3, 1]"); + console.log(rotateArray([1, 2, 3], 5)); + + console.log(""); + + console.log("Expecting: []"); + console.log(rotateArray([], 7)); +} + +module.exports = rotateArray; + +// Please add your pseudocode to this file +/*********************************************************************************** + * initialize rotations to store the remainder of k / array length + * remove rotations number elements from end of array + * return removed elements + remaining elements + * *********************************************************************************/ + +// And a written explanation of your solution +/*********************************************************************************** + * We can solve this problem by figuring out how many elements to remove from the end + * of the array, and then adding those removed elements to the front of the array. To + * do this, we need to account for when k is the same or larger than the array's length. + * This is ascertained by getting the remainder of k / array length. + * **********************************************************************************/ \ No newline at end of file diff --git a/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/solutions/rotate_array.rb b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/solutions/rotate_array.rb new file mode 100644 index 00000000..f0509c7b --- /dev/null +++ b/03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise/solutions/rotate_array.rb @@ -0,0 +1,54 @@ +def rotate_array(arr, k) + # avoid divide by zero error in Ruby, which will otherwise occur on L5 + return arr if arr.empty? || k.zero? + + rotations = k % arr.length + removed = arr.slice!(arr.length - rotations, rotations) + removed + arr +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [4, 1, 2, 3]" + print rotate_array([1, 2, 3, 4], 1) + + puts + + puts "Expecting: [2, 3, 1]" + print rotate_array([1, 2, 3], 2) + + puts + + puts "Expecting: [1, 2, 3]" + print rotate_array([1, 2, 3], 3) + # Don't forget to add your own! + + puts + + puts "Expecting: [1, 2, 3]" + print rotate_array([1, 2, 3], 0) + + puts + + puts "Expecting: [2, 3, 1]" + print rotate_array([1, 2, 3], 5) + + puts + + puts "Expecting: []" + print rotate_array([], 7) +end + +# Please add your pseudocode to this file +########################################################################################## + # initialize rotations to store the remainder of k / array length + # remove rotations number elements from end of array + # return removed elements + remaining elements +########################################################################################## + +# And a written explanation of your solution +########################################################################################## + # We can solve this problem by figuring out how many elements to remove from the end + # of the array, and then adding those removed elements to the front of the array. To + # do this, we need to account for when k is the same or larger than the array's length. + # This is ascertained by getting the remainder of k / array length. +########################################################################################## \ No newline at end of file diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/.gitignore b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/README.md b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/README.md new file mode 100644 index 00000000..63c3a025 --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/README.md @@ -0,0 +1,49 @@ +# Bonus 4: Distinct Pair Sum + +Given an input Array and a target value `k`, return all distinct pairs of consecutive numbers that add up to `k`. A pair is distinct if no other pair contains the same numbers. The order of the pairs and order of the values in each pair does not matter, e.g. we consider [[2, 8], [7, 3]] to be the same as [[3, 7], [8, 2]]. + +``` +Input: [0, 1, 1, 2, 0, 1, 1], 2 +Output: [[1, 1], [2, 0]] + +Input: [3, 4, 2, 1, 5, 2, 8, 2], 10 +Output: [[2, 8]] +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/javascript/distinct_pair_sum.js b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/javascript/distinct_pair_sum.js new file mode 100644 index 00000000..1c60bd41 --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/javascript/distinct_pair_sum.js @@ -0,0 +1,19 @@ +function distinctPairSum(arr, k) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [[1, 1], [2, 0]]"); + console.log("=>", distinctPairSum([0, 1, 1, 2, 0, 1, 1], 2)); + + console.log(""); + + console.log("Expecting: [[2, 8]]"); + console.log("=>", distinctPairSum([3, 4, 2, 1, 5, 2, 8, 2], 10)); +} + +module.exports = distinctPairSum; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/javascript/package.json b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/javascript/package.json new file mode 100644 index 00000000..aee161e2 --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "distinct_pair_sum.js", + "version": "1.0.0", + "description": "distinct_pair_sum.js", + "main": "distinct_pair_sum.js.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/javascript/tests/distinct_pair_sum.test.js b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/javascript/tests/distinct_pair_sum.test.js new file mode 100644 index 00000000..d385eb4e --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/javascript/tests/distinct_pair_sum.test.js @@ -0,0 +1,21 @@ +const distinctPairSum = require('../distinct_pair_sum'); + +function sortArray(arr, k) { + let res = distinctPairSum(arr, k); + res = res.map(arr => arr.sort((a, b) => a - b)); + return res.sort((a, b) => a[0] - b[0]); +} + +test('returns an empty array when there are no pairs that sum up to k', () => { + expect(distinctPairSum([3, 4, 2, 1, 5, 2, 8, 2], 100)).toEqual([]); + expect(distinctPairSum([3, 4, 2, 1, 5, 2, 8, 2], 100).length).toBe(0); + expect(distinctPairSum([], 100).length).toBe(0); + expect(distinctPairSum([59], 100).length).toBe(0); +}); + +test('only returns distinct pairs', () => { + expect(sortArray([0, 1, 1, 2, 0, 1, 1], 2)).toEqual([[0, 2], [1, 1]]); + expect(sortArray([3, 4, 2, 1, 5, 2, 8, 2], 10)).toEqual([[2, 8]]); + expect(sortArray([59, 41], 100)).toEqual([[41, 59]]); + expect(sortArray([1, 0, 0, 10, -10, 5, 4, 3, -3, -3], 0)).toEqual([[-10, 10], [-3, 3], [0, 0]]); +}); diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/.rspec b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/Gemfile b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/distinct_pair_sum.rb b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/distinct_pair_sum.rb new file mode 100644 index 00000000..1a2e1612 --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/distinct_pair_sum.rb @@ -0,0 +1,20 @@ +def distinct_pair_sum(arr, k) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [[1, 1], [2, 0]]" + print "=> " + print distinct_pair_sum([0, 1, 1, 2, 0, 1, 1], 2) + + puts + + puts "Expecting: [[2, 8]]" + print "=> " + print distinct_pair_sum([3, 4, 2, 1, 5, 2, 8, 2], 10) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/spec/distinct_pair_sum_spec.rb b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/spec/distinct_pair_sum_spec.rb new file mode 100644 index 00000000..0418a1ed --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/spec/distinct_pair_sum_spec.rb @@ -0,0 +1,22 @@ +require './distinct_pair_sum' + +def sort_array(arr, k) + res = distinct_pair_sum(arr, k) + res.map!(&:sort) + res.sort! +end + +RSpec.describe '#distinct_pair_sum' do + it 'returns an empty array when there are no pairs that sum up to k' do + expect(distinct_pair_sum([3, 4, 2, 1, 5, 2, 8, 2], 100)).to eq([]) + expect(distinct_pair_sum([], 100)).to eq([]) + expect(distinct_pair_sum([59], 100)).to eq([]) + end + + it 'only returns distinct pairs' do + expect(sort_array([0, 1, 1, 2, 0, 1, 1], 2)).to eq([[0, 2], [1, 1]]) + expect(sort_array([3, 4, 2, 1, 5, 2, 8, 2], 10)).to eq([[2, 8]]) + expect(sort_array([59, 41], 100)).to eq([[41, 59]]) + expect(sort_array([1, 0, 0, 10, -10, 5, 4, 3, -3, -3], 0)).to eq([[-10, 10], [-3, 3], [0, 0]]) + end +end diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/spec/spec_helper.rb b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/solutions/distinct_pair_sum.js b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/solutions/distinct_pair_sum.js new file mode 100644 index 00000000..6d7bef90 --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/solutions/distinct_pair_sum.js @@ -0,0 +1,73 @@ +function distinctPairSum(arr, k) { + const pairs = {}; + + arr.slice(0, -1).forEach((num, idx) => { + const nextValue = arr[idx + 1]; + + if (num + nextValue === k && + pairs[num] === undefined && + pairs[nextValue] === undefined) { + pairs[num] = [num, nextValue]; + } + }); + + return Object.values(pairs); +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [[1, 1], [2, 0]]"); + console.log(distinctPairSum([0, 1, 1, 2, 0, 1, 1], 2)); + + console.log(""); + + console.log("Expecting: [[2, 8]]"); + console.log(distinctPairSum([3, 4, 2, 1, 5, 2, 8, 2], 10)); + + console.log(""); + + console.log("Expecting: []"); + console.log(distinctPairSum([3, 4, 2, 1, 5, 2, 8, 2], 100)); + + console.log(""); + + console.log("Expecting: []"); + console.log(distinctPairSum([], 100)); + + console.log(""); + + console.log("Expecting: [[59, 41]]"); + console.log(distinctPairSum([59, 41], 100)); + + console.log(""); + + console.log("Expecting: []"); + console.log(distinctPairSum([59], 100)); + + console.log(""); + + console.log("Expecting: [[0, 0], [10, -10], [3, -3]]"); + console.log(distinctPairSum([1, 0, 0, 10, -10, 5, 4, 3, -3, -3], 0)); +} + +module.exports = distinctPairSum; + +// Please add your pseudocode to this file +/*********************************************************************************** + * initialize empty object called pairs + * + * iterate over array up to second to last item: + * if current item and next item are not keys in pairs and they sum to k: + * add current item as key in pairs with value of [current item, next item] + * + * return values stored in pairs +************************************************************************************/ + +// And a written explanation of your solution +/*********************************************************************************** + * Objects only allow keys with unique values. If we iterate over the array, and on + * each iteration, check if the current value and next value add up to k, we can then + * check if either of those values is a key in pairs. If neither is a key in pairs, we + * add the current element being iterated over as the key and the pair as the value. + * When the iteration is over, we just return the values from pairs. + ************************************************************************************/ \ No newline at end of file diff --git a/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/solutions/distinct_pair_sum.rb b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/solutions/distinct_pair_sum.rb new file mode 100644 index 00000000..0efbc9bd --- /dev/null +++ b/03-week-3--additional-practice/03-bonus-4--distinct-pair-sum/solutions/distinct_pair_sum.rb @@ -0,0 +1,72 @@ +def distinct_pair_sum(arr, k) + pairs = {} + + (0...(arr.length - 1)).each do |idx| + next_value = arr[idx + 1] + + if next_value + arr[idx] == k && + !pairs.key?(next_value) && + !pairs.key?(arr[idx]) + pairs[arr[idx]] = [arr[idx], next_value] + end + end + + pairs.values +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [[1, 1], [2, 0]]" + print distinct_pair_sum([0, 1, 1, 2, 0, 1, 1], 2) + + puts + + puts "Expecting: [[2, 8]]" + print distinct_pair_sum([3, 4, 2, 1, 5, 2, 8, 2], 10) + + # Don't forget to add your own! + + puts + + puts "Expecting: []" + print distinct_pair_sum([3, 4, 2, 1, 5, 2, 8, 2], 100) + + puts + + puts "Expecting: []" + print distinct_pair_sum([], 100) + + puts + + puts "Expecting: [[59, 41]]" + print distinct_pair_sum([59, 41], 100) + + puts + + puts "Expecting: []" + print distinct_pair_sum([59], 100) + + puts + + puts "Expecting: [[0, 0], [10, -10], [3, -3]]" + print distinct_pair_sum([1, 0, 0, 10, -10, 5, 4, 3, -3, -3], 0) +end + +# Please add your pseudocode to this file +########################################################################################## + # initialize empty object called pairs + # + # iterate over array up to second to last item: + # if current item and next item are not keys in pairs and they sum to k: + # add current item as key in pairs with value of [current item, next item] + # + # return values stored in pairs +########################################################################################## + +# And a written explanation of your solution +########################################################################################## + # Objects only allow keys with unique values. If we iterate over the array, and on + # each iteration, check if the current value and next value add up to k, we can then + # check if either of those values is a key in pairs. If neither is a key in pairs, we + # add the current element being iterated over as the key and the pair as the value. + # When the iteration is over, we just return the values from pairs. +########################################################################################## diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/.gitignore b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/README.md b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/README.md new file mode 100644 index 00000000..57c22a1d --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/README.md @@ -0,0 +1,53 @@ +# Bonus 5: Consecutive Substrings + +Given a string, return all consecutive substrings within that string consisting of at least one character. Substrings should be returned in the order in which they appear. + +Note than in the string 'abc', 'ac' is not a consecutive substring. + +The input string will have a length of 0 or more. + +``` +Input: 'abc' +Output: ['a', 'ab', 'abc', 'b', 'bc', 'c'] + +Input: 'a' +Output: ['a'] +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/javascript/consecutive_substrings.js b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/javascript/consecutive_substrings.js new file mode 100644 index 00000000..115c46f1 --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/javascript/consecutive_substrings.js @@ -0,0 +1,19 @@ +function consecutiveSubstrings(string) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: ['a', 'ab', 'abc', 'b', 'bc', 'c']"); + console.log("=>", consecutiveSubstrings('abc')); + + console.log(""); + + console.log("Expecting: ['a']"); + console.log("=>", consecutiveSubstrings('a')); +} + +module.exports = consecutiveSubstrings; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/javascript/package.json b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/javascript/package.json new file mode 100644 index 00000000..28ba390d --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "consecutive_substrings", + "version": "1.0.0", + "description": "consecutive_substrings", + "main": "consecutive_substrings.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/javascript/tests/consecutive_substrings.test.js b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/javascript/tests/consecutive_substrings.test.js new file mode 100644 index 00000000..4e83192b --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/javascript/tests/consecutive_substrings.test.js @@ -0,0 +1,14 @@ +const consecutiveSubstrings = require('../consecutive_substrings'); + +test('returns an empty array when the input string is empty', () => { + expect(consecutiveSubstrings('').length).toBe(0); +}); + +test('returns an array containing one string when the input is one character', () => { + expect(consecutiveSubstrings('a')).toEqual(['a']); +}); + +test('can handle many letters', () => { + expect(consecutiveSubstrings('ab')).toEqual(['a', 'ab', 'b']); + expect(consecutiveSubstrings('abc')).toEqual(['a', 'ab', 'abc', 'b', 'bc', 'c']); +}); diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/.rspec b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/Gemfile b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/consecutive_substrings.rb b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/consecutive_substrings.rb new file mode 100644 index 00000000..c2349e3a --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/consecutive_substrings.rb @@ -0,0 +1,20 @@ +def consecutive_substrings(string) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: ['a', 'ab', 'abc', 'b', 'bc', 'c']" + print "=> " + print consecutive_substrings('abc') + + puts + + puts "Expecting: ['a']" + print "=> " + print consecutive_substrings('a') + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/spec/consecutive_substrings_spec.rb b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/spec/consecutive_substrings_spec.rb new file mode 100644 index 00000000..7080c042 --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/spec/consecutive_substrings_spec.rb @@ -0,0 +1,16 @@ +require './consecutive_substrings' + +RSpec.describe '#consecutive_substrings' do + it 'returns an empty array when the input string is empty' do + expect(consecutive_substrings('')).to eq([]) + end + + it 'returns an array containing one string when the input is one character' do + expect(consecutive_substrings('a')).to eq(['a']) + end + + it 'can handle many letters' do + expect(consecutive_substrings('ab')).to eq(['a', 'ab', 'b']) + expect(consecutive_substrings('abc')).to eq(['a', 'ab', 'abc', 'b', 'bc', 'c']) + end +end diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/spec/spec_helper.rb b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/solutions/consecutive_substrings.js b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/solutions/consecutive_substrings.js new file mode 100644 index 00000000..197b56d1 --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/solutions/consecutive_substrings.js @@ -0,0 +1,64 @@ +function consecutiveSubstrings(string) { + const stringArr = string.split(''); + const subs = []; + + stringArr.forEach((char, idx) => { + subs.push(char); + + let fragment = char; + + stringArr.slice(idx + 1).forEach((letter) => { + fragment = fragment + letter; + subs.push(fragment); + }); + }); + + return subs; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: ['a', 'ab', 'abc', 'b', 'bc', 'c']"); + console.log(consecutiveSubstrings('abc')); + + console.log(""); + + console.log("Expecting: ['a']"); + console.log(consecutiveSubstrings('a')); + + console.log(""); + + console.log("Expecting: []"); + console.log(consecutiveSubstrings('')); + + console.log(""); + + console.log("Expecting: ['a', 'ab', 'b']"); + console.log(consecutiveSubstrings('ab')); +} + +module.exports = consecutiveSubstrings; + +// Please add your pseudocode to this file +/***************************************************************************** + * initialize an empty array called subs + * + * iterate over string: + * push character onto subs + * initialize fragment and store character in it + * + * iterate over string starting one index ahead: + * add letter to fragment + * push fragment onto subs + * + * return subs + * ****************************************************************************/ + +// And a written explanation of your solution +/***************************************************************************** + * We can get all of the consecutive substrings by iterating over the string. We + * push that character onto an array. We also store that character in a variable, + * which we'll use to store the rest of the following characters. Next, we iterate + * over the rest of the string. Each time we encounter a new character, we add it + * onto the variable and then push the variable onto the array. + * ****************************************************************************/ \ No newline at end of file diff --git a/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/solutions/consecutive_substrings.rb b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/solutions/consecutive_substrings.rb new file mode 100644 index 00000000..50b4671a --- /dev/null +++ b/03-week-3--additional-practice/04-bonus-5--consecutive-substrings/solutions/consecutive_substrings.rb @@ -0,0 +1,61 @@ +def consecutive_substrings(string) + subs = [] + + string.split('').each_with_index do |char, idx| + subs << char + fragment = char + + string[idx + 1..-1].split('').each_with_index do |letter| + fragment += letter + subs << fragment + end + end + + subs +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: ['a', 'ab', 'abc', 'b', 'bc', 'c']" + print consecutive_substrings('abc') + + puts + + puts "Expecting: ['a']" + print consecutive_substrings('a') + + # Don't forget to add your own! + + puts + + puts "Expecting: []" + print consecutive_substrings('') + + puts + + puts "Expecting: ['a', 'ab', 'b']" + print consecutive_substrings('ab') +end + +# Please add your pseudocode to this file +##################################################################################### + # initialize an empty array called subs + # + # iterate over string: + # push character onto subs + # initialize fragment and store character in it + # + # iterate over string starting one index ahead: + # add letter to fragment + # push fragment onto subs + # + # return subs +##################################################################################### + +# And a written explanation of your solution +##################################################################################### + # We can get all of the consecutive substrings by iterating over the string. We + # push that character onto an array. We also store that character in a variable, + # which we'll use to store the rest of the following characters. Next, we iterate + # over the rest of the string. Each time we encounter a new character, we add it + # onto the variable and then push the variable onto the array. +##################################################################################### diff --git a/04-pairing-exercises-1/00-code-review/README.md b/04-pairing-exercises-1/00-code-review/README.md new file mode 100644 index 00000000..d6a5256c --- /dev/null +++ b/04-pairing-exercises-1/00-code-review/README.md @@ -0,0 +1,96 @@ +# Code Review + +## Introduction + +For this activity, you will perform a code review with a partner. You should +each review the other's code. + +- Choose a challenge you have both completed. +- Share the link to your repo with your partner. +- Write the critique and share it with your partner over Slack. Alternatively, + fork your partner's repo, add your comments to the code or in a new file, and + then make a Pull Request. + +## How to Write a Good Code Review + +A good code review is constructive and detailed. It does not use harsh or +derogatory language, nor is it broad in nature. When writing a review, ask +yourself if the critique could actually help your partner become a better coder. + +Here are some areas you may wish to comment on: + +- Readability: is the code easy to understand as it is written? Are variables + and functions named appropriately? Is code formatted consistently (spaces/tabs + and line breaks)? Have appropriate naming conventions been followed for the + chosen language (e.g. camel case for JS and snake case for Ruby)? +- Separation: were helper functions declared and used where appropriate? Did + those functions have a single responsibility? Were overly long function bodies + avoided (e.g. a function containing 20+ lines of code is probably too long and + a good candidate for being refactored)? +- Syntax: was the correct syntax used? Be sure to point out any syntactical + errors. +- Redundancies: is there any useless code in the solution, such as unused + variables or functions? Are there lines of code that could be removed without + affecting the output? Were unnecessary comments removed? +- Efficacy: does the code achieve the goal? Were any tests cases missed? +- Overall quality: are there areas that could be improved? Could certain lines + be shortened without reducing readability? Were the appropriate methods used, + such as Array methods, when applicable? If pseudocode and an explanation were + included in your partner's repo, were you able to understand it? What did they + explain well? What could be made clearer and how? + +## Example + +The following is an example of a review for the below code. Use the example as a +guide for your own reviews and not a required template. + +```js +// Challenge: Print each character on a single line in the following format: : +function printCharacters(string) { + var output = []; + + for (var i = 0; i <= string.length; i++) { + var outputString = i + ": " + string[i]; + output.push(outputString); + } + + for (var i = 0; i <= output.length; i++) { + console.log(output[i]); + } +} +``` + +### Review + +Hi partner, + +I've looked over your solution to the challenge. I'm just going to dive right in +to what I think went well and what can be improved: + +What went well: + +- Formatting and consistency: The code is formatted well. The same spacing is + used throughout and so is camelcase. This made it very easy to read. +- Syntax: There are no syntax errors and the code runs without error. +- Naming: Everything is named clearly and easy to understand. +- Separation: The solution was short so I can see why you didn't use helper + functions. If you wanted to, you could separate each loop and their logic into + well-named helpers. + +What could be improved: + +- Syntax: I know it's weird to see this here also, but modern JS prefers let and + const to var. +- Efficacy: The code doesn't quite achieve the challenge. For example, even if + the string is empty it prints "0: undefined", which seems strange, and it + prints that same line but with a different number at the end of any provided + string. This is because of the terminating condition in the for loops. The + condition should be < and not <=. +- Overall quality: The challenge could be solved with a single loop that prints + the string. There's no need to create a new array and push strings to it. Also + we can loop over a string using a for...of loop, which would be more + appropriate than using a for loop. Whenever possible we should use the most + specific method provided by the language, e.g. when looping over an array use + forEach(). + +I didn't see an explanation or pseudocode, so I can't comment on that. diff --git a/04-pairing-exercises-1/01-whiteboard/README.md b/04-pairing-exercises-1/01-whiteboard/README.md new file mode 100644 index 00000000..d4a1176e --- /dev/null +++ b/04-pairing-exercises-1/01-whiteboard/README.md @@ -0,0 +1,92 @@ +# Whiteboard + +## Introduction + +For this activity, you and your partner will each choose a problem and solve it +in front of one another. You may select a problem you have already solved or a +problem you have not yet solved. You may complete this activity using an actual +whiteboard, if available, or in the IDE of your choosing, such as VS Code or an +online REPL. + +Keep in mind that these challenges tend to make people nervous, so remember to +always be kind, patient, and encouraging. Also be aware that nerves can cause +people to come up with some pretty weird solutions to problems, so remember to +bring your empathy with you! + +Plan to spend 15 minutes in each role. This means you and/or your partner might +not have enough time to finish the solution, and that's OK. If you can +reasonably spend more time on this, you can, but do put a time limit on it. + +## Instructions for Interviewer + +As the interviewer your job will be to first present the problem. Explain the +challenge to your partner and provide some example test cases. You are not +expected to provide every possible test case or edge case. Instead, provide just +enough detail for the interviewee to understand the problem and ask clarifying +questions. Example: "For this challenge, your function will accept a single +string as input and return it in reverse. So if it were to receive 'cat', it +would return 'tac'." + +You will also need to answer questions. Your partner might ask you to confirm +their understanding of the problem or whether or not they should handle certain +edge cases. If you don't know the answer to a question, it's OK to say "I don't +know" or "I'll let you decide." Sometimes the interviewer doesn't have the +answers. + +Notice when your partner gets stuck and needs a nudge in the right direction. +Provide helpful tips or hints, but don't give away the answer. Ideally, your +partner will ask questions when they get stuck, but if you notice that they're +struggling with something for a little too long, don't be afraid to give a +little nudge. You can also ask in advance if your partner would like a hint +before providing advice. + +When time is up, provide a constructive review of your partner's performance. +Some areas to talk about include: + +- Problem explanation: did your partner explain the problem back to you in their + own words and confirm their understanding before coding? Did they ask + clarifying questions when necessary? +- Testing: did your partner check their understanding against the given test + case/s? Did they write their own? +- Pseudocoding: did your partner explain what they were going to do out loud + before coding and with pseudocode? Did they check their pseudocode against the + test cases? +- Solution: did your partner solve the problem? Was their syntax correct? Did + they handle all of the test cases? Were variables and functions named + appropriately? How readable was the code? +- Openness to feedback: Did they ask for feedback when necessary? Were they + receptive to your feedback? + +Ultimately, it is more important for the interviewer to evaluate the +interviewee's communication during this exercise, so if time is an issue, focus +your review there rather than on the solution itself. When providing feedback, +be specific, so your partner has an opportunity to improve. Also structure your +feedback into two sections. For example, you might talk about what went well and +then what can be improved, rather than mixing them together. + +## Instructions for Interviewee + +As the interviewee, your job will be to solve the problem posed by your partner. +The Interviewer instructions above explain which areas you should focus on in +order to ace your interview. The content in the Welcome section of this course +will also help, so make sure you read it before attempting your first whiteboard +challenge. + +Here are some general tips: + +- Communication is key: explain the problem and your approach to the solution +- Assume you haven't been given all of the information, such as all of the + inputs you need to account for +- Ask for help when you need it and be receptive to feedback +- Don't be afraid to admit when you know there's a better way to do something, + e.g. "I know I'm brute forcing this solution and there's a more efficient way, + but I want to solve it first and then optimize once I know I can solve it." + +When time is up, provide a constructive review for your interviewer. Some areas +to talk about include: + +- Did the interviewer communicate clearly? Could you understand the challenge + and did they answer your questions in a manner that you could understand? +- Did they give you time to solve the problem or explore a possible solution + before providing feedback? In other words, were they patient? +- Was their feedback helpful? diff --git a/05-week-4--big-o-notation/00-day-1--introduction-to-big-o-notation/Big-O-graph.png b/05-week-4--big-o-notation/00-day-1--introduction-to-big-o-notation/Big-O-graph.png new file mode 100644 index 00000000..5a6124fb Binary files /dev/null and b/05-week-4--big-o-notation/00-day-1--introduction-to-big-o-notation/Big-O-graph.png differ diff --git a/05-week-4--big-o-notation/00-day-1--introduction-to-big-o-notation/README.md b/05-week-4--big-o-notation/00-day-1--introduction-to-big-o-notation/README.md new file mode 100644 index 00000000..5f47b5c5 --- /dev/null +++ b/05-week-4--big-o-notation/00-day-1--introduction-to-big-o-notation/README.md @@ -0,0 +1,177 @@ +# Day 1: Introduction to Big O Notation + +Let's say we were trying to solve a problem and we weren't sure which solution to choose. We might think about which solution is easier to code or understand, but we could also think about which solution runs faster. That last case is where Big O notation (calculating time complexity) comes in! + +## What is Big O Notation? + +Big O notation describes the amount of time it takes for a procedure to run relative to its input. For example, if the input is a string of one character: how long will it take? If the input is 1,000 characters, how long then? + +It describes the runtime abstractly in terms of the input size and the number of operations that will occur, rather than milliseconds or other units of time. Specifically, it describes the way in which the runtime grows as the input grows. Here are some examples of what Big O notation looks like: O(n), O(1), O(n2). We'll get into what this means in a moment. For now, know that n refers to the size of the input or the input itself, e.g. the length of a string or an array or a value itself, such as an integer. + +## Calculate for the Worst Case + +Before we get technical, let's use going to the grocery store as an analogy for calculating time complexity. + +### Grocery Store + +So we're at the store looking for chocolate chips. The best case is that we walk in, and BAM, those chocolate chips are right there in front of us. The worst case is that we end up walking up and down every single aisle, until finally we find them. That last case is what we're interested in, and we could then describe our chocolate chip search in terms of the number of aisles in the grocery store: O(aisles), because our search time grows as the number of aisles grows. In other words, the amount of time it takes to find chocolatey morsels of goodness depends on the number of aisles in the store. + +If there were 5 aisles, we could say O(5), but what if the grocery store added or removed aisles? We'd have to update our notation to the new value, e.g. O(20). This is why it's better to say O(aisles): it covers any number of aisles. + +### Finding an Element in an Array + +Let's say we have an unsorted array of elements and our function needs to find and return a specific element. That element could be at the 0th index or it could be at the last index or not in there at all, so we iterate over the array until we find it: + +``` +function findElement(arr, target): + iterate over arr: + if element == target: + return true + + return false +``` + +We're interested in the worst case, which is when the element is not in the array. In that case we must iterate over the entire array before returning false. We can refer to the number of elements as n, which means Big O for this function is O(n). The time it takes to run directly relates to the array's length. + +## Calculate for the Weakest Link + +Big O does not concern itself with every little detail in a procedure. Instead, it cares most about the part of the procedure that will take the longest. In other words, we find the part that will take the longest (the weakest link) and calculate Big O for that part. + +### Grocery Store + +Let's think back to our chocolate chip hunt. We discussed walking the aisles to search for those sweet nuggets, but we didn't talk about going to the store or walking home. Let's pretend we live one minute away from the store, so walking there always takes one minute. The time it takes to walk the aisles, on the other hand, depends on where the chocolate chips are. We don't need to concern ourselves with the time it takes to walk because it's not the weakest link: it's always the same amount of time. Walking the aisles is the weakest link because it has the potential to take up the most time. + +### Finding a Duplicate Element in an Array + +Let's say we need to check if there's a duplicate of the 0th element in an array elsewhere in that same array. Our algorithm might look like this: + +``` +function findDuplicate(arr): + zeroth_element = arr[0] + + iterate over arr starting at index 1: + if element == zeroth_element: + return true + + return false +``` + +We need not concern ourselves with the time it takes to grab the 0th element or return true or false - those tasks always take the same amount of time, and therefore, are not the weakest link. The weakest link is the iteration over the array, which depends on the length of the array, so it has the potential to take up the most time. If the array is one element long: great, it'll be super fast. But if it contains one million elements, that's going to take some time. Since the amount of time directly correlates with the length of the array (which we call n), Big O for this algorithm is O(n). + +## Drop the Coefficients + +When calculating time complexity, we don't concern ourselves with coefficients (the numbers that come before variables in mathematics, e.g. 2n or 5x). Let's look at a pseudocode example to see what this might look like: + +``` +function find_lowest_and_highest(arr): + result = [] + result << iterate and find minimum in arr + result << iterate and find maximum in arr + + return result +``` + +This code actually has two weakest links: iterating to find the maximum value and iterating to find the minimum value. Both of those procedures have a time complexity of O(n), since the minimum or maximum values could be at the end of the array in the worst case. We could say that the time complexity of find_lowest_and_highest is 2O(n), since the weakest link runs twice. However, Big O simply does not care about that, so we drop the coefficients and declare that this function runs in O(n) time. + +## Common Time Complexities + +It's a lot easier to calculate Big O if you know how the most common runtimes are defined. Please note that we aren't listing all of the possibilities here. + +### Constant Time: O(1) + +Constant time is the best time! Algorithms or procedures that run in O(1) time take the same amount of time to run regardless of the size of the input (like walking to the store to get chocolate chips). Some examples of constant time procedures include: + +- Mathematical operations, such as adding or subtracting numbers +- Accessing an element in an array at a specific index, e.g. array[2] +- Accessing a value in a Hash (Object in JS, Dictionary in Python) using a key +- Returning a value +- Printing a value (e.g. to the console or command line) + +As an example, it doesn't matter if you're accessing the 2nd or 1,000th element in an array when you're accessing it by its index number - it'll take the same amount of time either way! + +``` +function find_by_index(arr, index): + print arr[index] + return arr[index] +``` + +### Linear Time: O(n) + +Linear time is also good. The runtime for algorithms that run in O(n) time is proportional to the size of the input, e.g. 1 x input size or 3 x input size. Earlier we mentioned that finding an item in an array takes O(n) time because the item could be at the very end or not in there at all, which means we must iterate over the length of the array in the worst case. Some examples of linear time procedures include: + +- Iterating over an array, e.g. to find a target value +- Iterating over a string +- Printing each character or element of an array or string, respectively, one by one + +``` +function say_hi_to_everyone(names): + iterate over names: + print "hi " + name +``` + +### Quadratic Time: O(n2) + +Quadratic time is not good, but sometimes unavoidable. The runtime is defined as the square of the input's size. For example, if we had a quadratic time algorithm that took an array as an input: for an input of length 1, there would be 1 operation, but for an input of length 10, there would be 100 operations. Some examples of algorithms that take quadratic time include: + +- Bubble sort +- Selection sort +- Insertion sort +- Some algorithms that contain a loop nested in another loop + +``` +function nested_loop_quadratic_time(arr): + iterate over arr: + iterate over arr: + perform some operation +``` + +In the above example, the outer loop iterates over the entire input array. For each element in the input array, it then iterates over the entire array again. For an array of length 2, there are 2 \* 2 (or 4) operations, but for an array of length 10, there are 10 \* 10 (or 100) operations. + +### Logarithmic Time: O(log n) + +Logarithmic time is fantastic! It's not quite as good as constant time, but it is faster than linear time. If an algorithm runs in O(log n) time, time increases linearly while the input increases exponentially. Let's pretend we have some operation that takes 1 second to handle an input containing 10 elements. When the input has 100 elements, that operation will take 2 seconds, and 1,000 elements will take 3 seconds! That's fast! + +An algorithm may be logarithmic if: + +- The weakest link always runs less than n times +- Each time the input is operated upon, the size of the input is divided by some number + +An example of an O(log n) algorithm is: + +- Binary search + +``` +function useless_log_n_loop(arr): + n = length of arr + + while n > 0: + print arr[n] + n = n / 2 + round down n to nearest integer +``` + +### Graph of Common Time Complexities + +![Image of Big O Graph with Time vs Input](Big-O-graph.png) + +## Need to Know + +When we calculate Big O for a procedure, there are some runtimes we just need to know (or look up). For example, it's important to know the runtime for accessing an element in an array or accessing a value in a Hash (aka Object or Dictionary). Take some time to review this [resource](https://www.bigocheatsheet.com/) (bookmark it!), which includes the best, average, and worst case time complexity for common operations and algorithms. For now, the most important data structures to pay attention to are arrays and hash tables. + +## Note on Recursion + +When calculating Big O for recursive functions, we compare the total stack frames over time to the size of the input. + +``` +function count(stop_count, count = 0): + if count >= stop_count: + return 'done' + + return count(stop_count, count + 1) +``` + +For the above function the total number of frames added to the stack over time is directly proportional to the `stop_count`, so Big O is O(n). + +## Conclusion + +Calculating Big O for a procedure can help you decide between several solutions or improve a solution you've already thought of. It takes time and practice to get used to Big O notation. In the coming days, we'll be asking you to calculate the time complexity for problems you've already solved. We'll also show you our own calculations to help you along. diff --git a/05-week-4--big-o-notation/01-day-2--introduction-to-space-complexity/README.md b/05-week-4--big-o-notation/01-day-2--introduction-to-space-complexity/README.md new file mode 100644 index 00000000..b07bacfc --- /dev/null +++ b/05-week-4--big-o-notation/01-day-2--introduction-to-space-complexity/README.md @@ -0,0 +1,139 @@ +# Day 2: Introduction to Space Complexity + +Space complexity measures how much working memory an algorithm requires. Specifically it measures how the space requirements grow as the input grows. Sound familiar? If this reminds you of time complexity, you're correct! We use the same Big O notation to describe how much space a procedure needs in the worst case. + +Just like time complexity, we calculate space complexity when we're considering competing solutions or when we have specific space requirements we must meet. In game development, for example, engineers are often allocated upper limits for how much space each process may use, such as running the audio engine or rendering certain types of effects. + +If you're still struggling with the concept of time complexity, you might want to take 15 minutes to research Big O and get more comfortable with it before moving forward. Here are two resources you might wisht to read: +- [Interview Cake: Big O Notation](https://www.interviewcake.com/article/java/big-o-notation-time-and-space-complexity) +- [Rob Bell: A Beginner's Guide to Big O Notation](https://rob-bell.net/2009/06/a-beginners-guide-to-big-o-notation/) + +## Refresher + +The important things to remember when calculating time complexity are the same for space complexity: +- Calculate for the worst case +- Focus on the weakest link (the part that will take up the most space) +- Ignore the other details + +### Common Notations + +Here are some of the commonly used Big O values, though there are more, in order from most desirable to least desirable: + +1. Constant O(1): The amount of space required never changes, regardless of the input +2. Logarithmic O(log n): As the size of the input increases exponentially, the amount of space required grows linearly +3. Linear O(n): The amount of space required is equal to the size of the input +4. Quadratic O(n2): The amount of space required is the square of the input + +## Calculating Space Complexity + +When we calculate space complexity, we consider the size of the input as well as the extra space we're allocating for the algorithm to complete successfully. Let's take a look at some examples to see what this means. + +### Constant Space O(1) + +``` +function add(x, y): + return x + y +``` + +This function adds two numbers and returns the result. The inputs will be integers, and the return value will also be an integer. It doesn't matter what those integers are, the space requirements will always be the same for any given inputs. + +### Logarithmic Space O(log n) + +``` +/* for sorted arrays only */ + +function binary_search(array, target): + if array is empty: + return false + + middle = array[array length / 2] + + if middle == target: + return true + + if middle < target: + return binary_search(left half of array) + else: + return binary_search(right half of array) +``` + +The function above is recursive and requires logarithmic space: as the input size grows exponentially, the space required grows linearly. + +Each time it recurses it cuts the input array in half. If you recall from the time complexity reading, an algorithm _might_ take O(log n) runtime if the input is consistently being divided. The same is true for space complexity. + +Since the above method is recursive, we can think of the space in terms of the number of stack frames required for any input. If the target value isn't in the input array, the number of frames will be as follows: + +| Input Length | Frames | +|--------|--------| +| 1 | 1 | +| 2 | 2 | +| 3 | 2 | +| 4 | 3 | +| 5 | 3 | +| 10 | 4 | + +### Linear Space O(n) + +``` +function sum_array(array): + sum = 0 + + iterate over array: + sum = sum + array element + + return sum +``` + +This procedure stores an integer variable and requires an input array to run. If we were to code this fully using a `for` or `while` loop, we'd also have to store another variable for the iteration (the one commonly known as `i`). + +The integer variables, such as `sum` and `i` would require constant space, since they'll only ever store a single integer each. The input array, however, will have varying space requirements because its length is not fixed. The function could be called with an array that's empty or contains 1,000 elements. Therefore, this procedure requires O(n) space: the input array is the weakest link! + +### Quadratic Space O(n2) + +``` +/* a really useless function */ + +function quadratic_recurse(num): + total_times = num * num + + function recurse(count): + if count == total_times: + return + + recurse(count + 1) + + recurse(0) +``` + +This function is recursive and requires quadratic space. Once again, we can think of the required space in terms of total stack frames. If the input number is 1, 1 stack frame is required. If the input is 2, 4 stack frames, and if the input is 4, 16 stack frames are required. + +## Recursive Gotcha + +When thinking about the space complexity of a recursive function, we need to consider how deep the recursion will go in the worst case. In other words, we aren't interested in the total number of stack frames over time, but rather, the deepest depth or the largest number of frames that will be on the stack at any given time. Keep in mind that the stack grows and shrinks as recursive calls are made and then begin returning. A recursive call increases the stack, while hitting a return statement reduces its size. + +``` +function big_sum(array): + total = 0 + + function add(count): + if count == array length: + return + + iterate over each element in array: + total = total + element + add(count + 1) + + add(0) + return total +``` + +The above function adds array length number of frames to the stack for every element in the input array. Let's say the input array is `[1, 2]`. `add` will recurse with a `count` of 1, and will then recurse again with a `count` of 2, so two frames are on the stack. At this point the `count` equals the array length, so the frame will be popped from the stack, reducing its size. As it turns out, the largest number of frames on the stack (or the deepest depth), equals O(n) - i.e. the length fo the input array - for this function. + +## Conclusion + +We can use Big O notation to represent the space complexity of an algorithm, similar to how we use it for time complexity. To calculate the space complexity, we must consider all of the data required for the algorithm to run. We can then pinpoint the weakest link with the worst case in mind and use that to determine Big O. And don't forget, we have to consider how much space the input requires! + +## Further Resources + +- [Understanding Space Complexity](https://www.baeldung.com/cs/space-complexity) +- [Space Complexity of Algorithms](https://www.studytonight.com/data-structures/space-complexity-of-algorithms#) \ No newline at end of file diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/.gitignore b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/README.md b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/README.md new file mode 100644 index 00000000..5c53b014 --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/README.md @@ -0,0 +1,87 @@ +# Days 1-2: Implement a Stack Class + +A stack is a data structure where items are processed in last-in-first-out order (LIFO). Similar to a stack of pancakes, where the last pancake placed on the stack is eaten first, the last item placed on the stack is removed and operated upon first. The last item on the stack is known as the **top**, and the first item that was pushed on the stack is known as the **base**. This means that the base is removed from the stack last. + +![pancake stack](./pancakes.png) + +We use stacks to solve certain algorithm problems. We also think of recursive problems in terms of a stack, since each recursive call results in a stack frame being added to the stack: those frames are then processed and removed in LIFO order! If you are tackling a problem and you see that it must be solved depth-first, this is a good clue that you'll either be using recursion or implementing a stack. Depth-first means that we go as deep as we can before we start processing data (LIFO order). + +## Wait! This Sounds A Lot Like an Array! + +We can actually implement a Stack class using an Array as the underlying data structure! Arrays provide all of the methods we need for the core functionality of a Stack. Some of the methods we'll be adding to our Stack class include: `push`, `pop`, `peek`, `isEmpty`, and `print`. Do any of those sound familiar? + +However, it's important to note that an Array doesn't have to be the underlying data structure. It could be another data structure, such as a Linked List (don't worry if you don't know what that is). When calculating Big O for a Stack, we always need to consider what the underlying data structure is since that'll affect our calculations. + +## Implement the Stack Class + +The Stack class already has two attributes: the `stack` itself (an Array) and a `limit`, which is an Integer representing the total number of items allowed in the Stack at one time. + +Add the following methods to the Stack class: + +### `push(item)` + +`push` adds an item to the top of the Stack. If the Stack is full, the item should not be pushed and an Error should be thrown. + +### `pop` + +`pop` removes the item at the top of the Stack and returns it. Don't worry if `pop` is called on an empty stack. It's OK for it to return the default return value, such as `undefined` or `nil`. + +### `peek` + +`peek` returns the item at the top of the Stack without removing it. If the Stack is empty, use the default return value, e.g. `undefined` or `nil`. + +### `isEmpty` + +`isEmpty` returns `true` if the Stack is empty, otherwise `false`. + +### `isFull` + +`isFull` returns `true` if no more space is available in the Stack, otherwise `false`. + +### `size` + +`size` returns the number of items currently on the Stack. + +### `search(target)` + +`search` returns an Integer representing how far the target item is from the top of the stack. If the item is not in the Stack, return `-1`. Example: + +``` +// Stack = 1, 2, 3, 4, 5 <-top + +stack.search(5) => 0 +stack.search(4) => 1 +stack.search(6) => -1 +``` + +### `print` + +`print` prints the contents of the stack. It does not return them! You may print them however you wish. + +We've provided starter code for some languages. Choose whichever language you like. Once again, we recommend writing your own tests first and then running the test suites. + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/javascript/package.json b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/javascript/package.json new file mode 100644 index 00000000..c46ffedc --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "stack", + "version": "1.0.0", + "description": "stack class", + "main": "stack.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/javascript/stack.js b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/javascript/stack.js new file mode 100644 index 00000000..62df33d9 --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/javascript/stack.js @@ -0,0 +1,56 @@ +class Stack { + constructor() { + this.stack = []; + // this is an arbitrary value to make testing easier + // 1,024 would be way too high to test! + this.limit = 10; + } + + // add item to top of stack if not full + // if full throw error + push(item) { + + } + + // remove item from top of stack and return it + pop() { + + } + + // return item at top of stack without removing it + peek() { + + } + + // return true if stack is empty, otherwise false + isEmpty() { + + } + + // return true if stack is full, otherwise false + isFull() { + + } + + // return number of items in stack + size() { + + } + + // return -1 if item not in stack, otherwise integer representing + // how far it is from the top + search(target) { + + } + + // print contents of stack: do not return the stack itself! + print() { + + } +} + +if (require.main === module) { + // add your own tests in here +} + +module.exports = Stack; diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/javascript/tests/stack.test.js b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/javascript/tests/stack.test.js new file mode 100644 index 00000000..7decb52e --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/javascript/tests/stack.test.js @@ -0,0 +1,71 @@ +const Stack = require('../stack'); +const stack = new Stack(); +const stackWithItems = new Stack; +const fullStack = new Stack(); + +stackWithItems.stack = [1, 2, 3, 4, 5]; +fullStack.stack = [0,1,2,3,4,5,6,7,8,9]; + +test('size() returns 0 when the stack is empty', () => { + expect(stack.size()).toBe(0); +}); + +test('size() returns the correct number when the stack is not empty', () => { + expect(stackWithItems.size()).toBe(5); +}); + +test('isEmpty() returns true when the stack is empty', () => { + expect(stack.isEmpty()).toBe(true); +}); + +test('isEmpty() returns false when the stack has items', () => { + expect(stackWithItems.isEmpty()).toBe(false); +}); + +test('isFull() returns false when the stack has less than 10 items', () => { + expect(stack.isFull()).toBe(false); + expect(stackWithItems.isFull()).toBe(false); +}); + +test('isFull() returns true when the stack has 10 items', () => { + expect(fullStack.isFull()).toBe(true); +}); + +test('peek() returns the last item on the stack without removing it', () => { + expect(stackWithItems.peek()).toBe(5); + expect(stackWithItems.size()).toBe(5); +}); + +test('pop() returns the last item on the stack and removes it', () => { + expect(stackWithItems.pop()).toBe(5); + expect(stackWithItems.size()).toBe(4); + stackWithItems.push(5); +}); + +test('push() pushes an item onto the stack if it\'s not full', () => { + stackWithItems.push(6); + + expect(stackWithItems.size()).toBe(6); + expect(stackWithItems.peek()).toBe(6); + stackWithItems.pop(); +}); + +test('push() throws an exception if the stack is full', () => { + expect(() => { + fullStack.push(10); + }).toThrow(Error); +}); + +test('search() returns the distance between the top and the target element', () => { + expect(stackWithItems.search(5)).toBe(0); + expect(stackWithItems.search(4)).toBe(1); + expect(stackWithItems.search(1)).toBe(4); +}); + +test('search() returns -1 when the target is not in the stack', () => { + expect(stackWithItems.search(7)).toBe(-1); +}); + +test('print() does not return the stack array itself', () => { + expect(stack.print()).not.toBe(stack.stack); +}); diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/pancakes.png b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/pancakes.png new file mode 100644 index 00000000..ece9dde2 Binary files /dev/null and b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/pancakes.png differ diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/.rspec b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/Gemfile b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/spec/spec_helper.rb b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/spec/stack_spec.rb b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/spec/stack_spec.rb new file mode 100644 index 00000000..d5e94a2b --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/spec/stack_spec.rb @@ -0,0 +1,100 @@ +require './stack.rb' + +class Stack + attr_accessor :stack +end + +RSpec.describe 'Stack' do + let(:stack) { Stack.new } + + context '#size' do + it 'returns 0 when the stack is empty' do + expect(stack.size).to eq(0) + end + + it 'returns the correct size when items are in the stack' do + stack.stack = [1, 2, 3] + + expect(stack.size).to eq(3) + end + end + + context '#isEmpty?' do + it 'returns true when the stack is empty' do + expect(stack.isEmpty?).to be true + end + + it 'returns false when items are in the stack' do + stack.stack = [1] + + expect(stack.isEmpty?).to be false + end + end + + context '#isFull?' do + it 'returns false when the stack has less than 10 items' do + expect(stack.isFull?).to be false + end + + it 'returns true when the stack has 10 items' do + stack.stack = [1,2,3,4,5,6,7,8,9,10] + + expect(stack.isFull?).to be true + end + end + + context '#peek' do + it 'returns the last item on the stack without removing it' do + stack.stack = [1, 2, 3] + + expect(stack.peek).to eq(3) + expect(stack.size).to eq(3) + end + end + + context '#pop' do + it 'returns the last item on the stack and removes it' do + stack.stack = [1, 2, 3] + + expect(stack.pop).to eq(3) + expect(stack.size).to eq(2) + end + end + + context '#push' do + it 'pushes an item onto the stack if it\'s not full' do + stack.push(0) + + expect(stack.size).to eq(1) + expect(stack.peek).to eq(0) + end + + it 'throws an exception if the stack is full' do + stack.stack = [0,1,2,3,4,5,6,7,8,9] + + expect { stack.push(10) }.to raise_error + end + end + + context '#search' do + it 'returns the distance between the top and the target element' do + stack.stack = [1, 2, 3, 4, 5] + + expect(stack.search(5)).to eq(0) + expect(stack.search(4)).to eq(1) + expect(stack.search(1)).to eq(4) + end + + it 'returns -1 when the target is not in the stack' do + stack.stack = [1, 2, 3, 4, 5] + + expect(stack.search(7)).to eq(-1) + end + end + + context '#print' do + it 'does not return the stack array itself' do + expect(stack.print).to_not be(stack.stack) + end + end +end \ No newline at end of file diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/stack.rb b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/stack.rb new file mode 100644 index 00000000..df949718 --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/ruby/stack.rb @@ -0,0 +1,49 @@ +class Stack + attr_reader :limit + + def initialize + @stack = [] + # this is an arbitrary value to make testing easier + # 1,024 would be way too high to test! + @limit = 10 + end + + # add item to top of stack if not full + # if full, throw error + def push(item) + end + + # remove item from top of stack and return it + def pop + end + + # return item at top of stack without removing it + def peek + end + + # return true if stack is empty, otherwise false + def isEmpty? + end + + # return true if stack is full, otherwise false + def isFull? + end + + # return number of items in stack + def size + end + + # return -1 if item not in stack, otherwise integer representing + # how far it is from the top + def search(target) + end + + # print contents of stack: do not return the stack itself! + def print + end +end + +if __FILE__ == $PROGRAM_NAME + # Don't forget to add your tests! +end + diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/solutions/stack.js b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/solutions/stack.js new file mode 100644 index 00000000..807ef529 --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/solutions/stack.js @@ -0,0 +1,113 @@ +class Stack { + constructor() { + this.stack = []; + // this is an arbitrary value to make testing easier + // 1,024 would be way too high to test! + this.limit = 10; + } + + // add item to top of stack if not full + push(item) { + if (!this.isFull()) { + this.stack.push(item); + } else { + throw new Error('Stack is full!'); + } + } + + // remove item from top of stack + pop() { + return this.stack.pop(); + } + + // return item at top of stack without removing it + peek() { + return this.stack[this.size() - 1]; + } + + // return true if stack is empty, otherwise false + isEmpty() { + return this.size() === 0; + } + + // return true if stack is full, otherwise false + isFull() { + return this.size() === this.limit; + } + + // return number of items in stack + size() { + return this.stack.length; + } + + // return -1 if item not in stack, otherwise integer representing + // how far it is from the top + search(target) { + for (let i = -1; i >= -this.size(); --i) { + if (this.stack[this.size() + i] === target) { + return Math.abs(i) - 1; + } + } + + return -1; + } + + // print contents of stack: do not return the stack itself! + print() { + console.log(this.stack.join(' <- ')); + } +} + +if (require.main === module) { + // add your own tests in here + const stack = new Stack(); + + console.log('size', stack.size()); + console.log('is empty?', stack.isEmpty()); + console.log('is full?', stack.isFull()); + console.log('find 5', stack.search(5)); + console.log('peek while empty', stack.peek()); + + for (let i = 0; i < 5; ++i) { + stack.push(i); + } + + console.log('ADD ITEMS 0 TO 4'); + console.log('size', stack.size()); + console.log('is empty?', stack.isEmpty()); + console.log('is full?', stack.isFull()); + console.log('find 3', stack.search(3)); + console.log('peek', stack.peek()); + stack.print(); + + for (let i = 5; i < 10; ++i) { + stack.push(i); + } + + console.log('ADD ITEMS 5 TO 9'); + console.log('size', stack.size()); + console.log('is empty?', stack.isEmpty()); + console.log('is full?', stack.isFull()); + console.log('find 3', stack.search(3)); + console.log('peek', stack.peek()); + stack.print(); + + console.log('pop', stack.pop()); + console.log('size', stack.size()); + console.log('is empty?', stack.isEmpty()); + console.log('is full?', stack.isFull()); + console.log('peek', stack.peek()); + stack.print(); + + console.log('GENERATE ERROR'); + stack.push(9); + + try { + stack.push(10); + } catch(err) { + console.log(err); + stack.print(); + } +} + +module.exports = Stack; diff --git a/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/solutions/stack.rb b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/solutions/stack.rb new file mode 100644 index 00000000..1d39cc19 --- /dev/null +++ b/06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class/solutions/stack.rb @@ -0,0 +1,110 @@ +class Stack + attr_reader :limit + + def initialize + @stack = [] + # this is an arbitrary value to make testing easier + # 1,024 would be way too high to test! + @limit = 10 + end + + # add item to top of stack if not full + # if full, throw error + def push(item) + raise 'Stack is full' if isFull? + + @stack.push(item) + end + + # remove item from top of stack and return it + def pop + @stack.pop + end + + # return item at top of stack without removing it + def peek + @stack.last + end + + # return true if stack is empty, otherwise false + def isEmpty? + size.zero? + end + + # return true if stack is full, otherwise false + def isFull? + size === @limit + end + + # return number of items in stack + def size + @stack.length + end + + # return -1 if item not in stack, otherwise integer representing + # how far it is from the top + def search(target) + @stack.each_with_index do |item, idx| + return size - idx - 1 if item == target + end + + -1 + end + + # print contents of stack: do not return the stack itself! + def print + puts @stack.join(' <- ') + end +end + +if __FILE__ == $PROGRAM_NAME + stack = Stack.new; + + puts "'size': #{stack.size}" + puts "'is empty?': #{stack.isEmpty?}" + puts "'is full?': #{stack.isFull?}" + puts "'find 5': #{stack.search(5)}" + puts "'peek while empty': #{stack.peek}" + + (0...5).each do |num| + stack.push(num) + end + + puts "'ADD ITEMS 0 TO 4'" + puts "'size': #{stack.size}" + puts "'is empty?': #{stack.isEmpty?}" + puts "'is full?': #{stack.isFull?}" + puts "'find 3': #{stack.search(3)}" + puts "'peek': #{stack.peek}" + stack.print + + (5..9).each do |num| + stack.push(num) + end + + puts 'ADD ITEMS 5 TO 9' + puts "'size': #{stack.size}" + puts "'is empty?': #{stack.isEmpty?}" + puts "'is full?': #{stack.isFull?}" + puts "'find 3': #{stack.search(3)}" + puts "'peek': #{stack.peek}" + stack.print + + puts "'pop': #{stack.pop}" + puts "'size': #{stack.size}" + puts "'is empty?': #{stack.isEmpty?}" + puts "'is full?': #{stack.isFull?}" + puts "'peek': #{stack.peek}" + stack.print + + puts'GENERATE ERROR' + stack.push(9) + + begin + stack.push(10) + rescue StandardError => e + puts e.message + stack.print + end +end + diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/.gitignore b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/README.md b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/README.md new file mode 100644 index 00000000..c1c692cb --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/README.md @@ -0,0 +1,83 @@ +# Days 3-4: Implement a Queue Class + +A queue is a data structure where items are processed in first-in-first-out order (FIFO). It has two ends: a front and a rear. The front is where items are dequeued from first, while items at the rear are handled last. Items are added to the rear of the queue until the queue is full. A queue operates a lot like a queue at the checkout of a grocery store. Customers join at the end of the line and are served at the front of the line. + +![grocery queue](./grocery_store.jpg) + +We can implement a Queue class using an Array as the underlying data structure! Arrays provide all of the methods we need for the core functionality of a Queue. Some of the methods we'll be adding to our Queue class include: `enqueue`, `dequeue`, `peek`, `isEmpty`, and `print`. + +However, it's important to note that an Array doesn't have to be the underlying data structure. It could be another data structure, such as a Linked List (don't worry if you don't know what that is). When calculating Big O for a Queue, we always need to consider what the underlying data structure is since that'll affect our calculations. + +## Implement the Queue Class + +The Queue class already has two attributes: the `queue` itself (an Array) and a `limit`, which is an Integer representing the total number of items allowed in the `queue` at one time. + +Add the following methods to the class: + +### `enqueue(item)` + +`enqueue` adds an item to the back of the queue. If the queue is full, the item should not be pushed and an Error should be thrown. + +### `dequeue` + +`dequeue` removes the item at the front of the queue and returns it. Don't worry if `dequeue` is called on an empty queue. It's OK for it to return the default return value, such as `undefined` or `nil`. + +### `peek` + +`peek` returns the item at the front of the queue without removing it. If the queue is empty, use the default return value, e.g. `undefined` or `nil`. + +### `isEmpty` + +`isEmpty` returns `true` if the queue is empty, otherwise `false`. + +### `isFull` + +`isFull` returns `true` if no more space is available in the queue, otherwise `false`. + +### `size` + +`size` returns the number of items currently in the queue. + +### `search(target)` + +`search` returns an Integer representing how far the target item is from the front of the queue. If the item is not in the queue, return `-1`. Example: + +``` +// queue = 1, 2, 3, 4, 5 <- rear + +queue.search(5) => 4 +queue.search(4) => 3 +queue.search(6) => -1 +``` + +### `print` + +`print` prints the contents of the queue. It does not return them! You may print them however you wish. + +We've provided starter code for some languages. Choose whichever language you like. Once again, we recommend writing your own tests first and then running the test suites. + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/grocery_store.jpg b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/grocery_store.jpg new file mode 100644 index 00000000..2e6830ee Binary files /dev/null and b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/grocery_store.jpg differ diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/javascript/package.json b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/javascript/package.json new file mode 100644 index 00000000..01c377a0 --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "queue", + "version": "1.0.0", + "description": "queue class", + "main": "queue.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/javascript/queue.js b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/javascript/queue.js new file mode 100644 index 00000000..06654e80 --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/javascript/queue.js @@ -0,0 +1,56 @@ +class Queue { + constructor() { + this.queue = []; + // this is an arbitrary value to make testing easier + // 1,024 would be way too high to test! + this.limit = 10; + } + + // add item to rear of queue if not full + // if full throw error + enqueue(item) { + + } + + // remove item from front of queue and return it + dequeue() { + + } + + // return item at front of queue without removing it + peek() { + + } + + // return true if queue is empty, otherwise false + isEmpty() { + + } + + // return true if queue is full, otherwise false + isFull() { + + } + + // return number of items in queue + size() { + + } + + // return -1 if item not in queue, otherwise integer representing + // how far it is from the front + search(target) { + + } + + // print contents of queue: do not return the queue itself! + print() { + + } +} + +if (require.main === module) { + // add your own tests in here +} + +module.exports = Queue; diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/javascript/tests/queue.test.js b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/javascript/tests/queue.test.js new file mode 100644 index 00000000..6007b1f5 --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/javascript/tests/queue.test.js @@ -0,0 +1,71 @@ +const Queue = require('../queue'); +const queue = new Queue(); +const queueWithItems = new Queue; +const fullQueue = new Queue(); + +queueWithItems.queue = [1, 2, 3, 4, 5]; +fullQueue.queue = [0,1,2,3,4,5,6,7,8,9]; + +test('size() returns 0 when the queue is empty', () => { + expect(queue.size()).toBe(0); +}); + +test('size() returns the correct number when the queue is not empty', () => { + expect(queueWithItems.size()).toBe(5); +}); + +test('isEmpty() returns true when the queue is empty', () => { + expect(queue.isEmpty()).toBe(true); +}); + +test('isEmpty() returns false when the queue has items', () => { + expect(queueWithItems.isEmpty()).toBe(false); +}); + +test('isFull() returns false when the queue has less than 10 items', () => { + expect(queue.isFull()).toBe(false); + expect(queueWithItems.isFull()).toBe(false); +}); + +test('isFull() returns true when the queue has 10 items', () => { + expect(fullQueue.isFull()).toBe(true); +}); + +test('peek() returns the frontmost item in the queue without removing it', () => { + expect(queueWithItems.peek()).toBe(1); + expect(queueWithItems.size()).toBe(5); +}); + +test('dequeue() returns the first item in the queue and removes it', () => { + expect(queueWithItems.dequeue()).toBe(1); + expect(queueWithItems.size()).toBe(4); + queueWithItems.queue.unshift(1); +}); + +test('enqueue() pushes an item onto the queue if it\'s not full', () => { + queueWithItems.enqueue(6); + + expect(queueWithItems.size()).toBe(6); + expect(queueWithItems.peek()).toBe(1); + queueWithItems.queue.pop(); +}); + +test('enqueue() throws an exception if the queue is full', () => { + expect(() => { + fullQueue.enqueue(10); + }).toThrow(Error); +}); + +test('search() returns the distance between the front and the target element', () => { + expect(queueWithItems.search(5)).toBe(4); + expect(queueWithItems.search(4)).toBe(3); + expect(queueWithItems.search(1)).toBe(0); +}); + +test('search() returns -1 when the target is not in the queue', () => { + expect(queueWithItems.search(7)).toBe(-1); +}); + +test('print() does not return the queue array itself', () => { + expect(queue.print()).not.toBe(queue.queue); +}); diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/.rspec b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/Gemfile b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/queue.rb b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/queue.rb new file mode 100644 index 00000000..fee25564 --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/queue.rb @@ -0,0 +1,49 @@ +class Queue + attr_reader :limit + + def initialize + @queue = [] + # this is an arbitrary value to make testing easier + # 1,024 would be way too high to test! + @limit = 10 + end + + # add item to rear of queue if not full + # if full, throw error + def enqueue(item) + end + + # remove item from front of queue and return it + def dequeue + end + + # return item at front of queue without removing it + def peek + end + + # return true if queue is empty, otherwise false + def isEmpty? + end + + # return true if queue is full, otherwise false + def isFull? + end + + # return number of items in queue + def size + end + + # return -1 if item not in queue, otherwise integer representing + # how far it is from the front + def search(target) + end + + # print contents of queue: do not return the queue itself! + def print + end +end + +if __FILE__ == $PROGRAM_NAME + # Don't forget to add your tests! +end + diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/spec/queue_spec.rb b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/spec/queue_spec.rb new file mode 100644 index 00000000..c76b6922 --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/spec/queue_spec.rb @@ -0,0 +1,101 @@ +require './queue.rb' + +class Queue + attr_accessor :queue +end + +RSpec.describe 'Queue' do + let(:queue) { Queue.new } + + context '#size' do + it 'returns 0 when the queue is empty' do + expect(queue.size).to eq(0) + end + + it 'returns the correct size when items are in the queue' do + queue.queue = [1, 2, 3] + + expect(queue.size).to eq(3) + end + end + + context '#isEmpty?' do + it 'returns true when the queue is empty' do + expect(queue.isEmpty?).to be true + end + + it 'returns false when items are in the queue' do + queue.queue = [1] + + expect(queue.isEmpty?).to be false + end + end + + context '#isFull?' do + it 'returns false when the queue has less than 10 items' do + expect(queue.isFull?).to be false + end + + it 'returns true when the queue has 10 items' do + queue.queue = [1,2,3,4,5,6,7,8,9,10] + + expect(queue.isFull?).to be true + end + end + + context '#peek' do + it 'returns the frontmost item in the queue without removing it' do + queue.queue = [1, 2, 3] + + expect(queue.peek).to eq(1) + expect(queue.size).to eq(3) + end + end + + context '#dequeue' do + it 'returns the first item in the queue and removes it' do + queue.queue = [1, 2, 3] + + expect(queue.dequeue).to eq(1) + expect(queue.size).to eq(2) + end + end + + context '#enqueue' do + it 'pushes an item onto the queue if it\'s not full' do + queue.queue = [1, 2, 3] + queue.enqueue(0) + + expect(queue.size).to eq(4) + expect(queue.peek).to eq(1) + end + + it 'throws an exception if the queue is full' do + queue.queue = [0,1,2,3,4,5,6,7,8,9] + + expect { queue.enqueue(10) }.to raise_error + end + end + + context '#search' do + it 'returns the distance between the front and the target element' do + queue.queue = [1, 2, 3, 4, 5] + + expect(queue.search(5)).to eq(4) + expect(queue.search(4)).to eq(3) + expect(queue.search(1)).to eq(0) + end + + it 'returns -1 when the target is not in the queue' do + queue.queue = [1, 2, 3, 4, 5] + + expect(queue.search(7)).to eq(-1) + end + end + + context '#print' do + it 'does not return the queue array itself' do + expect(queue.print).to_not be(queue.queue) + end + end +end \ No newline at end of file diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/spec/spec_helper.rb b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/solutions/queue.js b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/solutions/queue.js new file mode 100644 index 00000000..f272776e --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/solutions/queue.js @@ -0,0 +1,107 @@ +class Queue { + constructor() { + this.queue = []; + // this is an arbitrary value to make testing easier + // 1,024 would be way too high to test! + this.limit = 10; + } + + // add item to rear of queue if not full + enqueue(item) { + if (!this.isFull()) { + this.queue.push(item); + } else { + throw new Error('Queue is full!'); + } + } + + // remove item from front of queue + dequeue() { + return this.queue.shift(); + } + + // return item at front of queue without removing it + peek() { + return this.queue[0]; + } + + // return true if queue is empty, otherwise false + isEmpty() { + return this.size() === 0; + } + + // return true if queue is full, otherwise false + isFull() { + return this.size() === this.limit; + } + + // return number of items in queue + size() { + return this.queue.length; + } + + // return -1 if item not in queue, otherwise integer representing + // how far it is from the front + search(target) { + return this.queue.indexOf(target); + } + + // print contents of queue: do not return the queue itself! + print() { + console.log(this.queue.join(' -> ')); + } +} + +if (require.main === module) { + // add your own tests in here + const queue = new Queue(); + + console.log('size', queue.size()); + console.log('is empty?', queue.isEmpty()); + console.log('is full?', queue.isFull()); + console.log('find 5', queue.search(5)); + console.log('peek while empty', queue.peek()); + + for (let i = 0; i < 5; ++i) { + queue.enqueue(i); + } + + console.log('ADD ITEMS 0 TO 4'); + console.log('size', queue.size()); + console.log('is empty?', queue.isEmpty()); + console.log('is full?', queue.isFull()); + console.log('find 3', queue.search(3)); + console.log('peek', queue.peek()); + queue.print(); + + for (let i = 5; i < 10; ++i) { + queue.enqueue(i); + } + + console.log('ADD ITEMS 5 TO 9'); + console.log('size', queue.size()); + console.log('is empty?', queue.isEmpty()); + console.log('is full?', queue.isFull()); + console.log('find 3', queue.search(3)); + console.log('peek', queue.peek()); + queue.print(); + + console.log('dequeue', queue.dequeue()); + console.log('size', queue.size()); + console.log('is empty?', queue.isEmpty()); + console.log('is full?', queue.isFull()); + console.log('peek', queue.peek()); + queue.print(); + + console.log('GENERATE ERROR'); + queue.enqueue(10); + + try { + queue.enqueue(11); + } catch(err) { + console.log(err); + queue.print(); + } +} + +module.exports = Queue; diff --git a/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/solutions/queue.rb b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/solutions/queue.rb new file mode 100644 index 00000000..1ae39aa9 --- /dev/null +++ b/06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class/solutions/queue.rb @@ -0,0 +1,106 @@ +class Queue + attr_reader :limit + + def initialize + @queue = [] + # this is an arbitrary value to make testing easier + # 1,024 would be way too high to test! + @limit = 10 + end + + # add item to rear of queue if not full + # if full, throw error + def enqueue(item) + raise 'Queue is full' if isFull? + + @queue.push(item) + end + + # remove item from front of queue and return it + def dequeue + @queue.shift + end + + # return item at front of queue without removing it + def peek + @queue.first + end + + # return true if queue is empty, otherwise false + def isEmpty? + size.zero? + end + + # return true if queue is full, otherwise false + def isFull? + size === @limit + end + + # return number of items in queue + def size + @queue.length + end + + # return -1 if item not in queue, otherwise integer representing + # how far it is from the front + def search(target) + @queue.index(target) || -1 + end + + # print contents of queue: do not return the queue itself! + def print + puts @queue.join(' -> ') + end +end + +if __FILE__ == $PROGRAM_NAME + queue = Queue.new; + + puts "'size': #{queue.size}" + puts "'is empty?': #{queue.isEmpty?}" + puts "'is full?': #{queue.isFull?}" + puts "'find 5': #{queue.search(5)}" + puts "'peek while empty': #{queue.peek}" + + (0...5).each do |num| + queue.enqueue(num) + end + + puts "'ADD ITEMS 0 TO 4'" + puts "'size': #{queue.size}" + puts "'is empty?': #{queue.isEmpty?}" + puts "'is full?': #{queue.isFull?}" + puts "'find 3': #{queue.search(3)}" + puts "'peek': #{queue.peek}" + queue.print + + (5..9).each do |num| + queue.enqueue(num) + end + + puts 'ADD ITEMS 5 TO 9' + puts "'size': #{queue.size}" + puts "'is empty?': #{queue.isEmpty?}" + puts "'is full?': #{queue.isFull?}" + puts "'find 3': #{queue.search(3)}" + puts "'peek': #{queue.peek}" + queue.print + + puts "'dequeue': #{queue.dequeue}" + puts "'size': #{queue.size}" + puts "'is empty?': #{queue.isEmpty?}" + puts "'is full?': #{queue.isFull?}" + puts "'peek': #{queue.peek}" + queue.print + + puts'GENERATE ERROR' + queue.enqueue(10) + + begin + queue.enqueue(11) + rescue StandardError => e + puts e.message + queue.print + end +end + diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/.gitignore b/06-week-5--big-o-continued/02-day-5--implement-a-set/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/README.md b/06-week-5--big-o-continued/02-day-5--implement-a-set/README.md new file mode 100644 index 00000000..d87a94ec --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/README.md @@ -0,0 +1,117 @@ +# Day 5: What Is a Set? + +A Set is a data structure that can store any number of unique values or elements. That means there are no repeating elements in a Set. Many languages provide Sets as part of the core language, but today, we'll be building our own MySet class by using a Hash/Object as the underlying data structure. We've chosen a Hash/Object because that data structure only allows unique keys. + +``` +// Convert an Array to a Set + +array = [1, 3, 1, 2] +set = new Set from array +=> {1, 3, 2} +``` + +## Implement the MySet class + +We have already declared a class called `MySet` in the starter files and provided the underlying data structure: a Hash/Object called `data`. You'll need to build out all of the methods. Do not worry about the ordering of items in the Set. They can be in any order. + +Be aware that these methods may go by different names in different languages. This is one way to implement a Set, there are others! We are basing our's on JavaScript's Set class. + +### Initialization, e.g. `constructor(iterable)` or `#initialize(iterable)` + +We should be able to initialize a new empty MySet, or one with an iterable, such as an Array or String. When a new MySet is initialized with an Array or String, only its unique contents should be added to MySet. Use a value of `true` for each key: `{ dog: true }`. + +``` +// Values in Hash omitted for conciseness +new MySet() +=> MySet data = {} +new MySet(Array [1, 2, 1, 3]) +=> MySet data = {1, 2, 3} +new MySet(String 'hello') +=> MySet data = {'h', 'e', 'l', 'o'} +``` + +An error should be thrown if a user tries to initialize a new MySet with anything other than nothing, a String or an Array. + +### `size` + +Returns the number of items in the MySet instance. + +### `add(item)` + +Add an item to a MySet instance. Remember, only unique items should exist in MySet. When adding an item, the item is added as is. Return the MySet instance. + +``` +my_set = new MySet() +my_set.add('caat') +=> MySet data = { 'caat' } + +next_set = new MySet('doooog') +next_set.add('caarp') +=> MySet data = { 'd', 'o', 'g', 'caarp' } +next_set.add(12) +=> MySet data = { 'd', 'o', 'g', 'caarp', 12 } +``` + +**_Note on JS: Arrays cannot be keys for JS Objects. They will be converted to strings by default like so: `[1, 2] => '1,2'`. Don't worry about this. We'll ensure our tests allow for this. For now, just be aware that actual Sets most certainly can handle Arrays!_** + +### `delete(item)` + +Removes the item from the MySet instance. If the removal was successful, return `true`. If the item was not removed (i.e. it wasn't in there), return `false`. + +``` +my_set = new MySet('aabb') +my_set.delete('a') +=> true +my_set.delete('z') +=> false +``` + +**_Note on JS: Don't worry about handling Arrays here!_** + +### `has(item)` + +If the item is in the MySet instance, return `true`, otherwise `false`. + +**_Note on JS: Don't worry about handling Arrays here!_** + +### `entries` + +Returns an Array containing all of the values in the MySet instance. + +``` +my_set = new MySet('aabb') +my_set.entries() +=> ['a', 'b'] +``` + +**_Note on JS: Don't worry about handling Arrays (which were meant to be keys in `this.data`) here!_** + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/javascript/my_set.js b/06-week-5--big-o-continued/02-day-5--implement-a-set/javascript/my_set.js new file mode 100644 index 00000000..21032e5e --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/javascript/my_set.js @@ -0,0 +1,47 @@ +class MySet { + // throw an error if called with anything other than string, array or nothing + // if an iterable is provided only its unique values should be in data + // strings and arrays will need to be broken down by their elements/characters + constructor(iterable) { + this.data = {}; + } + + // return number of elements in MySet + size() { + + } + + // add an item to MySet as is + // don't worry about arrays here! + // return the MySet instance + add(item) { + + } + + // delete an item from MySet + // don't worry about arrays here! + // return true if successful, otherwise false + delete(item) { + + } + + // return true if in MySet, otherwise false + // don't worry about arrays here! + has(item) { + + } + + // return data as an array + // don't worry about arrays here! + entries() { + + } +} + +if (require.main === module) { + // add your own tests in here + +} + +module.exports = MySet; + diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/javascript/package.json b/06-week-5--big-o-continued/02-day-5--implement-a-set/javascript/package.json new file mode 100644 index 00000000..6874fef6 --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "myset", + "version": "1.0.0", + "description": "set class", + "main": "my_set.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/javascript/tests/my_set.test.js b/06-week-5--big-o-continued/02-day-5--implement-a-set/javascript/tests/my_set.test.js new file mode 100644 index 00000000..1ae1deb4 --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/javascript/tests/my_set.test.js @@ -0,0 +1,57 @@ +const MySet = require('../my_set'); + +let emptySet = new MySet(); +let dataSet = new MySet('hello'); + +test('initialize(iterator = nil) raises an exception if the argument is not nothing, an array, or a string', () => { + expect(() => { + new MySet(1); + }).toThrow(Error); +}); + +test('initialize(iterator = nil) populates @data with keys when given an Array or String', () => { + const arraySet = new MySet([1, 2, 1, 3]); + const sortedArraySet = Object.keys(arraySet.data).sort(); + const sortedDataSet = Object.keys(dataSet.data).sort(); + + expect(sortedArraySet).toEqual(["1", "2", "3"]); + expect(sortedDataSet.join('')).toBe('ehlo'); +}); + +test('size() returns the number of items in the set', () => { + expect(emptySet.size()).toBe(0); + expect(dataSet.size()).toBe(4); +}); + +test('add(item) adds the item to the set', () => { + emptySet.add('cat'); + + expect(emptySet.data['cat']).toBe(true); +}); + +test('add(item) returns the instance of MySet', () => { + expect(emptySet.add('cat')).toBe(emptySet); +}); + +test('has(item) returns true if the item is in the set', () => { + expect(dataSet.has('h')).toBe(true); +}); + +test('has(item) returns false if the item is not in the set', () => { + expect(dataSet.has('z')).toBe(false); +}); + +test('delete(item) returns true upon successful deletion', () => { + expect(dataSet.delete('h')).toBe(true); +}); + +test('delete(item) returns false if item was not found', () => { + expect(dataSet.delete('iiii')).toBe(false); +}); + +test('entries() returns an array of all of the unique keys in data', () => { + const test = new MySet('aaabb'); + const letters = Object.keys(test.data).sort(); + + expect(letters.join('')).toBe('ab'); +}); diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/.rspec b/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/Gemfile b/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/my_set.rb b/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/my_set.rb new file mode 100644 index 00000000..09507d31 --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/my_set.rb @@ -0,0 +1,37 @@ +class MySet + attr_reader :data + # throw an error if called with anything other than string, array or nothing + # if an iterable is provided only its unique values should be in data + # strings and arrays will need to be broken down by their elements/characters + def initialize(iterable = nil) + @data = {} + end + + # return number of elements in MySet + def size + end + + # add an item to MySet as is + # return the MySet instance + def add(item) + end + + # delete an item from MySet + # return true if successful otherwise false + def delete(item) + end + + # return true if in MySet, otherwise false + def has(item) + end + + # return data as an array + def entries + end +end + +if __FILE__ == $PROGRAM_NAME + # Don't forget to add your own! +end + + diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/spec/my_set_spec.rb b/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/spec/my_set_spec.rb new file mode 100644 index 00000000..82f948d1 --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/spec/my_set_spec.rb @@ -0,0 +1,65 @@ +require './my_set' + +RSpec.describe 'MySet' do + let(:empty_set) { MySet.new } + let(:data_set) { MySet.new('hello') } + + context '#initialize(iterator = nil)' do + it 'raises an exception if the argument is not nothing, an array, or a string' do + expect { MySet.new(1) }.to raise_exception + end + + it 'populates @data with keys when given an Array or String' do + array_set = MySet.new([1, 2, 1, 3]) + string_set = MySet.new('hello') + + expect(array_set.data.keys.sort).to eq([1, 2, 3].sort) + expect(string_set.data.keys.sort).to eq('helo'.chars.sort) + end + end + + context '#size' do + it 'returns the number of items in the set' do + expect(empty_set.size).to eq(0) + expect(data_set.size).to eq(4) + end + end + + context '#add(item)' do + it 'adds the item to the set' do + empty_set.add('cat') + expect(empty_set.data['cat']).to eq(true) + end + + it 'returns the instance of MySet' do + expect(empty_set.add('cat')).to eq(empty_set) + end + end + + context '#has(item)' do + it 'returns true if the item is in the set' do + expect(data_set.has('h')).to be true + end + + it 'returns false if the item is not in the set' do + expect(data_set.has('z')).to be false + end + end + + context '#delete(item)' do + it 'returns true upon successful deletion' do + expect(data_set.delete('l')).to be true + end + + it 'returns false if the item could not be deleted' do + expect(data_set.delete('z')).to be false + end + end + + context '#entries' do + it 'returns an array of all of the unique keys in data' do + expect(data_set.entries.sort).to eq('helo'.chars.sort) + expect(empty_set.entries).to eq([]) + end + end +end diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/spec/spec_helper.rb b/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/solutions/my_set.js b/06-week-5--big-o-continued/02-day-5--implement-a-set/solutions/my_set.js new file mode 100644 index 00000000..f9e1a9cc --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/solutions/my_set.js @@ -0,0 +1,90 @@ +class MySet { + // throw an error if called with anything other than string, array or nothing + // if an iterable is provided only its unique values should be in data + // strings and arrays will need to be broken down by their elements/characters + constructor(iterable) { + if (!(iterable === undefined || + Array.isArray(iterable) || + typeof iterable === 'string')) { + throw new Error('MySet only accepts iterables or nothing on initialization!'); + } + + this.data = {}; + + if (iterable) { + for (const el of iterable) { + this.data[el] = true; + } + } + } + + // return number of elements in MySet + size() { + return this.entries().length; + } + + // add an item to MySet as is + // don't worry about arrays here! + add(item) { + this.data[item] = true; + return this; + } + + // delete an item from MySet + // don't worry about arrays here! + delete(item) { + if (this.has(item)) { + delete this.data[item]; + return true; + } + + return false; + } + + // return true if in MySet, otherwise false + // don't worry about arrays here! + has(item) { + return !!this.data[item]; + } + + // return data as an array + // don't worry about arrays here! + entries() { + return Object.keys(this.data); + } +} + +if (require.main === module) { + let mySet = new MySet(); + console.log('empty', mySet); + console.log('size', mySet.size()); + console.log('entries', mySet.entries()); + + mySet = new MySet([1, 2, 1, 3]); + console.log('with array [1, 2, 1, 3]', mySet); + console.log('size', mySet.size()); + console.log('entries', mySet.entries()); + + mySet = new MySet('hello'); + console.log('with string hello', mySet); + console.log('size', mySet.size()); + console.log('entries', mySet.entries()); + + console.log(''); + console.log('ADD STUFF'); + console.log(mySet.add('adding')); + console.log(mySet.add(5)); + + console.log(''); + console.log('HAS STUFF'); + console.log(mySet.has('adding')); + console.log(mySet.has(10000)); + + console.log(''); + console.log('DELETE STUFF'); + console.log(mySet.delete('adding')); + console.log(mySet.delete(10000)); +} + +module.exports = MySet; + diff --git a/06-week-5--big-o-continued/02-day-5--implement-a-set/solutions/my_set.rb b/06-week-5--big-o-continued/02-day-5--implement-a-set/solutions/my_set.rb new file mode 100644 index 00000000..5c16300d --- /dev/null +++ b/06-week-5--big-o-continued/02-day-5--implement-a-set/solutions/my_set.rb @@ -0,0 +1,82 @@ +class MySet + attr_reader :data + # throw an error if called with anything other than string, array or nothing + # if an iterable is provided only its unique values should be in data + # strings and arrays will need to be broken down by their elements/characters + def initialize(iterable = nil) + raise 'MySet only accepts iterables or nothing on initialization!' unless + iterable.nil? || iterable.kind_of?(Array) || iterable.kind_of?(String) + + @data = {} + + unless iterable.nil? + items = iterable.kind_of?(String) ? iterable.split('') : iterable + + items.each { |el| @data[el] = true } + end + end + + # return number of elements in MySet + def size + entries.length + end + + # add an item to MySet as is + # return the MySet instance + def add(item) + @data[item] = true + self + end + + # delete an item from MySet + # return true if successful otherwise false + def delete(item) + !!@data.delete(item) + end + + # return true if in MySet, otherwise false + def has(item) + !!@data[item] + end + + # return data as an array + def entries + @data.keys + end +end + +if __FILE__ == $PROGRAM_NAME + mySet = MySet.new + puts "'empty', #{mySet.data}" + puts "'size', #{mySet.size}" + puts "'entries', #{mySet.entries}" + + mySet = MySet.new([1, 2, 1, 3]) + puts "'with array [1, 2, 1, 3]', #{mySet.data}" + puts "'size', #{mySet.size}" + puts "'entries', #{mySet.entries}" + + mySet = MySet.new('hello') + puts "'with string hello', #{mySet.data}" + puts "'size', #{mySet.size}" + puts "'entries',#{mySet.entries}" + + puts '' + puts 'ADD STUFF' + puts mySet.add('adding') + print mySet.data + puts + puts mySet.add(5) + print mySet.data + puts + + puts '' + puts 'HAS STUFF' + puts mySet.has('adding') + puts mySet.has(10000) + + puts '' + puts 'DELETE STUFF' + puts mySet.delete('adding') + puts mySet.delete(10000) +end diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/.gitignore b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/README.md b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/README.md new file mode 100644 index 00000000..271cd566 --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/README.md @@ -0,0 +1,369 @@ +# Days 1-2: Implement a Linked List + +Today we'll be implementing a Linked List, more specifically a singly-linked list, or simple linked list (it goes by several names). A Linked List consists of Nodes, which are connected to one another. It is similar to an Array in that it consists of elements, or Nodes, which are in a specific order. + +![Linked List](./linked_list.png) + +We may choose to use Linked Lists in place of other data structures when we need fast insertion and deletion of data. The Node at the beginning of the list is called the `head`, while the Node at the end is called the `tail`. + +## What Is a Node? + +A Node is an object that has two attributes: `value` and `next`. The `value` stores the data that we might be interested in retrieving, such as an Integer, Array, String, or some other object. The `next` attribute points to the next Node, i.e. its value is the next Node. + +``` +array = [1, 2] +head = new Node(value: 1) +nextNode = new Node(value: 2) +head.next = nextNode +// head -> nextNode +``` + +The very last Node in the Linked List will point to nothing, so its `next` value may be `undefined`, `null`, `nil`, etc. It depends on the language being used and on the implementation of the Node class. + +## How Do We Track the Starting Node of a Linked List? + +It is common to declare another class called LinkedList when using Linked Lists. The LinkedList class stores the `head` Node, aka the start of the list. As long as we know where the list starts, we can always traverse it, which is why this class contains so little data! + +``` +head = new Node(value: 'i am the beginning!') +list = new LinkedList(head) +``` + +> A note on online algorithm challenges: When completing challenges online, such as through LeetCode, you might be provided with only the head Node, rather than a LinkedList class. + +## How Do We Visit Each Node? + +Iterables, like Arrays, provide instance methods that allow us to traverse them, and we can also traverse them using loops and indices. Linked Lists are a little different. Nodes do not have indexes: they just point to the next Node, or to nothing if it's the last node in the list (the `tail`). This means we have to go to the `head` Node, ask it what's next, go there, and repeat! + +Here's an example using people waiting in a queue to get into a club to dance to some sick beats! In order, the queue consists of Janzz, Murray, and Lakshmi. + +``` +You: "Hey Janzz! Who's next?" +Janzz: "Murray" +You: "Hey Murray! Who's next?" +Murray: "Lakshmi" +You: "Hey Lakshmi! Who's next?" +Lakshmi: [awkward silence] +``` + +We're not giving you the code here because we want you to figure out how to traverse a Linked List for yourself later on. We believe in you! + +## Summary + +A LinkedList is a data structure consisting of Nodes. The `head` Node denotes the start of the list. Each Node has two attributes: `value` and `next`. `value` stores the data we might be interested in retrieving, while `next` points to the next Node in the list. The last Node, called the `tail`, in the list points to nothing (e.g. `next` is `null`), and that's how we know it's the end! + +We can use another class called LinkedList to track the `head` of the list. + +## Implement a Linked List + +Please note that in Ruby we'll be using `next_node` instead of `next` as the `Node` attribute. This is to avoid confusing syntax, since next is a reserved keyword in Ruby. It also leads to confusing syntax highlighting as a result. In short, wherever you see `next`, think `next_node` for Ruby. + +### 1. Declare the `Node` Class + +A `Node` has two attributes: `value` and `next`. `value` can store anything, while `next` will either point to the next `Node` or to nothing. + +Provide default values for both `value` and `next` so that a new Node can be instantiated without any arguments. In JS, the default values for both should be `null`, while they should both be `nil` in Ruby. Choose a comparable value if coding in other languages. + +When instantiating a new `Node`, the arguments in order should be: `value`, `next`. + +``` +node = new Node() +node.value +=> null or nil +node.next +=> null or nil + +node = new Node('hi', new Node('bye')) +node.value +=> 'hi' +node.next +=> Instance of Node with value of 'bye' +``` + +### 2. Declare the `LinkedList` Class + +The `LinkedList` class tracks the `head` of the list, so we know where it begins. It should have one attribute: `head`. Provide a default value for `head` of `null` or `nil`, or some other falsy value. + +``` +node = new Node() +list = new LinkedList(node) +list.head +=> Instance of Node + +emptyList = new LinkedList() +list.head +=> null or nil +``` + +### 3. Spend a Few Minutes Playing With Your Linked List + +See if you can recreate the following Arrays as Linked Lists using your classes, where the 0th element denotes the `head` of the list: + +``` +characters = ['Hamtaro', 'Walter White'] +drinks = ['Coffee', 'Manhattan', 'Brandy Sour'] +``` + +You can test this manually like so: + +``` +head = new Node('hi again', new Node('but why?')) +list = new LinkedList(head) +print list.head.value +print list.head.next.value +print list.head.next.next +``` + +### 4. Add `iterate` Method to `LinkedList` + +For now, we'll build part of the `iterate` method but not all of it. The `iterate` method traverses the entire `LinkedList`. To ensure that it's working, we'll print the value of each `Node`. Later we'll remove this functionality and update the method to take a callback. + +Remember, the `head` is the first `Node` in the list, and the next one is stored in its `next` attribute. We can go to each `Node` by visiting all of the `next`s. When `next` is equal to a falsy value, such as `null` or `nil`, we've reached the end of the list. At the end of the iteration, return the `head`. + +``` +head = new Node('hi again', new Node('but why?')) +list = new LinkedList(head) +list.iterate() +=> 'hi again' +=> 'but why?' +=> Node with value 'hi again' +``` + +### 5. Modify the `iterate(callback)` Method to Take a Callback + +Change the `iterate` method, so that it takes a callback (a function) as an argument. + +Replace the print statements in the `iterate` method with a call to the callback. When calling the callback, provide the current Node as an argument to the callback. You can test if this is working by calling `iterate` on the list with a callback that prints the value of each Node. + +Hint: Rubyists might be interested in learning about passing blocks and using `yield`. + +``` +function printNode(node): + print node.value + +head = new Node('hi again', new Node('but why?')) +list = new LinkedList(head) +list.iterate(printNode) +=> 'hi again' +=> 'but why?' +=> Node with value 'hi again' +``` + +### 6. Add `print` method to `LinkedList` + +The `print` method should print each Node value on its own line. Use the `iterate` method in the `print` method. + +``` +head = new Node('hi again', new Node('but why?')) +list = new LinkedList(head) +list.print() +=> 'hi again' +=> 'but why?' +``` + +### 7. Add `find(target)` method to `LinkedList` + +The `find` method searches for a `Node` with the `target` value. If the `Node` is found, it returns that `Node`. Otherwise, it returns a falsy value such as `null` or `nil`. Use the `iterate` method to keep your code short and DRY. + +``` +head = new Node('hi again', new Node('but why?')) +list = new LinkedList(head) +list.find('but why?') +=> Node with value 'but why?' + +list.find('tell me secrets') +=> null or nil, etc. +``` + +### 8. Add `addFirst(node)` method to `LinkedList` + +`addFirst` takes a Node as an argument and adds it as the `head` of the Linked List. No existing Nodes are removed. + +This method adds only 1 Node to the list. + +``` +head = new Node('hi again', new Node('but why?')) +list = new LinkedList(head) +list.addFirst(new Node('I am first now')) +list.print() +=> 'I am first now' +=> 'hi again' +=> 'but why?' +``` + +### 9. Add `addLast(node)` method to `LinkedList` + +`addLast` takes a Node as an argument and adds it at the end of the Linked List (i.e. it will be the tail). No existing Nodes are removed. The `iterate` method can help you here. + +This method adds only 1 Node to the list. + +``` +head = new Node('hi again', new Node('but why?')) +list = new LinkedList(head) +list.addLast(new Node('I am last')) +list.print() +=> 'hi again' +=> 'but why?' +=> 'I am last' +``` + +### 10. Add `removeFirst` method to `LinkedList` + +`removeFirst` removes the first (head) Node in the list and returns the node that was removed. + +Hint: Try not to overthink this. Removing the head takes one line of code. You'll need a little bit more code to handle returning the node that was removed, however. + +``` +head = new Node('hi again', new Node('but why?')) +list = new LinkedList(head) +list.removeFirst() +=> Node with value 'hi again' +list.print() +=> 'but why?' +``` + +### 11. Add `removeLast` method to `LinkedList` + +`removeLast` removes the last (tail) Node in the list and returns the removed Node. + +Hint: The `iterate` method might be helpful here. + +``` +head = new Node('hi again', new Node('but why?')) +list = new LinkedList(head) +list.removeLast() +=> Node with value 'but why?' +list.print() +=> 'hi again' +``` + +### 12. Add `replace(index, node)` to `LinkedList` + +Replace the Node at the given `index` with the given `node`. `replace` should work on all Node indexes. Nodes are zero-indexed. + +Don't worry about handling invalid indexes, such as -1 or those that go beyond the size of the list. + +Return the inserted Node. + +Hint: The `iterate` method might be helpful here. You may wish to modify it by adding the ability to count, or you can declare the count within `replace` and update it in the callback passed to `iterate`. Or you can create an `iterate_with_count` method and use that (and that method can call the `iterate` method). So many options! + +``` +head = new Node('one', new Node('two', new Node('three'))) +list = new LinkedList(head) +list.replace(0, '1') +=> Node with value '1' +// list is now '1' -> 'two' -> 'three' + +list.replace(1, '2') +=> Node with value '2' +// list is now '1' -> '2' -> 'three' + +list.replace(2, '3') +=> Node with value '3' +// list is now '1' -> '2' -> '3' +``` + +### 13. Add `insert(index, node)` to `LinkedList` + +Insert the given `node` at the given `index` in the `LinkedList`. No nodes should be removed or replaced! This method inserts only 1 Node into the list. + +Ensure you can handle all valid `index` values: 0 to last index + 1 in list. Don't worry about invalid index values. + +Hint: `iterate` may be helpful once more. + +``` +head = new Node('one', new Node('two', new Node('three'))) +list = new LinkedList(head) +list.insert(1, new Node('inserted at 1')) +// list is now 'one' -> 'inserted at 1' -> 'two' -> 'three' +``` + +``` +head = new Node('one', new Node('two', new Node('three'))) +list = new LinkedList(head) +list.insert(0, new Node('inserted at 0')) +// list is now 'inserted at 0' -> 'one' -> 'two' -> 'three' +``` + +``` +head = new Node('one', new Node('two', new Node('three'))) +list = new LinkedList(head) +list.insert(3, new Node('inserted at 3')) +// list is now 'one' -> 'two' -> 'three' -> 'inserted at 3' +``` + +### 14. Add `remove(index)` to `LinkedList` + +Remove the `Node` at the given `index` and return the removed `Node`. Don't worry about invalid indices, such as -1 or those that go beyond the size of the list. + +Hint: Good ol' `iterate`...again! + +``` +head = new Node('one', new Node('two', new Node('three'))) +list = new LinkedList(head) + +list.remove(1) +=> Node with value 'two' +// list is now 'one' -> 'three' + +list.remove(1) +=> Node with value 'three' +// list is now 'one' + +list.remove(0) +=> Node with value 'one' +// list is empty :( +``` + +### 15. Add `clear` to `LinkedList` + +Clear the Linked List. + +``` +head = new Node('one', new Node('two', new Node('three'))) +list = new LinkedList(head) + +list.clear() +list.print() +// nothing happens because it's empty +list.head +=> null or nil +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, and optionally explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/javascript/linked_list.js b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/javascript/linked_list.js new file mode 100644 index 00000000..3276629c --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/javascript/linked_list.js @@ -0,0 +1,75 @@ +class LinkedList { + constructor() { + + } + + iterate() { + + } + + // print each node's value on its own line + // use your iterate method to be DRY! Don't get caught in the code rain, brrr. + print() { + + } + + // find the node with the target value and return it + // if not found return null, use your iterate method to be DRY! + find(target) { + + } + + // add the node to the start of the list, no nodes should be removed + addFirst(node) { + + } + + // add node to end of list, no nodes should be removed + // you may wish to use the iterate method + addLast(node) { + + } + + // remove the first Node in the list and update head + // and return the removed node + removeFirst() { + + } + + // remove the tail node, iterate may be helpful + // return the node you just removed + removeLast() { + + } + + // replace the node at the given index with the given node + replace(idx, node) { + + } + + // insert the node at the given index + // no existing nodes should be removed or replaced + insert(idx, node) { + + } + + // remove the node at the given index, and return it + remove(idx) { + + } +} + +class Node { + constructor() { + + } +} + +if (require.main === module) { + // add your own tests in here + +} + +module.exports = { + Node, LinkedList +}; diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/javascript/package.json b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/javascript/package.json new file mode 100644 index 00000000..60d1adf6 --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "linked_list", + "version": "1.0.0", + "description": "linked list", + "main": "linked_list.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/javascript/tests/linked_list.test.js b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/javascript/tests/linked_list.test.js new file mode 100644 index 00000000..5e83a4b0 --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/javascript/tests/linked_list.test.js @@ -0,0 +1,292 @@ +const { Node, LinkedList } = require("../linked_list"); + +describe("Node", () => { + const makeNode = () => new Node("hi", "there"); + const emptyNode = new Node(); + + test("sets an attribute called value to the argument on initialization", () => { + expect(makeNode().value).toBe("hi"); + }); + + test("sets an attribute called next to the argument on initialization", () => { + expect(makeNode().next).toBe("there"); + }); + + test("sets an attribute called value to null on initialization when there is no argument", () => { + expect(emptyNode.value).toBe(null); + }); + + test("sets an attribute called next to null on initialization when there is no argument", () => { + expect(emptyNode.next).toBe(null); + }); +}); + +describe("LinkedList", () => { + const nodeFour = new Node("four"); + const nodeThree = new Node("three", nodeFour); + const nodeTwo = new Node("two", nodeThree); + const nodeOne = new Node("one", nodeTwo); + const justOne = new Node("just one"); + + let emptyList = new LinkedList(); + let oneItemList = new LinkedList(justOne); + let linkedList = new LinkedList(nodeOne); + + const consoleLog = console.log; + const LLValues = ["one", "two", "three", "four"]; + + beforeEach(() => { + console.log = consoleLog; + + const values = ["one", "two", "three", "four", "just one"]; + const nodes = [nodeOne, nodeTwo, nodeThree, nodeFour, justOne]; + + nodes.forEach((node, i) => { + node.value = values[i]; + }); + + emptyList.head = null; + + oneItemList.head = justOne; + justOne.next = null; + + linkedList.head = nodeOne; + nodeOne.next = nodeTwo; + nodeTwo.next = nodeThree; + nodeThree.next = nodeFour; + nodeFour.next = null; + }); + + test("head is null if no argument provided on initialization", () => { + expect(emptyList.head).toBe(null); + }); + + test("head is set to the argument if provided on initialization", () => { + expect(oneItemList.head).toBe(justOne); + }); + + describe("iterate()", () => { + test("iterate() calls the provided callback on every node with the node as an argument to the callback", () => { + const values = []; + linkedList.iterate((node) => values.push(node)); + + expect(values.length).toBe(4); + expect(values[3]).toBe(nodeFour); + }); + + test("iterate() can handle an empty list", () => { + const values = []; + emptyList.iterate((node) => values.push(node)); + + expect(values.length).toBe(0); + }); + + test("iterate() can handle a list with one node", () => { + const values = []; + oneItemList.iterate((node) => values.push(node)); + + expect(values.length).toBe(1); + expect(values[0]).toBe(justOne); + }); + }); + + describe("print()", () => { + let consoleOutput = []; + const mockOutput = output => consoleOutput.push(output); + beforeEach(() => (console.log = mockOutput)); + afterEach(() => (consoleOutput = [])); + + test("prints nothing at all when the list is empty", () => { + emptyList.print(); + + expect(consoleOutput.length).toBe(0); + }); + + test("prints a single line for a list with one node", () => { + oneItemList.print(); + + expect(consoleOutput[0]).toBe(justOne.value); + }); + + test("prints all nodes", () => { + linkedList.print(); + + expect(consoleOutput).toEqual(LLValues); + }); + }); + + describe("find()", () => { + test("returns null when the list is empty", () => { + expect(emptyList.find(4)).toBe(null); + }); + + test("returns the correct Node when a Node with that value is in the list", () => { + expect(linkedList.find("two")).toBe(nodeTwo); + }); + + test("returns null when the value is not in the list", () => { + expect(linkedList.find("nope")).toBe(null); + }); + }); + + describe("addFirst()", () => { + test("adds the given Node to the beginning of the list without removing any", () => { + const newNode = new Node("I'm new"); + oneItemList.addFirst(newNode); + + expect(oneItemList.head).toBe(newNode); + expect(oneItemList.head.next).toBe(justOne); + }); + + test("adds the given node to an empty list", () => { + const newNode = new Node("I'm new"); + emptyList.addFirst(newNode); + + expect(emptyList.head).toBe(newNode); + expect(emptyList.head.next).toBe(null); + }); + }); + + describe("addLast()", () => { + test("adds the given Node to the end of the list without removing any", () => { + const newNode = new Node("I'm new"); + oneItemList.addLast(newNode); + + expect(oneItemList.head).toBe(justOne); + expect(oneItemList.head.next).toBe(newNode); + }); + + test("adds the given node to an empty list", () => { + const newNode = new Node("I'm new"); + emptyList.addLast(newNode); + + expect(emptyList.head).toBe(newNode); + expect(emptyList.head.next).toBe(null); + }); + }); + + describe("removeFirst()", () => { + test("removes and returns the head of the list", () => { + expect(linkedList.removeFirst()).toBe(nodeOne); + }); + + test("updates the head to the correct Node", () => { + linkedList.removeFirst(); + + expect(linkedList.head).toBe(nodeTwo); + expect(linkedList.head.next).toBe(nodeThree); + }); + + test("does not produce an error when called on an empty list", () => { + expect(() => emptyList.removeFirst()).not.toThrow(Error); + }); + }); + + describe("removeLast()", () => { + test("removes and returns the tail of the list", () => { + expect(linkedList.removeLast()).toBe(nodeFour); + expect(linkedList.head.next.next.next).toBe(null); + }); + + test("makes the node before the old tail the new tail", () => { + linkedList.removeLast(); + + expect(linkedList.head.next.next).toBe(nodeThree); + }); + + test("does not produce an error when called on an empty list", () => { + expect(() => emptyList.removeLast()).not.toThrow(Error); + }); + }); + + describe("replace()", () => { + test("returns the inserted node", () => { + const newNode = new Node("replacing"); + + expect(linkedList.replace(0, newNode)).toBe(newNode); + }); + + test("replaces the correct nodes at the correct indexes", () => { + const zero = new Node("replace at 0"); + const one = new Node("replace at 1"); + const two = new Node("replace at 2"); + const three = new Node("replace at 3"); + + [zero, one, two, three].forEach((node, i) => { linkedList.replace(i, node) }); + + expect(linkedList.head).toBe(zero); + expect(linkedList.head.next).toBe(one); + expect(linkedList.head.next.next).toBe(two); + expect(linkedList.head.next.next.next).toBe(three); + expect(linkedList.head.next.next.next.next).toBe(null); + }); + }); + + describe("insert()", () => { + test("can insert a node at the beginning of the list", () => { + const newNode = new Node("hi"); + oneItemList.insert(0, newNode); + + expect(oneItemList.head).toBe(newNode); + expect(oneItemList.head.next).toBe(justOne); + expect(oneItemList.head.next.next).toBe(null); + }); + + test("can insert a node at the very end of the list (making a new tail)", () => { + const newNode = new Node("hi"); + oneItemList.insert(1, newNode); + + expect(oneItemList.head).toBe(justOne); + expect(oneItemList.head.next).toBe(newNode); + expect(oneItemList.head.next.next).toBe(null); + }); + + test("can insert a node in the middle of a list", () => { + const newNode = new Node("hi"); + linkedList.insert(2, newNode); + + expect(linkedList.head.next).toBe(nodeTwo); + expect(linkedList.head.next.next).toBe(newNode); + expect(linkedList.head.next.next.next).toBe(nodeThree); + }); + }); + + describe("remove()", () => { + let consoleOutput = []; + const mockOutput = output => consoleOutput.push(output); + beforeEach(() => (console.log = mockOutput)); + afterEach(() => (consoleOutput = [])); + + test("returns the removed node", () => { + expect(linkedList.remove(3)).toBe(nodeFour); + }); + + test("removes the correct node", () => { + const removed = linkedList.remove(1); + let found = false; + + linkedList.iterate(node => { + if (node === removed) { + founds = true; + } + }); + + expect(found).toBe(false); + }); + + test("keeps the list intact when a node is removed", () => { + linkedList.remove(2); + linkedList.print(); + + expect(consoleOutput).toEqual(['one', 'two', 'four']); + }); + }); + + describe("clear()", () => { + test("empties the list", () => { + linkedList.clear(); + + expect(linkedList.head).toBe(null); + }); + }); +}); diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/linked_list.png b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/linked_list.png new file mode 100644 index 00000000..0c241dc5 Binary files /dev/null and b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/linked_list.png differ diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/.rspec b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/Gemfile b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/linked_list.rb b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/linked_list.rb new file mode 100644 index 00000000..758311c6 --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/linked_list.rb @@ -0,0 +1,67 @@ +class LinkedList + attr_accessor :head + + def initialize() + end + + def iterate() + end + + # print each node's value on its own line + # use your iterate method to be DRY! Don't get caught in the code rain, brrr. + def print + end + + # find the node with the target value and return it + # if not found return nil, use your iterate method to be DRY! + def find(target) + end + + # add the node to the start of the list, no nodes should be removed + def add_first(node) + end + + # add node to end of list, no nodes should be removed + # you may wish to use the iterate method + def add_last(node) + end + + # remove the first Node in the list and update head + # and return the removed node + def remove_first + end + + # remove the tail node, iterate may be helpful + # return the node you just removed + def remove_last + end + + # replace the node at the given index with the given node + def replace(idx, node) + end + + # insert the node at the given index + # no existing nodes should be removed or replaced + def insert(idx, node) + end + + # remove the node at the given index, and return it + def remove(idx) + end +end + +class Node + # next is a reserved word in Ruby, so we'll use next_node instead + # just to keep things clear + attr_accessor :value, :next_node + + def initialize() + end +end + +if __FILE__ == $PROGRAM_NAME + # Don't forget to add your tests! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/spec/linked_list_spec.rb b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/spec/linked_list_spec.rb new file mode 100644 index 00000000..43231459 --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/spec/linked_list_spec.rb @@ -0,0 +1,257 @@ +require "./linked_list" + +RSpec.describe "Node" do + let(:node) { Node.new("hi", "there") } + + context "#initialize with arguments" do + it "sets an attribute called value to the argument" do + expect(node.value).to eq("hi") + end + + it "sets an attribute called next_node to the argument" do + expect(node.next_node).to eq("there") + end + end + + context "#initialize without arguments" do + it "sets an attribute called value to nil" do + expect(Node.new.value).to eq(nil) + end + + it "sets an attribute called next_node to nil" do + expect(Node.new.next_node).to eq(nil) + end + end +end + +RSpec.describe "LinkedList" do + let(:linked_list) { LinkedList.new(Node.new("one", Node.new("two", Node.new("three", Node.new("four"))))) } + let(:empty_list) { LinkedList.new } + let(:one_item_list) { LinkedList.new(Node.new("just one")) } + + context "#initialize" do + it "sets head to nil if no argument is provided" do + expect(empty_list.head).to eq(nil) + end + + it "sets head to the argument if an argument is provided" do + head = Node.new("hi") + list = LinkedList.new(head) + + expect(list.head).to eq(head) + end + end + + context "#iterate" do + it "calls the provided callback on every list node and provides the node as an argument to the callback" do + values = [] + linked_list.iterate { |node| values << node } + + expect(values.length).to eq(4) + expect(values.last).to be_a_kind_of(Node) + end + + it "can handle an empty list" do + values = [] + empty_list.iterate { |node| values << node } + + expect(values.length).to eq(0) + end + + it "can handle a list with only a head" do + values = [] + one_item_list.iterate { |node| values << node } + + expect(values.length).to eq(1) + expect(values.last).to be_a_kind_of(Node) + end + end + + context "#print" do + it "prints nothing at all when the list is empty" do + expect { empty_list.print }.to_not output.to_stdout + end + + it "prints a single line for a list with one node" do + expect { one_item_list.print }.to output("just one\n").to_stdout + end + + it "prints all nodes" do + expect { linked_list.print }.to output("one\ntwo\nthree\nfour\n").to_stdout + end + end + + context "#find" do + it "returns nil when the list is empty" do + expect(empty_list.find(4)).to be_nil + end + + it "returns the correct Node when a Node with that value is in the list" do + expect(linked_list.find("two")).to be(linked_list.head.next_node) + end + + it "returns nil when the value is not in the list" do + expect(linked_list.find("nope")).to be_nil + end + end + + context "#add_first" do + it "adds the given Node to the beginning of the list without removing any" do + old_head = one_item_list.head + node = Node.new("I'm new") + one_item_list.add_first(node) + + expect(one_item_list.head).to be(node) + expect(one_item_list.head.next_node).to be(old_head) + end + + it "adds the given node to an empty list" do + node = Node.new("I'm new") + empty_list.add_first(node) + + expect(empty_list.head).to be(node) + expect(empty_list.head.next_node).to be_nil + end + end + + context "#add_last" do + it "adds the given Node to the end of the list without removing any" do + old_tail = one_item_list.head + node = Node.new("I'm new") + one_item_list.add_last(node) + + expect(one_item_list.head).to be(old_tail) + expect(one_item_list.head.next_node).to be(node) + end + + it "adds the given node to an empty list" do + node = Node.new("I'm new") + empty_list.add_last(node) + + expect(empty_list.head).to be(node) + expect(empty_list.head.next_node).to be_nil + end + end + + context "#remove_first" do + it "removes and returns the head of the list" do + old_head = linked_list.head + + expect(linked_list.remove_first).to be(old_head) + end + + it "updates the head to the correct Node" do + old_head = linked_list.head + linked_list.remove_first + + expect(linked_list.head).to be(old_head.next_node) + expect(linked_list.head.next_node).to be(old_head.next_node.next_node) + end + + it "does not produce an error when called on an empty list" do + expect { empty_list.remove_first }.to_not raise_exception + end + end + + context "#remove_last" do + it "removes and returns the tail of the list" do + old_tail = linked_list.head.next_node.next_node.next_node + + expect(linked_list.remove_last).to be(old_tail) + expect(linked_list.head.next_node.next_node.next_node).to be_nil + end + + it "makes the node before the old tail the new tail" do + new_tail = linked_list.head.next_node.next_node + + expect(linked_list.head.next_node.next_node).to be(new_tail) + end + + it "does not produce an error when called on an empty list" do + expect { empty_list.remove_last }.to_not raise_exception + end + end + + context "#replace" do + it "returns the inserted node" do + node = Node.new("replacing") + + expect(linked_list.replace(0, node)).to be(node) + end + + it "replaces the correct nodes at the correct indexes" do + node_zero = Node.new("replace at 0") + node_one = Node.new("replace at 1") + node_two = Node.new("replace at 2") + node_three = Node.new("replace at 3") + + [node_zero, node_one, node_two, node_three].each_with_index { |node, idx| linked_list.replace(idx, node) } + + expect(linked_list.head).to be(node_zero) + expect(linked_list.head.next_node).to be(node_one) + expect(linked_list.head.next_node.next_node).to be(node_two) + expect(linked_list.head.next_node.next_node.next_node).to be(node_three) + expect(linked_list.head.next_node.next_node.next_node.next_node).to be_nil + end + end + + context "#insert" do + it "can insert a node at the beginning of the list" do + old_head = one_item_list.head + node = Node.new('hi') + one_item_list.insert(0, node) + + expect(one_item_list.head).to be(node) + expect(one_item_list.head.next_node).to be(old_head) + expect(one_item_list.head.next_node.next_node).to be_nil + end + + it "can insert a node at the very end of the list (making a new tail)" do + old_head = one_item_list.head + node = Node.new('hi') + one_item_list.insert(1, node) + + expect(one_item_list.head).to be(old_head) + expect(one_item_list.head.next_node).to be(node) + expect(one_item_list.head.next_node.next_node).to be_nil + end + + it "can insert a node in the middle of a list" do + node = Node.new('hi') + linked_list.insert(2, node) + + expect { linked_list.print }.to output("one\ntwo\nhi\nthree\nfour\n").to_stdout + end + end + + context "#remove" do + it "returns the removed node" do + to_remove = linked_list.head.next_node.next_node.next_node + + expect(linked_list.remove(3)).to be(to_remove) + end + + it "removes the correct node" do + removed = linked_list.remove(1) + + found = false + linked_list.iterate { |node| found = true if node == removed } + + expect(found).to be false + end + + it "keeps the list intact when a node is removed" do + linked_list.remove(2) + + expect { linked_list.print }.to output("one\ntwo\nfour\n").to_stdout + end + end + + context "#clear" do + it "empties the list" do + linked_list.clear + + expect(linked_list.head).to be_nil + end + end +end \ No newline at end of file diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/spec/spec_helper.rb b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/solutions/linked_list.js b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/solutions/linked_list.js new file mode 100644 index 00000000..b806aecf --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/solutions/linked_list.js @@ -0,0 +1,320 @@ +class LinkedList { + constructor(head = null) { + this.head = head; + } + + iterate(callback) { + let count = 0; + let temp = this.head; + + while (temp !== null) { + const result = callback(temp, count); + + if (result === true) { + return temp; + } + + ++count; + temp = temp.next; + } + + return this.head; + } + + // print each node's value on its own line + // use your iterate method to be DRY! Don't get caught in the code rain, brrr. + print() { + this.iterate(node => console.log(node.value)); + } + + // find the node with the target value and return it + // if not found return null, use your iterate method to be DRY! + find(target) { + let result = null; + + this.iterate(node => { + if (node.value === target) { + result = node; + + return true; + } + }); + + return result; + } + + // add the node to the start of the list, no nodes should be removed + addFirst(node) { + node.next = this.head; + this.head = node; + } + + // add node to end of list, no nodes should be removed + // you may wish to use the iterate method + addLast(node) { + if (this.head === null) { + this.head = node; + return; + } + + this.iterate(currNode => { + if (currNode.next === null) { + currNode.next = node; + return true; + } + }); + } + + // remove the first Node in the list and update head + // and return the removed node + removeFirst() { + const oldHead = this.head; + + if (this.head !== null) { + this.head = this.head.next; + } + + return oldHead; + } + + // remove the tail node, iterate may be helpful + // return the node you just removed + removeLast() { + if (this.head === null || this.head.next === null) { + return this.removeFirst(); + } + + let oldTail = null; + + this.iterate(node => { + if (node.next.next === null) { + oldTail = node.next; + node.next = null; + return true; + } + }); + + return oldTail; + } + + // replace the node at the given index with the given node + replace(idx, node) { + if (idx === 0) { + this.removeFirst(); + this.addFirst(node); + return node; + } + + this.iterate((currNode, count) => { + if (count === idx - 1) { + node.next = currNode.next.next; + currNode.next = node; + + return true; + } + }); + + return node; + } + + // insert the node at the given index + // no existing nodes should be removed or replaced + insert(idx, node) { + if (idx === 0) { + this.addFirst(node); + return; + } + + this.iterate((currNode, count) => { + if (count === idx - 1) { + const oldNext = currNode.next; + currNode.next = node; + node.next = oldNext; + + return true; + } + }); + } + + // remove the node at the given index, and return it + remove(idx) { + if (idx === 0) { + return this.removeFirst(); + } + + let oldNode = null; + + this.iterate((node, count) => { + if (count === idx - 1) { + oldNode = node.next; + node.next = node.next.next; + + return true; + } + }); + + return oldNode; + } + + clear() { + this.head = null; + } +} + +class Node { + constructor(value = null, next = null) { + this.value = value; + this.next = next; + } +} + +if (require.main === module) { + let head = new Node('one', new Node('two', new Node('three', new Node('four')))); + let list = new LinkedList(head); + let emptyList = new LinkedList(); + let oneItemList = new LinkedList(new Node('just one')); + + console.log("Print one to four"); + list.print(); + console.log("-----------------------------"); + + console.log("Handle empty list print"); + emptyList.print(); + console.log("-----------------------------"); + + console.log("Handle one item list print"); + oneItemList.print(); + console.log("-----------------------------"); + + console.log(`Find four`); + console.log(`${list.find('four').value}`); + console.log(`Find non-existent value`); + console.log(`Nothing: ${list.find(50)}`); + console.log(`Find in empty list: ${emptyList.find(20)}`); + console.log(`Find just one in one item list: ${oneItemList.find('just one').value}`); + console.log(`Find nothing in one item list: ${oneItemList.find('nothing')}`); + console.log("-----------------------------"); + + console.log("Add zero as head"); + list.addFirst(new Node('zero')); + list.print(); + console.log("-----------------------------"); + + console.log("Add zero as head to empty list"); + emptyList.addFirst(new Node('zero')); + emptyList.print(); + emptyList.head = null; + console.log("-----------------------------"); + + console.log("Add zero as head to one item list"); + oneItemList.addFirst(new Node('zero')); + oneItemList.print(); + oneItemList.head = oneItemList.head.next; + console.log("-----------------------------"); + + console.log("Add five as tail"); + list.addLast(new Node('five')); + list.print(); + console.log("-----------------------------"); + + console.log("Add whaaa as tail to empty list"); + emptyList.addLast(new Node('whaaa')); + emptyList.print(); + emptyList.head = null; + console.log("-----------------------------"); + + console.log("Add whaaa as tail to one item list"); + oneItemList.addLast(new Node('whaaa')); + oneItemList.print(); + oneItemList.head.next = null; + console.log("-----------------------------"); + + console.log("Remove first node zero and return it"); + console.log(`${list.removeFirst().value} was removed`); + list.print(); + console.log("-----------------------------"); + + console.log("Remove first node from an empty list"); + console.log(`${emptyList.removeFirst()} was removed`); + emptyList.print(); + console.log("-----------------------------"); + + console.log("Remove first node from one item list"); + console.log(`${oneItemList.removeFirst().value} was removed`); + oneItemList.print(); + oneItemList.head = new Node('just one'); + console.log("-----------------------------"); + + console.log("Remove last node five and return it"); + console.log(`${list.removeLast().value} was removed`); + list.print(); + console.log("-----------------------------"); + + console.log("Remove last node from empty list and return it"); + console.log(`${emptyList.removeLast()} was removed`); + emptyList.print(); + console.log("-----------------------------"); + + console.log("Remove last node from one item list and return it"); + console.log(`${oneItemList.removeLast().value} was removed`); + oneItemList.print(); + oneItemList.head = new Node('just one'); + console.log("-----------------------------"); + + console.log("Replace node at index and return inserted node"); + console.log(`replace middle two with 2: ${list.replace(1, new Node('2')).value}`); + list.print(); + console.log(`replace zeroth one with 1: ${list.replace(0, new Node('1')).value}`); + list.print(); + console.log(`replace tail four with 4: ${list.replace(3, new Node('4')).value}`); + list.print(); + console.log(`replace middle three with 3: ${list.replace(2, new Node('3')).value}`); + list.print(); + console.log("-----------------------------"); + + console.log("Insert node at index"); + console.log("Insert at 0"); + list.insert(0, new Node('zero')); + list.print(); + list.removeFirst(); + + console.log("Insert at 2"); + list.insert(2, new Node('two')); + list.print(); + + console.log("Insert at 4"); + list.insert(4, new Node('four')); + list.print(); + + console.log("Insert at 6"); + list.insert(6, new Node('six')); + list.print(); + list.removeLast(); + + console.log("Insert at 0 in empty list"); + emptyList.insert(0, new Node('zero')); + emptyList.print(); + emptyList.removeFirst(); + console.log("-----------------------------"); + + head = new Node('one', new Node('two', new Node('three', new Node('four')))); + list = new LinkedList(head); + + console.log("Remove the node at the index and return it"); + console.log(`Remove two: ${list.remove(1).value}`); + console.log(`Remove tail four: ${list.remove(2).value}`); + console.log(`Remove three: ${list.remove(1).value}`); + console.log(`Remove one: ${list.remove(0).value}`); + list.print(); + console.log("-----------------------------"); + + console.log("Clear a list"); + head = new Node('one', new Node('two', new Node('three', new Node('four')))); + list = new LinkedList(head); + list.clear(); + list.print(); +} + +module.exports = { + Node, LinkedList +}; diff --git a/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/solutions/linked_list.rb b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/solutions/linked_list.rb new file mode 100644 index 00000000..52562c12 --- /dev/null +++ b/07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list/solutions/linked_list.rb @@ -0,0 +1,245 @@ +class LinkedList + attr_accessor :head + + def initialize(head = nil) + @head = head + end + + def iterate + count = 0 + temp = @head + + until temp.nil? + yield(temp, count) + temp = temp.next_node + count += 1 + end + + @head + end + + def print + iterate { |node| puts node.value } + end + + def find(target) + iterate do |node| + return node if node.value == target + end + + nil + end + + def add_first(node) + node.next_node = @head + @head = node + end + + def add_last(node) + if @head.nil? + @head = node + return + end + + iterate do |curr_node| + if curr_node.next_node.nil? + curr_node.next_node = node + return + end + end + end + + def remove_first + old_head = @head + @head = @head.next_node unless @head.nil? + old_head + end + + def remove_last + return remove_first if @head.nil? || @head.next_node.nil? + + iterate do |node| + if node.next_node.next_node.nil? + old_tail = node.next_node + node.next_node = nil + return old_tail + end + end + end + + def replace(idx, node) + if idx.zero? + remove_first + add_first(node) + return node + end + + iterate do |curr_node, count| + if count == idx - 1 + node.next_node = curr_node.next_node.next_node + curr_node.next_node = node + return node + end + end + end + + def insert(idx, node) + if idx.zero? + add_first(node) + return + end + + iterate do |curr_node, count| + if count == idx - 1 + old_next = curr_node.next_node + curr_node.next_node = node + node.next_node = old_next + return + end + end + end + + def remove(idx) + if idx.zero? + return remove_first + end + + iterate do |node, count| + if count == idx - 1 + old_node = node.next_node + node.next_node = node.next_node.next_node + return old_node + end + end + end + + def clear + @head = nil + end +end + +class Node + attr_accessor :value, :next_node + + def initialize(value = nil, next_node = nil) + @value = value + @next_node = next_node + end +end + +if __FILE__ == $PROGRAM_NAME + head = Node.new('one', Node.new('two', Node.new('three', Node.new('four')))) + list = LinkedList.new(head) + empty_list = LinkedList.new + + puts "Print one to four" + list.print + puts "-----------------------------" + + puts "Print empty list" + empty_list.print + puts "-----------------------------" + + puts "Find four" + puts "#{list.find('four').value}" + puts "Find non-existent value" + puts "Nothing: #{list.find(50)}" + puts "-----------------------------" + + puts "Find four in empty list" + puts "#{empty_list.find('four')}" + puts "-----------------------------" + + puts "Add zero as head" + list.add_first(Node.new('zero')) + list.print + puts "-----------------------------" + + puts "Add zero as head to empty list" + empty_list.add_first(Node.new('zero')) + empty_list.print + empty_list.head = nil + puts "-----------------------------" + + puts "Add five as tail" + list.add_last(Node.new('five')) + list.print + puts "-----------------------------" + + puts "Add five as tail to empty list" + empty_list.add_last(Node.new('five')) + empty_list.print + empty_list.head = nil + puts "-----------------------------" + + puts "Remove first node zero and return it" + puts "#{list.remove_first.value} was removed" + list.print + puts "-----------------------------" + + puts "Remove first node from an empty list" + puts "#{empty_list.remove_first} was removed" + empty_list.print + puts "-----------------------------" + + puts "Remove last node five and return it" + puts "#{list.remove_last.value} was removed" + list.print + puts "-----------------------------" + + puts "Remove last node from empty list and return it" + puts "#{empty_list.remove_last} was removed" + empty_list.print + puts "-----------------------------" + + puts "Replace node at index and return inserted node" + puts "replace middle two with 2: #{list.replace(1, Node.new('2')).value}" + list.print + puts "replace zeroth one with 1: #{list.replace(0, Node.new('1')).value}" + list.print + puts "replace tail four with 4: #{list.replace(3, Node.new('4')).value}" + list.print + puts "replace middle three with 3: #{list.replace(2, Node.new('3')).value}" + list.print + puts "-----------------------------" + + puts "Insert node at index" + puts "Insert at 0" + list.insert(0, Node.new('zero')) + list.print + list.remove_first + + puts "Insert at 2" + list.insert(2, Node.new('two')) + list.print + + puts "Insert at 4" + list.insert(4, Node.new('four')) + list.print + + puts "Insert at 6" + list.insert(6, Node.new('six')) + list.print + list.remove_last + puts "-----------------------------" + + head = Node.new('one', Node.new('two', Node.new('three', Node.new('four')))) + list = LinkedList.new(head) + + puts "Remove the node at the index and return it" + puts "Remove two: #{list.remove(1).value}" + puts "Remove tail four: #{list.remove(2).value}" + puts "Remove three: #{list.remove(1).value}" + puts "Remove one: #{list.remove(0).value}" + list.print + puts "-----------------------------" + + puts "Clear a list" + head = Node.new('one', Node.new('two', Node.new('three', Node.new('four')))) + list = LinkedList.new(head) + list.clear + list.print +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/07-week-6--foundational-data-structures/01-day-3--underneath-arrays/README.md b/07-week-6--foundational-data-structures/01-day-3--underneath-arrays/README.md new file mode 100644 index 00000000..9db4a41e --- /dev/null +++ b/07-week-6--foundational-data-structures/01-day-3--underneath-arrays/README.md @@ -0,0 +1,98 @@ +# Arrays + +## Introduction + +While you may be familiar with arrays, chances are you have not considered what happens when our computer either manipulates an array by adding or removing elements, or retrieves information from an array. In this lesson, we'll consider what happens when we retrieve or manipulate data in an array. + +## Arrays - under the hood + +When we initialize an array in a programming language, the language +allocates space in memory for your array, and then points that starting variable to that address in memory. Then the program assigns a fixed amount of memory for each element. + +![](https://s3.amazonaws.com/learn-verified/objects-tenElementArray.gif) + +So let's say my array say starts at memory address 100. And assume that the programming language allocates 8 bits for each element in the array. And that it allocates enough space for ten elements evenly spaced in memory. + +Now, let's try to think through how a computer program retrieves an element at a specific index. + +```javascript +let my_arr = ["a"]; + +my_arr[0] = "a"; +``` + +Well, we first initialize the array and assign the letter 'a' as the first element, the programming language associates the letter "a" to a specific space in memory. In our example, address 100. So then, when we call `my_arr[0]` all the program has to do is go to address 100, and retrieve the element. + +So now, what do you think happens if we call `my_arr[3]`, to return what is in that slot. If the `my_arr` begins at address 100, and we allocate eight bits of space for each, what address does the program go to to retrieve the element at index 3? + +Is there a formula that we can come up with for retrieval? Yes, it's algebra. + +```javascript +my_arr[3] + +100 + 3*8 = 124 +``` + +![](https://s3-us-west-2.amazonaws.com/curriculum-content/web-development/algorithms/mailboxes.jpg) + +So our programming language knows that if eight bits are allocated to each element, and then to retrieve an element at a specific index, the program simply visits an address by using the following formula: + +- memoryLocationOfElement = arrayStartAddress + indexNumber \* bit_allocation + +### Manipulating array elements + +Now that we talked about retrieving elements from an array, let's talk about removing elements from an array. + +```javascript +let arr = [1, 24, 48, 9]; +arr.pop; +// 9 + +arr; +// [1, 24, 48] +``` + +Performing an operation like pop is fairly simple. Again let's assume that our array begins at memory address 100. + +| memory address | 100 | 108 | 116 | 124 | +| -------------- | :-: | --: | --: | --: | +| arr | 1 | 24 | 48 | 9 | +| arr.pop | 1 | 24 | 48 | X | + +So removing from the end of the array, is not so bad. But removing an element from the beginning involves a lot more. + +| memory address | 100 | 108 | 116 | 124 | +| -------------- | :-: | --: | --: | --: | +| arr | 1 | 24 | 48 | 9 | +| arr.shift | 24 | 48 | 9 | X | + +Looking at the chart above, shifting involves moving every remaining element to a new space in memory. The cost is equal to the number of elements in the array. So the time complexity of shifting is big O(n). Note that to add elements to the beginning of the array also will cost big O(n) as every subsequent element would have to move to different spot in memory. + +| memory address | 100 | 108 | 116 | 124 | 132 | +| -------------- | :-: | --: | --: | --: | --: | +| arr | 1 | 24 | 48 | 9 | | +| arr.unshift(5) | 5 | 1 | 24 | 48 | 9 | + +So unshifting is big O(n) and shifting is big O(n). However, popping and finding elements take the same amount of time regardless the size of the array. That is, the time complexity is big O(1), meaning that the cost of the operation does not depend on the number of elements in the array. + +### A second problem: too many elements + +Remember that to retrieve information from an array, we simply need to apply the formula `startingAddress + index * bitAllocation` and go to the corresponding address. Well, one problem that occurs with having all of these contiguous elements, is that we must allocate a specific amount of space, say enough space for 10 elements. And now we have to think about what occurs when we want to add eleven elements? + +| memory address | 100 | 108 | 116 | 124 | 132 | 140 | +| -------------- | :-: | --: | --: | --: | --: | -------: | +| arr | 1 | 24 | 48 | 9 | 32 | song.mp3 | +| arr.push(5) | 1 | 24 | 48 | 9 | 32 | song.mp3 | + +Do you see our problem? We want to push another element, but something else is on those eight bits. If we move our new element to a different location, our formula for retrieving elements no longer works. Instead what we do, is copy our array into a new location in memory where there is enough space. However, notice that the cost of doing this is big O(n) as we must incur a cost for each element we copy over. + +| new memory address with wide open space | 300 | 308 | 316 | 324 | 332 | 340 | +| --------------------------------------- | :-: | --: | --: | --: | --: | --: | +| arr | 1 | 24 | 48 | 9 | 32 | | +| arr.push(5) | 1 | 24 | 48 | 9 | 32 | 5 | + +### Summary + +We saw in this section that some of the strengths and weaknesses of using an array. Retrieving elements by index and adding elements to the end of the array has a time complexity of big O(1), while adding or removing elements at the beginning of an array is big O(n). We also saw that because operations in our array rely on using neighboring locations in memory, we can run out of space. + +But do not despair, there is alternative data structure that does not rely on elements having contiguous memory addresses and is less costly for adding and removing elements from the beginning. That is a linked list. We will learn about it in the next section. diff --git a/07-week-6--foundational-data-structures/02-day-4--underneath-hashes/README.md b/07-week-6--foundational-data-structures/02-day-4--underneath-hashes/README.md new file mode 100644 index 00000000..75524218 --- /dev/null +++ b/07-week-6--foundational-data-structures/02-day-4--underneath-hashes/README.md @@ -0,0 +1,104 @@ +# Hash Table + +## Objectives + +- Learn the components of a hash table. +- Learn about collisions and how to resolve them. +- Learn the role of a hash function and the attributes of a good hash function. + +## Hash Tables + +![](https://s3.amazonaws.com/learn-verified/reintroduce-415x400.png) + +Now it's time to formally introduce you to the hash. A hash table is where information related to a key is assigned to a specific index. + +For a hash to work, we use a **hash function** to determine where exactly to store a information related to that key. Later, use the same hash function to determine where to search for a given key. + +## A library as an analogy + +One way to think about how hashes relate to hash functions is thinking about how we find a book in a library. We do this by telling a librarian the title and author of a book, and the librarian tells us precisely where to find the book. + +![](https://s3-us-west-2.amazonaws.com/curriculum-content/algorithms/dewey-decimal-arrangement.jpg) + +So here our key is the title and author of the book, which then responds with a card catalogue id. The cart catalogue id (which comes from the Dewey Decimal System above) tells us exactly where to find the book. If the book is there, we have our book and all of the information inside. If nothing is there, there is no book. + +So let's start with inserting some books. We have the following books: _The Bible_, _Alexander Hamilton_, _Introduction to Physics_, and _War and Peace_. Based on our hash function, we store the books in the following locations: + +| Index | Book | +| ----- | :-----------------------: | +| 000 | | +| 100 | | +| 200 | _The Bible_ | +| 300 | | +| 400 | | +| 500 | _Introduction to Physics_ | +| 600 | | +| 700 | | +| 800 | _War and Peace_ | +| 900 | _Alexander Hamilton_ | + +You will see that while the Dewey Decimal System assigns us one of a range of numbers, we adapt its formula to store each book at the lowest number possible for each section. So based on that, The Bible is assigned 200, because it falls under religion. Accordingly, we also assign Introduction to Physics number 500, War and Peace 800 and Alexander Hamilton 900. + +Because we assigned each of our books according to this formula, when we retrieve a book, we do not need to look through every index to find our books, instead we just look at the place of the book based on the Dewey Decimal System. + +![](https://s3.amazonaws.com/learn-verified/geroge-peabody-library-horizontal-large-gallery.jpg) + +> A massive library + +So we use our formula to tell us both where to insert a book. + +And we also use our formula to know if a book exists in our collection. If someone asks us if _Eloquent Javascript_ is in our hash table, we simply visit our index at location 600, see that nothing is there, and can confidently reply that the book is not located there. Because our formula tells us where to retrieve a book we are able to retrieve and insert an element in constant time. + +So with a hash table, we look at the data in our key, run it through our hash function to determine where to place the element and associated data. Later, we also use the information in the key, run it through our hash function to tell us where to retrieve this data. With this process we achieve our goal of constant time for inserting and retrieving elements irrespective of the number of elements in our collection. + +### The Problem: Collision + +Our hash table currently looks like the following: + +| Index | Book | +| ----- | :-----------------------: | +| 000 | | +| 100 | | +| 200 | _The Bible_ | +| 300 | | +| 400 | | +| 500 | _Introduction to Physics_ | +| 600 | | +| 700 | | +| 800 | _War and Peace_ | +| 900 | _Alexander Hamilton_ | + +Now what happens if we need to store another book, this time _Introduction to Biology_. Well, our adapted Dewey Decimal System tells us to store the key at precisely index 500. The only problem is that the slot is already filled. We have just encountered a **collision**. A collision is where our hash function outputs an index that already is assigned to another key in our hash table. + +To handle our collision we apply a technique called _separate chaining_. With separate chaining, each index points to a linked list. So in our example above we could place both _Introduction to Physics_ and _Introduction to Biology_ in the place linked list is located at index 500. Applying the separate chaining technique, our hash table looks like the following: + +| Index | Book | +| ----- | :----------------------------------------------------------: | +| 000 | | +| 100 | | +| 200 | [ "*The Bible*" ] | +| 300 | | +| 400 | | +| 500 | [ "*Introduction to Physics*", "*Introduction to Biology*" ] | +| 600 | | +| 700 | | +| 800 | [ "*War and Peace*" ] | +| 900 | [ "*Alexander Hamilton*" ] | + +Note that in the worse case scenario, all of our inserted elements collide and we have to traverse a linked list of length n to retrieve an element, so we have O(n). However, on average collisions do not occur, so we retrieve constant time for lookup, insertion and deletion _on average_. + +## Choosing a good hash function + +Going forward, we should choose a hash function that minimizes the chance of a collision occurring. Some properties of a good hash function. + +1. Makes use of all information provided by a given key to maximize the number of possible hash values. Note that the real Dewey Decimal System does a better job at this: different titles by different authors map to different values. +2. Maps similar keys to very different values - making collisions much less likely. +3. Also hash function called frequently so should employ simple and quick introductions. + +## Summary + +In this function we learned about hash tables. Hash tables place the value of an element into a hash function which outputs a hash value. The hash value determines where to place the element. Because a hash function produces the same hash value for a given element, it also gives us fast lookup time to retrieve an element. + +When a hash function outputs the same hash value for two different elements we have a collision. We can resolve a collision by employing separate chaining where each hash value points to a linked list, and when there is a collision we attach the element to the linked list. + +Because retrieving elements from a linked list is O(n), we try to choose a hash function that avoids collisions. Because we must use our hash function to insert, delete, and retrieve elements we also choose a fast hash function. diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/.gitignore b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/README.md b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/README.md new file mode 100644 index 00000000..08a76fbc --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/README.md @@ -0,0 +1,51 @@ +# Bonus Algorithm: Recursive Reverse a String + +For this task, you'll need to reverse a string...**recursively**! Your method will receive one argument, a string, and be expected to output that string with its letters in reverse order. + +``` +Input: "hi" +Output: "ih" + +Input: "catbaby" +Output: "ybabtac" +``` + +**Do not call any type of built-in reverse method!** + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/javascript/package.json b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/javascript/package.json new file mode 100644 index 00000000..df161685 --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "reverse_string", + "version": "1.0.0", + "description": "reverse a string", + "main": "reverse_string.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} \ No newline at end of file diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/javascript/reverse_string.js b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/javascript/reverse_string.js new file mode 100644 index 00000000..e82e621c --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/javascript/reverse_string.js @@ -0,0 +1,19 @@ +function reverseString(str) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: 'ih'"); + console.log("=>", reverseString('ih')); + + console.log(""); + + console.log("Expecting: 'ybabtac'"); + console.log("=>", reverseString('catbaby')); +} + +module.exports = reverseString; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/javascript/tests/reverse_string.test.js b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/javascript/tests/reverse_string.test.js new file mode 100644 index 00000000..9b2e65cd --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/javascript/tests/reverse_string.test.js @@ -0,0 +1,21 @@ +const reverseString = require('../reverse_string'); + +test("can handle an empty string", () => { + expect(reverseString("")).toBe(""); +}); + +test("can handle a single character", () => { + expect(reverseString("a")).toBe("a"); +}); + +test("can handle two characters", () => { + expect(reverseString("ab")).toBe("ba"); +}); + +test("can handle three characters", () => { + expect(reverseString("cat")).toBe("tac"); +}); + +test("can handle many characters", () => { + expect(reverseString("sham-meow")).toBe("sham-meow".split("").reverse().join("")); +}); diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/.rspec b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/Gemfile b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/reverse_string.rb b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/reverse_string.rb new file mode 100644 index 00000000..99487efc --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/reverse_string.rb @@ -0,0 +1,18 @@ +def reverse_string(str) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: 'ih'" + puts "=>", reverse_string('hi') + + puts + + puts "Expecting: 'ybabtac'" + puts "=>", reverse_string('catbaby') + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution \ No newline at end of file diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/spec/reverse_string_spec.rb b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/spec/reverse_string_spec.rb new file mode 100644 index 00000000..f906e168 --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/spec/reverse_string_spec.rb @@ -0,0 +1,23 @@ +require './reverse_string' + +RSpec.describe '#reverse_string' do + it "can handle an empty string" do + expect(reverse_string('')).to eq('') + end + + it "can handle a single character" do + expect(reverse_string('a')).to eq('a') + end + + it "can handle two characters" do + expect(reverse_string('ab')).to eq('ba') + end + + it "can handle three characters" do + expect(reverse_string('cat')).to eq('tac') + end + + it "can handle many characters" do + expect(reverse_string('sham-meow')).to eq('sham-meow'.reverse) + end +end \ No newline at end of file diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/spec/spec_helper.rb b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/solutions/reverse_string.js b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/solutions/reverse_string.js new file mode 100644 index 00000000..011924df --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/solutions/reverse_string.js @@ -0,0 +1,48 @@ +function reverseString(str) { + if (str.length < 2) { + return str; + } + + return reverseString(str.slice(1)) + str[0]; +} + +console.log("Expecting: 'ih'"); +console.log(reverseString('hi')); + +console.log(""); + +console.log("Expecting: 'ybabtac'"); +console.log(reverseString('catbaby')); + +console.log(""); + +console.log("Expecting: 'a'"); +console.log(reverseString('a')); + +console.log(""); + +console.log("Expecting: '' (empty string)"); +console.log(reverseString('')); + +// Please add your pseudocode to this file +/******************************************************************************************* + * return string if length == 1 or is empty + * + * return reverse_string(string - 0th char) + 0th char + * *****************************************************************************************/ + + +// And a written explanation of your solution +/******************************************************************************************* + * First I thought about the base case: if the string is empty or just one character, + * we can return it as is. Next, I thought about if the string were 2 characters. In + * that case, I need to return str[1] + str[0] for it to be reversed. Since recursion + * is depth-first, i.e. it goes as deep as possible before it starts returning up the + * stack, that means the algorithm won't start returning until it hits the last character + * in the string. Let's pretend the string is 'hi'. On the first frame we recurse with 'i' + * as the argument. This hits the base case, so 'i' returns. In the previous frame, the + * 0th character is 'h', so I just need to put the 'h' after the 'i' that's returned from + * the recursive call: reverse_string('i') + 'h'. This holds true for strings of any length + * because as each frame returns up the stack, the 0th character in each previous frame is + * the character that comes right before the one that was returned. + * *******************************************************************************************/ diff --git a/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/solutions/reverse_string.rb b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/solutions/reverse_string.rb new file mode 100644 index 00000000..3121a7fb --- /dev/null +++ b/07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse/solutions/reverse_string.rb @@ -0,0 +1,43 @@ +def reverse_string(str) + return str if str.length < 2 + + reverse_string(str[1..-1]) + str[0] +end + +puts "Expecting: 'ih'" +puts reverse_string('hi') + +puts + +puts "Expecting: 'ybabtac'" +puts reverse_string('catbaby') + +puts + +puts "Expecting: '' (empty string)" +puts reverse_string('') + +puts + +puts "Expecting: 'a'" +puts reverse_string('a') + +############################################################################ +# return string if length == 1 or is empty +# +# return reverse_string(string - 0th char) + 0th char +############################################################################ + +############################################################################ +# First I thought about the base case: if the string is empty or just one character, +# we can return it as is. Next, I thought about if the string were 2 characters. In +# that case, I need to return str[1] + str[0] for it to be reversed. Since recursion +# is depth-first, i.e. it goes as deep as possible before it starts returning up the +# stack, that means the algorithm won't start returning until it hits the last character +# in the string. Let's pretend the string is 'hi'. On the first frame we recurse with 'i' +# as the argument. This hits the base case, so 'i' returns. In the previous frame, the +# 0th character is 'h', so I just need to put the 'h' after the 'i' that's returned from +# the recursive call: reverse_string('i') + 'h'. This holds true for strings of any length +# because as each frame returns up the stack, the 0th character in each previous frame is +# the character that comes right before the one that was returned. +############################################################################ \ No newline at end of file diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/.gitignore b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/README.md b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/README.md new file mode 100644 index 00000000..b7308785 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/README.md @@ -0,0 +1,119 @@ +# Bonus: Modify the Linked List to Track Tail and Size + +Today we'll be modifying the Linked List we created earlier to track its `tail` and `size`. You can use your solution or ours as starter code. We've included ours in the starter files if you wish to use it. + +Note that this is a pretty strange implementation of a Linked List and there are those who would say: "Ermahgerd! What have you done?!" We're trying to stretch our skills here: that's what we've done! + +![Linked List](./linked_list.png) + +## Refresher in Case You Forgot About Our Old List Friend + +A Linked List is a data structure consisting of Nodes. The `head` Node denotes the start of the list. Each Node has two attributes: `value` and `next`. `value` stores the data we might be interested in retrieving, while `next` points to the next Node in the list. The last Node, called the `tail`, in the list points to nothing (e.g. `next` is `null`), and that's how we know it's the end! + +We can use another class called `LinkedList` to track the `head` of the list. + +## Modify the `LinkedList` Class + +We'll be tracking the `tail` and `size` of the list. There are a number of ways to implement these features with varying time complexities. Create helper methods if needed. Make sure you can explain to yourself and others why you chose the approach you took. + +For any method, such as adding or removing a Node, assume that only valid inputs will be provided, such as valid indices. + +Also make the following assumptions: + +- Upon initialization of a new list, a user might provide a Node that is already connected to other Nodes, i.e. the `head` Node being provided has a `next` attribute that points to another Node, and that Node might point to another Node. +- Any time a Node is added via any other method, such as `add_first` or `insert`, that Node is not yet connected to any other Nodes, i.e. its `next` value is `null` or `nil` or some other falsy value. + +As you modify the class, think about what the time complexity is for any methods you add or modify. + +We've included all of the original tests in the test suites to ensure all of the methods continue to work as expected as you modify them. + +### 1. Track the `tail` + +Add an attribute to `LinkedList` called `tail`. When an empty list is initialized, `tail` should be a falsy value, such as `null` or `nil`. As Nodes are added and removed, the `tail` should be updated to reflect the current `tail`. Think about which methods in the class may affect the `tail` of the list. + +``` +list = new LinkedList +list.tail +=> null or nil + +node = new Node('it meee') +list.add_first(node) +list.tail +=> Node with value 'it meee' + +list.remove_first +=> Node with value 'it meee' +list.tail +=> null or nil + +another_list = new LinkedList(node) +another_list.tail +=> Node with value 'it meee' +``` + +### 2. Track the `size` + +Add an attribute to `LinkedList` called `size`. When an empty list is initialized, `size` should be `0`. As Nodes are added and removed, the `size` should be updated to reflect the current `size`. Think about which methods in the class may affect the `size` of the list. + +``` +list = new LinkedList +list.size +=> 0 + +node = new Node('it meee') +list.add_first(node) +list.size +=> 1 + +list.remove_first +=> Node with value 'it meee' +list.size +=> 0 + +list.add_first(node) +list.add_first(new Node('it not meee')) +list.size +=> 2 +``` + +### 3. Super Bonus Refactor + +Once you can track the `tail` and `size` successfully, go through the `LinkedList` methods and see if you can refactor any to be more time-efficient. + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, and explain your solution, and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/javascript/linked_list.js b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/javascript/linked_list.js new file mode 100644 index 00000000..358ff327 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/javascript/linked_list.js @@ -0,0 +1,181 @@ +class LinkedList { + constructor(head = null) { + this.head = head; + } + + iterate(callback) { + let count = 0; + let temp = this.head; + + while (temp !== null) { + const result = callback(temp, count); + + if (result === true) { + return temp; + } + + ++count; + temp = temp.next; + } + + return this.head; + } + + // print each node's value on its own line + // use your iterate method to be DRY! Don't get caught in the code rain, brrr. + print() { + this.iterate(node => console.log(node.value)); + } + + // find the node with the target value and return it + // if not found return null, use your iterate method to be DRY! + find(target) { + let result = null; + + this.iterate(node => { + if (node.value === target) { + result = node; + + return true; + } + }); + + return result; + } + + // add the node to the start of the list, no nodes should be removed + addFirst(node) { + node.next = this.head; + this.head = node; + } + + // add node to end of list, no nodes should be removed + // you may wish to use the iterate method + addLast(node) { + if (this.head === null) { + this.head = node; + return; + } + + this.iterate(currNode => { + if (currNode.next === null) { + currNode.next = node; + return true; + } + }); + } + + // remove the first Node in the list and update head + // and return the removed node + removeFirst() { + const oldHead = this.head; + + if (this.head !== null) { + this.head = this.head.next; + } + + return oldHead; + } + + // remove the tail node, iterate may be helpful + // return the node you just removed + removeLast() { + if (this.head === null || this.head.next === null) { + return this.removeFirst(); + } + + let oldTail = null; + + this.iterate(node => { + if (node.next.next === null) { + oldTail = node.next; + node.next = null; + return true; + } + }); + + return oldTail; + } + + // replace the node at the given index with the given node + replace(idx, node) { + if (idx === 0) { + this.removeFirst(); + this.addFirst(node); + return node; + } + + this.iterate((currNode, count) => { + if (count === idx - 1) { + node.next = currNode.next.next; + currNode.next = node; + + return true; + } + }); + + return node; + } + + // insert the node at the given index + // no existing nodes should be removed or replaced + insert(idx, node) { + if (idx === 0) { + this.addFirst(node); + return; + } + + this.iterate((currNode, count) => { + if (count === idx - 1) { + const oldNext = currNode.next; + currNode.next = node; + node.next = oldNext; + + return true; + } + }); + } + + // remove the node at the given index, and return it + remove(idx) { + if (idx === 0) { + return this.removeFirst(); + } + + let oldNode = null; + + this.iterate((node, count) => { + if (count === idx - 1) { + oldNode = node.next; + node.next = node.next.next; + + return true; + } + }); + + return oldNode; + } + + clear() { + this.head = null; + } +} + +class Node { + constructor(value = null, next = null) { + this.value = value; + this.next = next; + } +} + +if (require.main === module) { + let head = new Node('one', new Node('two', new Node('three', new Node('four')))); + let list = new LinkedList(head); + let emptyList = new LinkedList(); + let oneItemList = new LinkedList(new Node('just one')); + +} + +module.exports = { + Node, LinkedList +}; diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/javascript/package.json b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/javascript/package.json new file mode 100644 index 00000000..60d1adf6 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "linked_list", + "version": "1.0.0", + "description": "linked list", + "main": "linked_list.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/javascript/tests/linked_list.test.js b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/javascript/tests/linked_list.test.js new file mode 100644 index 00000000..c88f5522 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/javascript/tests/linked_list.test.js @@ -0,0 +1,430 @@ +const { Node, LinkedList } = require("../linked_list"); + +describe("Node", () => { + const makeNode = () => new Node("hi", "there"); + const emptyNode = new Node(); + + test("sets an attribute called value to the argument on initialization", () => { + expect(makeNode().value).toBe("hi"); + }); + + test("sets an attribute called next to the argument on initialization", () => { + expect(makeNode().next).toBe("there"); + }); + + test("sets an attribute called value to null on initialization when there is no argument", () => { + expect(emptyNode.value).toBe(null); + }); + + test("sets an attribute called next to null on initialization when there is no argument", () => { + expect(emptyNode.next).toBe(null); + }); +}); + +describe("LinkedList", () => { + const nodeFour = new Node("four"); + const nodeThree = new Node("three", nodeFour); + const nodeTwo = new Node("two", nodeThree); + const nodeOne = new Node("one", nodeTwo); + const justOne = new Node("just one"); + + let emptyList = new LinkedList(); + let oneItemList = new LinkedList(justOne); + let linkedList = new LinkedList(nodeOne); + + const consoleLog = console.log; + const LLValues = ["one", "two", "three", "four"]; + + beforeEach(() => { + console.log = consoleLog; + + const values = ["one", "two", "three", "four", "just one"]; + const nodes = [nodeOne, nodeTwo, nodeThree, nodeFour, justOne]; + + nodes.forEach((node, i) => { + node.value = values[i]; + }); + + emptyList = new LinkedList(); + + justOne.next = null; + oneItemList = new LinkedList(justOne); + + nodeOne.next = nodeTwo; + nodeTwo.next = nodeThree; + nodeThree.next = nodeFour; + nodeFour.next = null; + linkedList = new LinkedList(nodeOne); + }); + + test("head is null if no argument provided on initialization", () => { + expect(emptyList.head).toBe(null); + }); + + test("head is set to the argument if provided on initialization", () => { + expect(oneItemList.head).toBe(justOne); + }); + + describe("iterate()", () => { + test("iterate() calls the provided callback on every node with the node as an argument to the callback", () => { + const values = []; + linkedList.iterate((node) => values.push(node)); + + expect(values.length).toBe(4); + expect(values[3]).toBe(nodeFour); + }); + + test("iterate() can handle an empty list", () => { + const values = []; + emptyList.iterate((node) => values.push(node)); + + expect(values.length).toBe(0); + }); + + test("iterate() can handle a list with one node", () => { + const values = []; + oneItemList.iterate((node) => values.push(node)); + + expect(values.length).toBe(1); + expect(values[0]).toBe(justOne); + }); + }); + + describe("print()", () => { + let consoleOutput = []; + const mockOutput = output => consoleOutput.push(output); + beforeEach(() => (console.log = mockOutput)); + afterEach(() => (consoleOutput = [])); + + test("prints nothing at all when the list is empty", () => { + emptyList.print(); + + expect(consoleOutput.length).toBe(0); + }); + + test("prints a single line for a list with one node", () => { + oneItemList.print(); + + expect(consoleOutput[0]).toBe(justOne.value); + }); + + test("prints all nodes", () => { + linkedList.print(); + + expect(consoleOutput).toEqual(LLValues); + }); + }); + + describe("find()", () => { + test("returns null when the list is empty", () => { + expect(emptyList.find(4)).toBe(null); + }); + + test("returns the correct Node when a Node with that value is in the list", () => { + expect(linkedList.find("two")).toBe(nodeTwo); + }); + + test("returns null when the value is not in the list", () => { + expect(linkedList.find("nope")).toBe(null); + }); + }); + + describe("addFirst()", () => { + test("adds the given Node to the beginning of the list without removing any", () => { + const newNode = new Node("I'm new"); + oneItemList.addFirst(newNode); + + expect(oneItemList.head).toBe(newNode); + expect(oneItemList.head.next).toBe(justOne); + }); + + test("adds the given node to an empty list", () => { + const newNode = new Node("I'm new"); + emptyList.addFirst(newNode); + + expect(emptyList.head).toBe(newNode); + expect(emptyList.head.next).toBe(null); + }); + }); + + describe("addLast()", () => { + test("adds the given Node to the end of the list without removing any", () => { + const newNode = new Node("I'm new"); + oneItemList.addLast(newNode); + + expect(oneItemList.head).toBe(justOne); + expect(oneItemList.head.next).toBe(newNode); + }); + + test("adds the given node to an empty list", () => { + const newNode = new Node("I'm new"); + emptyList.addLast(newNode); + + expect(emptyList.head).toBe(newNode); + expect(emptyList.head.next).toBe(null); + }); + }); + + describe("removeFirst()", () => { + test("removes and returns the head of the list", () => { + expect(linkedList.removeFirst()).toBe(nodeOne); + }); + + test("updates the head to the correct Node", () => { + linkedList.removeFirst(); + + expect(linkedList.head).toBe(nodeTwo); + expect(linkedList.head.next).toBe(nodeThree); + }); + + test("does not produce an error when called on an empty list", () => { + expect(() => emptyList.removeFirst()).not.toThrow(Error); + }); + }); + + describe("removeLast()", () => { + test("removes and returns the tail of the list", () => { + expect(linkedList.removeLast()).toBe(nodeFour); + expect(linkedList.head.next.next.next).toBe(null); + }); + + test("makes the node before the old tail the new tail", () => { + linkedList.removeLast(); + + expect(linkedList.head.next.next).toBe(nodeThree); + }); + + test("does not produce an error when called on an empty list", () => { + expect(() => emptyList.removeLast()).not.toThrow(Error); + }); + }); + + describe("replace()", () => { + test("returns the inserted node", () => { + const newNode = new Node("replacing"); + + expect(linkedList.replace(0, newNode)).toBe(newNode); + }); + + test("replaces the correct nodes at the correct indexes", () => { + const zero = new Node("replace at 0"); + const one = new Node("replace at 1"); + const two = new Node("replace at 2"); + const three = new Node("replace at 3"); + + [zero, one, two, three].forEach((node, i) => { linkedList.replace(i, node) }); + + expect(linkedList.head).toBe(zero); + expect(linkedList.head.next).toBe(one); + expect(linkedList.head.next.next).toBe(two); + expect(linkedList.head.next.next.next).toBe(three); + expect(linkedList.head.next.next.next.next).toBe(null); + }); + }); + + describe("insert()", () => { + test("can insert a node at the beginning of the list", () => { + const newNode = new Node("hi"); + oneItemList.insert(0, newNode); + + expect(oneItemList.head).toBe(newNode); + expect(oneItemList.head.next).toBe(justOne); + expect(oneItemList.head.next.next).toBe(null); + }); + + test("can insert a node at the very end of the list (making a new tail)", () => { + const newNode = new Node("hi"); + oneItemList.insert(1, newNode); + + expect(oneItemList.head).toBe(justOne); + expect(oneItemList.head.next).toBe(newNode); + expect(oneItemList.head.next.next).toBe(null); + }); + + test("can insert a node in the middle of a list", () => { + const newNode = new Node("hi"); + linkedList.insert(2, newNode); + + expect(linkedList.head.next).toBe(nodeTwo); + expect(linkedList.head.next.next).toBe(newNode); + expect(linkedList.head.next.next.next).toBe(nodeThree); + }); + }); + + describe("remove()", () => { + let consoleOutput = []; + const mockOutput = output => consoleOutput.push(output); + beforeEach(() => (console.log = mockOutput)); + afterEach(() => (consoleOutput = [])); + + test("returns the removed node", () => { + expect(linkedList.remove(3)).toBe(nodeFour); + }); + + test("removes the correct node", () => { + const removed = linkedList.remove(1); + let found = false; + + linkedList.iterate(node => { + if (node === removed) { + founds = true; + } + }); + + expect(found).toBe(false); + }); + + test("keeps the list intact when a node is removed", () => { + linkedList.remove(2); + linkedList.print(); + + expect(consoleOutput).toEqual(['one', 'two', 'four']); + }); + }); + + describe("clear()", () => { + test("empties the list", () => { + linkedList.clear(); + + expect(linkedList.head).toBe(null); + }); + }); + + describe("tail", () => { + test("is null for an empty list", () => { + expect(emptyList.tail).toBe(null); + }); + + test("is a Node when a Node is provided on list initialization", () => { + expect(oneItemList.tail).toBe(oneItemList.head); + }); + + test("is the last Node when a Node connected to other Nodes is provided on initialization", () => { + expect(linkedList.tail.value).toBe("four"); + }); + + test("is the correct Node when #addFirst is called on a list", () => { + [emptyList, oneItemList, linkedList].forEach(l => l.addFirst(new Node('testing'))); + + expect(emptyList.tail.value).toBe("testing"); + expect(oneItemList.tail.value).toBe("just one"); + expect(linkedList.tail.value).toBe("four"); + }); + + test("is the correct Node when #addLast is called on a list", () => { + [emptyList, oneItemList, linkedList].forEach(l => l.addLast(new Node('testing'))); + + expect(emptyList.tail.value).toBe("testing"); + expect(oneItemList.tail.value).toBe("testing"); + expect(linkedList.tail.value).toBe("testing"); + }); + + test("is the correct Node when #removeFirst is called on a list", () => { + [emptyList, oneItemList, linkedList].forEach(l => l.removeFirst()); + + expect(emptyList.tail).toBe(null); + expect(oneItemList.tail).toBe(null); + expect(linkedList.tail.value).toBe("four"); + }); + + test("is the correct Node when #removeLast is called on a list", () => { + [emptyList, oneItemList, linkedList].forEach(l => l.removeLast()); + + expect(emptyList.tail).toBe(null); + expect(oneItemList.tail).toBe(null); + expect(linkedList.tail.value).toBe("three"); + }); + + test("is the correct Node when #replace is called on a list", () => { + linkedList.replace(3, new Node('should be me')); + linkedList.replace(1, new Node('hi')); + + expect(linkedList.tail.value).toBe("should be me"); + }); + + test("is the correct Node when #insert is called on a list", () => { + linkedList.insert(3, new Node('should not be me')); + linkedList.insert(5, new Node('should be me')); + linkedList.insert(1, new Node('hi')); + + expect(linkedList.tail.value).toBe("should be me"); + }); + + test("is the correct Node when #remove is called", () => { + linkedList.remove(0); + expect(linkedList.tail.value).toBe("four"); + + linkedList.remove(1); + expect(linkedList.tail.value).toBe("four"); + + linkedList.remove(1); + expect(linkedList.tail.value).toBe("two"); + }); + }); + + describe("size", () => { + test("should be 0 when a new empty list is initialized", () => { + expect(emptyList.size).toBe(0); + }); + + test("should be the corrrect value when a new list is initialized with Node/s", () => { + expect(oneItemList.size).toBe(1); + expect(linkedList.size).toBe(4); + }); + + test("increases when a Node is added first or last", () => { + emptyList.addFirst(new Node('hi')); + emptyList.addLast(new Node("bye")); + emptyList.addFirst(new Node("stuff")); + + expect(emptyList.size).toBe(3); + }); + + test("decreases when a Node is removed from the head or tail", () => { + linkedList.removeFirst(); + linkedList.removeLast(); + linkedList.removeLast(); + + expect(linkedList.size).toBe(1); + }); + + test("does not go below 0", () => { + oneItemList.removeLast(); + oneItemList.removeLast(); + oneItemList.removeFirst(); + oneItemList.removeFirst(); + + expect(oneItemList.size).toBe(0); + }); + + test("increases when a Node is inserted", () => { + oneItemList.insert(0, new Node("hi")); + oneItemList.insert(1, new Node("hi")); + + expect(oneItemList.size).toBe(3); + }); + + test("decreases when a Node is removed at an index", () => { + linkedList.remove(0); + linkedList.remove(2); + + expect(linkedList.size).toBe(2); + }); + + test("does not go below 0 when a Node is removed at an index", () => { + linkedList.remove(0); + linkedList.remove(0); + linkedList.remove(0); + linkedList.remove(0); + linkedList.remove(0); + + expect(linkedList.size).toBe(0); + }); + + test("is set to 0 when a list is cleared", () => { + linkedList.clear(); + + expect(linkedList.size).toBe(0); + }); + }); +}); diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/linked_list.png b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/linked_list.png new file mode 100644 index 00000000..0c241dc5 Binary files /dev/null and b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/linked_list.png differ diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/.rspec b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/Gemfile b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/linked_list.rb b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/linked_list.rb new file mode 100644 index 00000000..f2757af0 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/linked_list.rb @@ -0,0 +1,138 @@ +class LinkedList + attr_accessor :head + attr_reader :size, :tail + + def initialize(head = nil) + @head = head + end + + def iterate + count = 0 + temp = @head + + until temp.nil? + yield(temp, count) + temp = temp.next_node + count += 1 + end + + @head + end + + def print + iterate { |node| puts node.value } + end + + def find(target) + iterate do |node| + return node if node.value == target + end + + nil + end + + def add_first(node) + node.next_node = @head + @head = node + end + + def add_last(node) + if @head.nil? + @head = node + return + end + + iterate do |curr_node| + if curr_node.next_node.nil? + curr_node.next_node = node + return + end + end + end + + def remove_first + old_head = @head + @head = @head.next_node unless @head.nil? + old_head + end + + def remove_last + return remove_first if @head.nil? || @head.next_node.nil? + + iterate do |node| + if node.next_node.next_node.nil? + old_tail = node.next_node + node.next_node = nil + return old_tail + end + end + end + + def replace(idx, node) + if idx.zero? + remove_first + add_first(node) + return node + end + + iterate do |curr_node, count| + if count == idx - 1 + node.next_node = curr_node.next_node.next_node + curr_node.next_node = node + return node + end + end + end + + def insert(idx, node) + if idx.zero? + add_first(node) + return + end + + iterate do |curr_node, count| + if count == idx - 1 + old_next = curr_node.next_node + curr_node.next_node = node + node.next_node = old_next + return + end + end + end + + def remove(idx) + if idx.zero? + return remove_first + end + + iterate do |node, count| + if count == idx - 1 + old_node = node.next_node + node.next_node = node.next_node.next_node + return old_node + end + end + end + + def clear + @head = nil + end +end + +class Node + attr_accessor :value, :next_node + + def initialize(value = nil, next_node = nil) + @value = value + @next_node = next_node + end +end + +if __FILE__ == $PROGRAM_NAME + head = Node.new('one', Node.new('two', Node.new('three', Node.new('four')))) + list = LinkedList.new(head) + empty_list = LinkedList.new +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/spec/linked_list_spec.rb b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/spec/linked_list_spec.rb new file mode 100644 index 00000000..4802bbcc --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/spec/linked_list_spec.rb @@ -0,0 +1,395 @@ +require "./linked_list" + +RSpec.describe "Node" do + let(:node) { Node.new("hi", "there") } + + context "#initialize with arguments" do + it "sets an attribute called value to the argument" do + expect(node.value).to eq("hi") + end + + it "sets an attribute called next_node to the argument" do + expect(node.next_node).to eq("there") + end + end + + context "#initialize without arguments" do + it "sets an attribute called value to nil" do + expect(Node.new.value).to eq(nil) + end + + it "sets an attribute called next_node to nil" do + expect(Node.new.next_node).to eq(nil) + end + end +end + +RSpec.describe "LinkedList" do + let(:linked_list) { LinkedList.new(Node.new("one", Node.new("two", Node.new("three", Node.new("four"))))) } + let(:empty_list) { LinkedList.new } + let(:one_item_list) { LinkedList.new(Node.new("just one")) } + + context "#initialize" do + it "sets head to nil if no argument is provided" do + expect(empty_list.head).to eq(nil) + end + + it "sets head to the argument if an argument is provided" do + head = Node.new("hi") + list = LinkedList.new(head) + + expect(list.head).to eq(head) + end + end + + context "#iterate" do + it "calls the provided callback on every list node and provides the node as an argument to the callback" do + values = [] + linked_list.iterate { |node| values << node } + + expect(values.length).to eq(4) + expect(values.last).to be_a_kind_of(Node) + end + + it "can handle an empty list" do + values = [] + empty_list.iterate { |node| values << node } + + expect(values.length).to eq(0) + end + + it "can handle a list with only a head" do + values = [] + one_item_list.iterate { |node| values << node } + + expect(values.length).to eq(1) + expect(values.last).to be_a_kind_of(Node) + end + end + + context "#print" do + it "prints nothing at all when the list is empty" do + expect { empty_list.print }.to_not output.to_stdout + end + + it "prints a single line for a list with one node" do + expect { one_item_list.print }.to output("just one\n").to_stdout + end + + it "prints all nodes" do + expect { linked_list.print }.to output("one\ntwo\nthree\nfour\n").to_stdout + end + end + + context "#find" do + it "returns nil when the list is empty" do + expect(empty_list.find(4)).to be_nil + end + + it "returns the correct Node when a Node with that value is in the list" do + expect(linked_list.find("two")).to be(linked_list.head.next_node) + end + + it "returns nil when the value is not in the list" do + expect(linked_list.find("nope")).to be_nil + end + end + + context "#add_first" do + it "adds the given Node to the beginning of the list without removing any" do + old_head = one_item_list.head + node = Node.new("I'm new") + one_item_list.add_first(node) + + expect(one_item_list.head).to be(node) + expect(one_item_list.head.next_node).to be(old_head) + end + + it "adds the given node to an empty list" do + node = Node.new("I'm new") + empty_list.add_first(node) + + expect(empty_list.head).to be(node) + expect(empty_list.head.next_node).to be_nil + end + end + + context "#add_last" do + it "adds the given Node to the end of the list without removing any" do + old_tail = one_item_list.head + node = Node.new("I'm new") + one_item_list.add_last(node) + + expect(one_item_list.head).to be(old_tail) + expect(one_item_list.head.next_node).to be(node) + end + + it "adds the given node to an empty list" do + node = Node.new("I'm new") + empty_list.add_last(node) + + expect(empty_list.head).to be(node) + expect(empty_list.head.next_node).to be_nil + end + end + + context "#remove_first" do + it "removes and returns the head of the list" do + old_head = linked_list.head + + expect(linked_list.remove_first).to be(old_head) + end + + it "updates the head to the correct Node" do + old_head = linked_list.head + linked_list.remove_first + + expect(linked_list.head).to be(old_head.next_node) + expect(linked_list.head.next_node).to be(old_head.next_node.next_node) + end + + it "does not produce an error when called on an empty list" do + expect { empty_list.remove_first }.to_not raise_exception + end + end + + context "#remove_last" do + it "removes and returns the tail of the list" do + old_tail = linked_list.head.next_node.next_node.next_node + + expect(linked_list.remove_last).to be(old_tail) + expect(linked_list.head.next_node.next_node.next_node).to be_nil + end + + it "makes the node before the old tail the new tail" do + new_tail = linked_list.head.next_node.next_node + + expect(linked_list.head.next_node.next_node).to be(new_tail) + end + + it "does not produce an error when called on an empty list" do + expect { empty_list.remove_last }.to_not raise_exception + end + end + + context "#replace" do + it "returns the inserted node" do + node = Node.new("replacing") + + expect(linked_list.replace(0, node)).to be(node) + end + + it "replaces the correct nodes at the correct indexes" do + node_zero = Node.new("replace at 0") + node_one = Node.new("replace at 1") + node_two = Node.new("replace at 2") + node_three = Node.new("replace at 3") + + [node_zero, node_one, node_two, node_three].each_with_index { |node, idx| linked_list.replace(idx, node) } + + expect(linked_list.head).to be(node_zero) + expect(linked_list.head.next_node).to be(node_one) + expect(linked_list.head.next_node.next_node).to be(node_two) + expect(linked_list.head.next_node.next_node.next_node).to be(node_three) + expect(linked_list.head.next_node.next_node.next_node.next_node).to be_nil + end + end + + context "#insert" do + it "can insert a node at the beginning of the list" do + old_head = one_item_list.head + node = Node.new('hi') + one_item_list.insert(0, node) + + expect(one_item_list.head).to be(node) + expect(one_item_list.head.next_node).to be(old_head) + expect(one_item_list.head.next_node.next_node).to be_nil + end + + it "can insert a node at the very end of the list (making a new tail)" do + old_head = one_item_list.head + node = Node.new('hi') + one_item_list.insert(1, node) + + expect(one_item_list.head).to be(old_head) + expect(one_item_list.head.next_node).to be(node) + expect(one_item_list.head.next_node.next_node).to be_nil + end + + it "can insert a node in the middle of a list" do + node = Node.new('hi') + linked_list.insert(2, node) + + expect { linked_list.print }.to output("one\ntwo\nhi\nthree\nfour\n").to_stdout + end + end + + context "#remove" do + it "returns the removed node" do + to_remove = linked_list.head.next_node.next_node.next_node + + expect(linked_list.remove(3)).to be(to_remove) + end + + it "removes the correct node" do + removed = linked_list.remove(1) + + found = false + linked_list.iterate { |node| found = true if node == removed } + + expect(found).to be false + end + + it "keeps the list intact when a node is removed" do + linked_list.remove(2) + + expect { linked_list.print }.to output("one\ntwo\nfour\n").to_stdout + end + end + + context "#clear" do + it "empties the list" do + linked_list.clear + + expect(linked_list.head).to be_nil + end + end + + context "#tail" do + it "is nil for an empty list" do + expect(empty_list.tail).to be_nil + end + + it "is a Node when a Node is provided on list initialization" do + expect(one_item_list.tail).to be(one_item_list.head) + end + + it "is the last Node when a Node connected to other Nodes is provided on initialization" do + expect(linked_list.tail.value).to eq("four") + end + + it "is the correct Node when #add_first is called on a list" do + [empty_list, one_item_list, linked_list].each { |l| l.add_first(Node.new('testing')) } + + expect(empty_list.tail.value).to eq("testing") + expect(one_item_list.tail.value).to eq("just one") + expect(linked_list.tail.value).to eq("four") + end + + it "is the correct Node when #add_last is called on a list" do + [empty_list, one_item_list, linked_list].each { |l| l.add_last(Node.new('testing')) } + + expect(empty_list.tail.value).to eq("testing") + expect(one_item_list.tail.value).to eq("testing") + expect(linked_list.tail.value).to eq("testing") + end + + it "is the correct Node when #remove_first is called on a list" do + [empty_list, one_item_list, linked_list].each { |l| l.remove_first } + + expect(empty_list.tail).to be_nil + expect(one_item_list.tail).to be_nil + expect(linked_list.tail.value).to eq("four") + end + + it "is the correct Node when #remove_last is called on a list" do + [empty_list, one_item_list, linked_list].each { |l| l.remove_last } + + expect(empty_list.tail).to be_nil + expect(one_item_list.tail).to be_nil + expect(linked_list.tail.value).to eq("three") + end + + it "is the correct Node when #replace is called on a list" do + linked_list.replace(3, Node.new('should be me')) + linked_list.replace(1, Node.new('hi')) + + expect(linked_list.tail.value).to eq("should be me") + end + + it "is the correct Node when #insert is called on a list" do + linked_list.insert(3, Node.new('should not be me')) + linked_list.insert(5, Node.new('should be me')) + linked_list.insert(1, Node.new('hi')) + + expect(linked_list.tail.value).to eq("should be me") + end + + it "is the correct Node when #remove is called" do + linked_list.remove(0) + expect(linked_list.tail.value).to eq("four") + + linked_list.remove(1) + expect(linked_list.tail.value).to eq("four") + + linked_list.remove(1) + expect(linked_list.tail.value).to eq("two") + end + end + + context "#size" do + it "should be 0 when a new empty list is initialized" do + expect(empty_list.size).to eq(0) + end + + it "should be the corrrect value when a new list is initialized with Node/s" do + expect(one_item_list.size).to eq(1) + expect(linked_list.size).to eq(4) + end + + it "increases when a Node is added first or last" do + empty_list.add_first(Node.new('hi')) + empty_list.add_last(Node.new("bye")) + empty_list.add_first(Node.new("stuff")) + + expect(empty_list.size).to eq(3) + end + + it "decreases when a Node is removed from the head or tail" do + linked_list.remove_first + linked_list.remove_last + linked_list.remove_last + + expect(linked_list.size).to eq(1) + end + + it "does not go below 0" do + one_item_list.remove_last + one_item_list.remove_last + one_item_list.remove_first + one_item_list.remove_first + + expect(one_item_list.size).to eq(0) + end + + it "increases when a Node is inserted" do + one_item_list.insert(0, Node.new("hi")) + one_item_list.insert(1, Node.new("hi")) + + expect(one_item_list.size).to eq(3) + end + + it "decreases when a Node is removed at an index" do + linked_list.remove(0) + linked_list.remove(2) + + expect(linked_list.size).to eq(2) + end + + it "does not go below 0 when a Node is removed at an index" do + linked_list.remove(0) + linked_list.remove(0) + linked_list.remove(0) + linked_list.remove(0) + linked_list.remove(0) + + expect(linked_list.size).to eq(0) + end + + it "is set to 0 when a list is cleared" do + linked_list.clear + + expect(linked_list.size).to eq(0) + end + end +end \ No newline at end of file diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/spec/spec_helper.rb b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/solutions/linked_list.js b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/solutions/linked_list.js new file mode 100644 index 00000000..59de2232 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/solutions/linked_list.js @@ -0,0 +1,366 @@ +class LinkedList { + constructor(head = null) { + this.head = head; + this.tail = null; + this.size = 0; + // worst case O(n) for setting tail on initializing new list + this.setTailAndSize(); + } + + // helper method for setting tail when list goes from empty to not empty + // upon initialization + // will be O(n) because user could add Node connected to other Nodes + setTailAndSize() { + this.size = 0; + + this.iterate((node) => { + this.tail = node; + this.updateSize('+'); + }); + } + + // helper method for updating size + updateSize(operation) { + operation === '+' ? ++this.size : --this.size; + } + + iterate(callback) { + let count = 0; + let temp = this.head; + + while (temp !== null) { + const result = callback(temp, count); + + if (result === true) { + return temp; + } + + ++count; + temp = temp.next; + } + + return this.head; + } + + // print each node's value on its own line + // use your iterate method to be DRY! Don't get caught in the code rain, brrr. + print() { + this.iterate(node => console.log(node.value)); + } + + // find the node with the target value and return it + // if not found return null, use your iterate method to be DRY! + find(target) { + let result = null; + + this.iterate(node => { + if (node.value === target) { + result = node; + + return true; + } + }); + + return result; + } + + // add the node to the start of the list, no nodes should be removed + addFirst(node) { + // O(1) operation: the tail will only be set if the list is empty + if (this.head === null) { + this.tail = node; + } + + node.next = this.head; + this.head = node; + + // increase size: O(1) + this.updateSize('+'); + } + + // add node to end of list, no nodes should be removed + // you may wish to use the iterate method + addLast(node) { + if (this.head === null) { + this.addFirst(node); + return; + } + + this.tail.next = node; + // O(1) operation to set tail + this.tail = node; + // increase size: O(1) + this.updateSize('+'); + + // if (this.head === null) { + // this.head = node; + // } + + // this.iterate(currNode => { + // if (currNode.next === null) { + // currNode.next = node; + // return true; + // } + // }); + } + + // remove the first Node in the list and update head + // and return the removed node + removeFirst() { + const oldHead = this.head; + + if (this.head !== null) { + this.head = this.head.next; + this.updateSize('-'); + } + + // if list goes from 1 Node to empty, update tail O(1) + if (this.head === null) { + this.tail = null; + } + + return oldHead; + } + + // remove the tail node, iterate may be helpful + // return the node you just removed + removeLast() { + if (this.head === null || this.head.next === null) { + return this.removeFirst(); + } + + let oldTail = null; + + this.iterate(node => { + if (node.next.next === null) { + oldTail = node.next; + node.next = null; + return true; + } + }); + + // Could also set the tail inside iterate above + // O(n) operation + this.setTailAndSize(); + + return oldTail; + } + + // replace the node at the given index with the given node + replace(idx, node) { + if (idx === 0) { + this.removeFirst(); + this.addFirst(node); + return node; + } + + this.iterate((currNode, count) => { + if (count === idx - 1) { + node.next = currNode.next.next; + currNode.next = node; + + // If tail is being replaced O(1) + if (node.next === null) { + this.tail = node; + } + + return true; + } + }); + + return node; + } + + // insert the node at the given index + // no existing nodes should be removed or replaced + insert(idx, node) { + if (idx === 0) { + this.addFirst(node); + return; + } + + this.iterate((currNode, count) => { + if (count === idx - 1) { + const oldNext = currNode.next; + currNode.next = node; + node.next = oldNext; + + // if new node is inserted at the very end, O(1) + if (oldNext === null) { + this.tail = node; + } + + return true; + } + }); + + this.updateSize('+'); + } + + // remove the node at the given index, and return it + remove(idx) { + if (idx === 0) { + return this.removeFirst(); + } + + let oldNode = null; + + this.iterate((node, count) => { + if (count === idx - 1) { + oldNode = node.next; + node.next = node.next.next; + // if tail was removed, update tail + if (node.next === null) { + this.tail = node; + } + + return true; + } + }); + + this.updateSize('-'); + + return oldNode; + } + + clear() { + this.head = null; + // null tail for empty list + this.tail = null; + this.size = 0; + } +} + +class Node { + constructor(value = null, next = null) { + this.value = value; + this.next = next; + } +} + +if (require.main === module) { + let head = new Node('one', new Node('two', new Node('three', new Node('four')))); + let list = new LinkedList(head); + let emptyList = new LinkedList(); + let oneItemList = new LinkedList(new Node('just one')); + + console.log("CHECK FOR CORRECT TAIL ON INIT"); + console.log(`Should be 'four': ${list.tail.value}`); + console.log(`Should be 'null': ${emptyList.tail}`); + console.log(`Should be 'just one': ${oneItemList.tail.value}`); + console.log("---------------------------------------------------"); + + console.log("CHECK FOR CORRECT TAIL ON ADD FIRST"); + list.addFirst(new Node('beans')); + emptyList.addFirst(new Node('beans')); + oneItemList.addFirst(new Node('beans')); + console.log(`Should be 'four': ${list.tail.value}`); + console.log(`Should be 'beans': ${emptyList.tail.value}`); + console.log(`Should be 'just one': ${oneItemList.tail.value}`); + [list, emptyList, oneItemList].forEach(l => l.removeFirst()); + console.log("---------------------------------------------------"); + + console.log("CHECK FOR CORRECT TAIL ON ADD LAST"); + list.addLast(new Node('beans')); + emptyList.addLast(new Node('beans')); + oneItemList.addLast(new Node('beans')); + console.log(`Should be 'beans': ${list.tail.value}`); + console.log(`Should be 'beans': ${emptyList.tail.value}`); + console.log(`Should be 'beans': ${oneItemList.tail.value}`); + [list, emptyList, oneItemList].forEach(l => { + l.removeLast(); + l.setTailAndSize(); + }); + console.log("---------------------------------------------------"); + + console.log("CHECK FOR CORRECT TAIL ON REMOVE FIRST"); + let removed = list.removeFirst(); + let removedAgain = oneItemList.removeFirst(); + emptyList.removeFirst(); + console.log(`Should be 'four': ${list.tail.value}`); + console.log(`Should be 'null': ${emptyList.tail}`); + console.log(`Should be 'null': ${oneItemList.tail}`); + list.addFirst(removed); + oneItemList.addFirst(removedAgain); + console.log("---------------------------------------------------"); + + console.log("CHECK FOR CORRECT TAIL ON REMOVE LAST"); + removed = list.removeLast(); + removedAgain = oneItemList.removeLast(); + emptyList.removeLast(); + console.log(`Should be 'three': ${list.tail.value}`); + console.log(`Should be 'null': ${emptyList.tail}`); + console.log(`Should be 'null': ${oneItemList.tail}`); + list.addLast(removed); + oneItemList.addLast(removedAgain); + console.log("---------------------------------------------------"); + + console.log("CHECK FOR CORRECT TAIL ON REPLACE"); + let nodeTwo = new Node('two'); + let nodeOne = new Node('one', nodeTwo); + let replaceList = new LinkedList(nodeOne); + + replaceList.replace(0, new Node('hi')); + console.log(`Should be two: ${replaceList.tail.value}`); + replaceList.replace(1, new Node('bye')); + console.log(`Should be bye: ${replaceList.tail.value}`); + console.log("---------------------------------------------------"); + + console.log("CHECK FOR CORRECT TAIL ON INSERT"); + nodeTwo = new Node('two'); + nodeOne = new Node('one', nodeTwo); + insertList = new LinkedList(nodeOne); + + insertList.insert(0, new Node('hi')); + // hi -> one -> two + console.log(`Should be two: ${insertList.tail.value}`); + insertList.insert(1, new Node('bye')); + // hi -> bye -> one -> two + console.log(`Should be two: ${insertList.tail.value}`); + insertList.insert(4, new Node('last')); + // hi -> bye -> one -> two -> last + console.log(`Should be last: ${insertList.tail.value}`); + console.log("---------------------------------------------------"); + + console.log("CHECK FOR CORRECT TAIL ON REMOVE"); + // one -> two -> three -> four + list.remove(1); + // one -> three -> four + console.log(`Should be four: ${list.tail.value}`); + list.remove(2); + // one -> three + console.log(`Should be three: ${list.tail.value}`); + list.insert(1, new Node('two')); + list.addLast(new Node('four')); + console.log("---------------------------------------------------"); + + console.log("CHECK FOR CORRECT TAIL ON CLEAR"); + list.clear(); + console.log(`Should be null: ${list.tail}`); + head = new Node('one', new Node('two', new Node('three', new Node('four')))); + list = new LinkedList(head); + console.log("---------------------------------------------------"); + + console.log("CHECKING SIZE"); + console.log(`New empty list should be 0: ${new LinkedList().size}`); + console.log(`New list with one Node should be 1: ${new LinkedList(new Node('hi')).size}`); + console.log(`New list with 3 nodes should be 3: ${new LinkedList(new Node('hi', new Node('bye', new Node('what')))).size}`); + list.addFirst(new Node('s')); + console.log(`On addFirst should be 5: ${list.size}`); + list.addLast(new Node('b')); + console.log(`On addLast should be 6: ${list.size}`); + list.removeFirst(); + console.log(`On removeFirst should be 5: ${list.size}`); + list.removeLast(); + console.log(`On removeLast should be 4: ${list.size}`); + list.insert(1, new Node('stuff')); + console.log(`On insert should be 5: ${list.size}`); + list.remove(1); + console.log(`On remove should be 4: ${list.size}`); + list.clear(); + console.log(`On clear should be 0: ${list.size}`); +} + +module.exports = { + Node, LinkedList +}; diff --git a/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/solutions/linked_list.rb b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/solutions/linked_list.rb new file mode 100644 index 00000000..c31e0276 --- /dev/null +++ b/07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size/solutions/linked_list.rb @@ -0,0 +1,309 @@ +class LinkedList + attr_accessor :head + attr_reader :size, :tail + + def initialize(head = nil) + @head = head + @tail = head + @size = 0 + + set_size_and_tail + end + + def set_size_and_tail + @size = 0 + + iterate do |node| + @tail = node + update_size('+') + end + end + + def update_size(operation) + operation === '+' ? @size += 1 : @size -= 1 + end + + def iterate + count = 0 + temp = @head + + until temp.nil? + yield(temp, count) + temp = temp.next_node + count += 1 + end + + @head + end + + def print + iterate { |node| puts node.value } + end + + def find(target) + iterate do |node| + return node if node.value == target + end + + nil + end + + def add_first(node) + if @head.nil? + @tail = node + end + + node.next_node = @head + @head = node + + update_size('+') + end + + def add_last(node) + if @head.nil? + add_first(node) + return + end + + @tail.next_node = node + @tail = node + update_size('+') + + # if @head.nil? + # @head = node + # end + + # iterate do |curr_node| + # if curr_node.next_node.nil? + # curr_node.next_node = node + # return + # end + # end + end + + def remove_first + old_head = @head + + unless @head.nil? + @head = @head.next_node + update_size('-') + end + + if @head.nil? + @tail = nil + end + + old_head + end + + def remove_last + return remove_first if @head.nil? || @head.next_node.nil? + + update_size('-') + + iterate do |node| + if node.next_node.next_node.nil? + old_tail = node.next_node + node.next_node = nil + @tail = node + return old_tail + end + end + end + + def replace(idx, node) + if idx.zero? + remove_first + add_first(node) + return node + end + + iterate do |curr_node, count| + if count == idx - 1 + node.next_node = curr_node.next_node.next_node + curr_node.next_node = node + + @tail = node if node.next_node.nil? + + return node + end + end + end + + def insert(idx, node) + if idx.zero? + add_first(node) + return + end + + update_size('+') + + iterate do |curr_node, count| + if count == idx - 1 + old_next = curr_node.next_node + curr_node.next_node = node + node.next_node = old_next + + @tail = node if node.next_node.nil? + + return + end + end + end + + def remove(idx) + return remove_first if idx.zero? + + update_size('-') + + iterate do |node, count| + if count == idx - 1 + old_node = node.next_node + node.next_node = node.next_node.next_node + + @tail = node if node.next_node.nil? + + return old_node + end + end + end + + def clear + @head = nil + @tail = nil + @size = 0 + end +end + +class Node + attr_accessor :value, :next_node + + def initialize(value = nil, next_node = nil) + @value = value + @next_node = next_node + end +end + +if __FILE__ == $PROGRAM_NAME + head = Node.new('one', Node.new('two', Node.new('three', Node.new('four')))) + list = LinkedList.new(head) + empty_list = LinkedList.new + one_item_list = LinkedList.new(Node.new('just one')) + + puts("CHECK FOR CORRECT TAIL ON INIT"); + puts("Should be 'four': #{list.tail.value}"); + puts("Should be 'null': #{empty_list.tail}"); + puts("Should be 'just one': #{one_item_list.tail.value}"); + puts("---------------------------------------------------"); + + puts("CHECK FOR CORRECT TAIL ON ADD FIRST"); + list.add_first(Node.new('beans')) + empty_list.add_first(Node.new('beans')) + one_item_list.add_first(Node.new('beans')) + puts("Should be 'four': #{list.tail.value}") + puts("Should be 'beans': #{empty_list.tail.value}") + puts("Should be 'just one': #{one_item_list.tail.value}") + [list, empty_list, one_item_list].each { |l| l.remove_first } + puts("---------------------------------------------------") + + puts("CHECK FOR CORRECT TAIL ON ADD LAST") + list.add_last(Node.new('beans')) + empty_list.add_last(Node.new('beans')) + one_item_list.add_last(Node.new('beans')) + puts("Should be 'beans': #{list.tail.value}") + puts("Should be 'beans': #{empty_list.tail.value}") + puts("Should be 'beans': #{one_item_list.tail.value}") + [list, empty_list, one_item_list].each do |l| + l.remove_last + l.set_size_and_tail + end + puts("---------------------------------------------------") + + puts("CHECK FOR CORRECT TAIL ON REMOVE FIRST") + removed = list.remove_first + removed_again = one_item_list.remove_first + empty_list.remove_first + puts("Should be 'four': #{list.tail.value}") + puts("Should be 'null': #{empty_list.tail}") + puts("Should be 'null': #{one_item_list.tail}") + list.add_first(removed) + one_item_list.add_first(removed_again) + puts("---------------------------------------------------") + + puts("CHECK FOR CORRECT TAIL ON REMOVE LAST") + removed = list.remove_last + removed_again = one_item_list.remove_last + empty_list.remove_last + puts("Should be 'three': #{list.tail.value}") + puts("Should be 'null': #{empty_list.tail}") + puts("Should be 'null': #{one_item_list.tail}") + list.add_last(removed) + one_item_list.add_last(removed_again) + puts("---------------------------------------------------") + + puts("CHECK FOR CORRECT TAIL ON REPLACE") + node_two = Node.new('two') + node_one = Node.new('one', node_two) + replace_list = LinkedList.new(node_one) + + replace_list.replace(0, Node.new('hi')) + puts("Should be two: #{replace_list.tail.value}") + replace_list.replace(1, Node.new('bye')) + puts("Should be bye: #{replace_list.tail.value}") + puts("---------------------------------------------------") + + puts("CHECK FOR CORRECT TAIL ON INSERT") + node_two = Node.new('two') + node_one = Node.new('one', node_two) + insert_list = LinkedList.new(node_one) + + insert_list.insert(0, Node.new('hi')) + # hi -> one -> two + puts("Should be two: #{insert_list.tail.value}") + insert_list.insert(1, Node.new('bye')) + # hi -> bye -> one -> two + puts("Should be two: #{insert_list.tail.value}") + insert_list.insert(4, Node.new('last')) + # hi -> bye -> one -> two -> last + puts("Should be last: #{insert_list.tail.value}") + puts("---------------------------------------------------") + + puts("CHECK FOR CORRECT TAIL ON REMOVE") + # one -> two -> three -> four + list.remove(1) + # one -> three -> four + puts("Should be four: #{list.tail.value}") + list.remove(2) + # one -> three + puts("Should be three: #{list.tail.value}") + list.insert(1, Node.new('two')) + list.add_last(Node.new('four')) + puts("---------------------------------------------------") + + puts("CHECK FOR CORRECT TAIL ON CLEAR") + list.clear + puts("Should be null: #{list.tail}") + head = Node.new('one', Node.new('two', Node.new('three', Node.new('four')))) + list = LinkedList.new(head) + puts("---------------------------------------------------") + + puts("CHECKING SIZE") + puts("New empty list should be 0: #{LinkedList.new.size}") + puts("New list with one Node should be 1: #{LinkedList.new(Node.new('hi')).size}") + puts("New list with 3 nodes should be 3: #{LinkedList.new(Node.new('hi', Node.new('bye', Node.new('what')))).size}") + list.add_first(Node.new('s')) + puts("On addFirst should be 5: #{list.size}") + list.add_last(Node.new('b')) + puts("On addLast should be 6: #{list.size}") + list.remove_first + puts("On removeFirst should be 5: #{list.size}") + list.remove_last + puts("On removeLast should be 4: #{list.size}") + list.insert(1, Node.new('stuff')) + puts("On insert should be 5: #{list.size}") + list.remove(1) + puts("On remove should be 4: #{list.size}") + list.clear + puts("On clear should be 0: #{list.size}") +end + diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/.gitignore b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/README.md b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/README.md new file mode 100644 index 00000000..c0c7adb5 --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/README.md @@ -0,0 +1,90 @@ +# Bonus: Build a Doubly Linked List + +Today we'll be implementing a Doubly Linked List. A Doubly Linked List is like a singly-linked list, except it has an extra attribute on each Node: a `prev` pointer that points to the previous Node. + +![Linked List](./linked_list.png) + +## Implement a Doubly Linked List + +For this challenge, assume that only one Node is added at a time, including upon initialization of a new list. + +### 1. Modify the `Node` Class + +Each node should have a pointer called `prev` that points to the Node that comes before it. If no Node comes before it, it should be a falsy value, such as `null` in JS or `nil` in Ruby. + +``` +node = new Node('first') +node.prev +=> nil or null + +node.prev = new Node('zeroth') +node.prev +=> Node with value 'zeroth' +``` + +### 2. Modify the `LinkedList` Class + +Look through the methods and determine which need to be modified in order to ensure that a Node's `prev` attribute always points to the correct Node. + +``` +list = new LinkedList +list.add_first(new Node('zeroth')) +list.head +=> Node with value 'zeroth' +list.head.prev +=> nil or null + +list.add_first(new Node('less than zero')) +list.head +=> Node with value 'less than zero' +list.head.next +=> Node with value 'zeroth' +list.head.next.prev +=> Node with value 'less than zero' + +list.remove_first +list.head +=> Node with value 'zeroth' +list.head.prev +=> nil or null +``` + +Use the language of your choosing. We've included code from the original LinkedList implementation. You may also copy and paste your own. + +We've also included the original LinkedList tests, so you can ensure that your code still functions correctly. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/javascript/doubly_linked_list.js b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/javascript/doubly_linked_list.js new file mode 100644 index 00000000..622ca4c3 --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/javascript/doubly_linked_list.js @@ -0,0 +1,178 @@ +class DoublyLinkedList { + constructor(head = null) { + this.head = head; + } + + iterate(callback) { + let count = 0; + let temp = this.head; + + while (temp !== null) { + const result = callback(temp, count); + + if (result === true) { + return temp; + } + + ++count; + temp = temp.next; + } + + return this.head; + } + + // print each node's value on its own line + // use your iterate method to be DRY! Don't get caught in the code rain, brrr. + print() { + this.iterate(node => console.log(node.value)); + } + + // find the node with the target value and return it + // if not found return null, use your iterate method to be DRY! + find(target) { + let result = null; + + this.iterate(node => { + if (node.value === target) { + result = node; + + return true; + } + }); + + return result; + } + + // add the node to the start of the list, no nodes should be removed + addFirst(node) { + node.next = this.head; + this.head = node; + } + + // add node to end of list, no nodes should be removed + // you may wish to use the iterate method + addLast(node) { + if (this.head === null) { + this.head = node; + return; + } + + this.iterate(currNode => { + if (currNode.next === null) { + currNode.next = node; + return true; + } + }); + } + + // remove the first Node in the list and update head + // and return the removed node + removeFirst() { + const oldHead = this.head; + + if (this.head !== null) { + this.head = this.head.next; + } + + return oldHead; + } + + // remove the tail node, iterate may be helpful + // return the node you just removed + removeLast() { + if (this.head === null || this.head.next === null) { + return this.removeFirst(); + } + + let oldTail = null; + + this.iterate(node => { + if (node.next.next === null) { + oldTail = node.next; + node.next = null; + return true; + } + }); + + return oldTail; + } + + // replace the node at the given index with the given node + replace(idx, node) { + if (idx === 0) { + this.removeFirst(); + this.addFirst(node); + return node; + } + + this.iterate((currNode, count) => { + if (count === idx - 1) { + node.next = currNode.next.next; + currNode.next = node; + + return true; + } + }); + + return node; + } + + // insert the node at the given index + // no existing nodes should be removed or replaced + insert(idx, node) { + if (idx === 0) { + this.addFirst(node); + return; + } + + this.iterate((currNode, count) => { + if (count === idx - 1) { + const oldNext = currNode.next; + currNode.next = node; + node.next = oldNext; + + return true; + } + }); + } + + // remove the node at the given index, and return it + remove(idx) { + if (idx === 0) { + return this.removeFirst(); + } + + let oldNode = null; + + this.iterate((node, count) => { + if (count === idx - 1) { + oldNode = node.next; + node.next = node.next.next; + + return true; + } + }); + + return oldNode; + } + + clear() { + this.head = null; + } +} + +class Node { + constructor(value = null, next = null) { + this.value = value; + this.next = next; + } +} + +if (require.main === module) { + // add your own tests in here + +} + +module.exports = { + Node, DoublyLinkedList +}; diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/javascript/package.json b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/javascript/package.json new file mode 100644 index 00000000..ce623a87 --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "doubly_linked_list", + "version": "1.0.0", + "description": "doubly linked list", + "main": "doubly_linked_list.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/javascript/tests/doubly_linked_list.test.js b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/javascript/tests/doubly_linked_list.test.js new file mode 100644 index 00000000..c84bfafc --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/javascript/tests/doubly_linked_list.test.js @@ -0,0 +1,414 @@ +const { Node, DoublyLinkedList } = require("../doubly_linked_list"); + +describe("Node", () => { + const makeNode = () => new Node("hi", "there"); + const emptyNode = new Node(); + + test("sets an attribute called value to the argument on initialization", () => { + expect(makeNode().value).toBe("hi"); + }); + + test("sets an attribute called next to the argument on initialization", () => { + expect(makeNode().next).toBe("there"); + }); + + test("sets an attribute called value to null on initialization when there is no argument", () => { + expect(emptyNode.value).toBe(null); + }); + + test("sets an attribute called next to null on initialization when there is no argument", () => { + expect(emptyNode.next).toBe(null); + }); +}); + +describe("LinkedList", () => { + const nodeFour = new Node("four"); + const nodeThree = new Node("three", nodeFour); + const nodeTwo = new Node("two", nodeThree); + const nodeOne = new Node("one", nodeTwo); + const justOne = new Node("just one"); + + let emptyList = new DoublyLinkedList(); + let oneItemList = new DoublyLinkedList(justOne); + let linkedList = new DoublyLinkedList(nodeOne); + + const consoleLog = console.log; + const LLValues = ["one", "two", "three", "four"]; + + beforeEach(() => { + console.log = consoleLog; + + const values = ["one", "two", "three", "four", "just one"]; + const nodes = [nodeOne, nodeTwo, nodeThree, nodeFour, justOne]; + + nodes.forEach((node, i) => { + node.value = values[i]; + }); + + emptyList.head = null; + + oneItemList.head = justOne; + justOne.next = null; + + linkedList.head = nodeOne; + nodeOne.next = nodeTwo; + nodeTwo.next = nodeThree; + nodeThree.next = nodeFour; + nodeFour.next = null; + }); + + test("head is null if no argument provided on initialization", () => { + expect(emptyList.head).toBe(null); + }); + + test("head is set to the argument if provided on initialization", () => { + expect(oneItemList.head).toBe(justOne); + }); + + describe("iterate()", () => { + test("iterate() calls the provided callback on every node with the node as an argument to the callback", () => { + const values = []; + linkedList.iterate((node) => values.push(node)); + + expect(values.length).toBe(4); + expect(values[3]).toBe(nodeFour); + }); + + test("iterate() can handle an empty list", () => { + const values = []; + emptyList.iterate((node) => values.push(node)); + + expect(values.length).toBe(0); + }); + + test("iterate() can handle a list with one node", () => { + const values = []; + oneItemList.iterate((node) => values.push(node)); + + expect(values.length).toBe(1); + expect(values[0]).toBe(justOne); + }); + }); + + describe("print()", () => { + let consoleOutput = []; + const mockOutput = output => consoleOutput.push(output); + beforeEach(() => (console.log = mockOutput)); + afterEach(() => (consoleOutput = [])); + + test("prints nothing at all when the list is empty", () => { + emptyList.print(); + + expect(consoleOutput.length).toBe(0); + }); + + test("prints a single line for a list with one node", () => { + oneItemList.print(); + + expect(consoleOutput[0]).toBe(justOne.value); + }); + + test("prints all nodes", () => { + linkedList.print(); + + expect(consoleOutput).toEqual(LLValues); + }); + }); + + describe("find()", () => { + test("returns null when the list is empty", () => { + expect(emptyList.find(4)).toBe(null); + }); + + test("returns the correct Node when a Node with that value is in the list", () => { + expect(linkedList.find("two")).toBe(nodeTwo); + }); + + test("returns null when the value is not in the list", () => { + expect(linkedList.find("nope")).toBe(null); + }); + }); + + describe("addFirst()", () => { + test("adds the given Node to the beginning of the list without removing any", () => { + const newNode = new Node("I'm new"); + oneItemList.addFirst(newNode); + + expect(oneItemList.head).toBe(newNode); + expect(oneItemList.head.next).toBe(justOne); + }); + + test("adds the given node to an empty list", () => { + const newNode = new Node("I'm new"); + emptyList.addFirst(newNode); + + expect(emptyList.head).toBe(newNode); + expect(emptyList.head.next).toBe(null); + }); + }); + + describe("addLast()", () => { + test("adds the given Node to the end of the list without removing any", () => { + const newNode = new Node("I'm new"); + oneItemList.addLast(newNode); + + expect(oneItemList.head).toBe(justOne); + expect(oneItemList.head.next).toBe(newNode); + }); + + test("adds the given node to an empty list", () => { + const newNode = new Node("I'm new"); + emptyList.addLast(newNode); + + expect(emptyList.head).toBe(newNode); + expect(emptyList.head.next).toBe(null); + }); + }); + + describe("removeFirst()", () => { + test("removes and returns the head of the list", () => { + expect(linkedList.removeFirst()).toBe(nodeOne); + }); + + test("updates the head to the correct Node", () => { + linkedList.removeFirst(); + + expect(linkedList.head).toBe(nodeTwo); + expect(linkedList.head.next).toBe(nodeThree); + }); + + test("does not produce an error when called on an empty list", () => { + expect(() => emptyList.removeFirst()).not.toThrow(Error); + }); + }); + + describe("removeLast()", () => { + test("removes and returns the tail of the list", () => { + expect(linkedList.removeLast()).toBe(nodeFour); + expect(linkedList.head.next.next.next).toBe(null); + }); + + test("makes the node before the old tail the new tail", () => { + linkedList.removeLast(); + + expect(linkedList.head.next.next).toBe(nodeThree); + }); + + test("does not produce an error when called on an empty list", () => { + expect(() => emptyList.removeLast()).not.toThrow(Error); + }); + }); + + describe("replace()", () => { + test("returns the inserted node", () => { + const newNode = new Node("replacing"); + + expect(linkedList.replace(0, newNode)).toBe(newNode); + }); + + test("replaces the correct nodes at the correct indexes", () => { + const zero = new Node("replace at 0"); + const one = new Node("replace at 1"); + const two = new Node("replace at 2"); + const three = new Node("replace at 3"); + + [zero, one, two, three].forEach((node, i) => { linkedList.replace(i, node) }); + + expect(linkedList.head).toBe(zero); + expect(linkedList.head.next).toBe(one); + expect(linkedList.head.next.next).toBe(two); + expect(linkedList.head.next.next.next).toBe(three); + expect(linkedList.head.next.next.next.next).toBe(null); + }); + }); + + describe("insert()", () => { + test("can insert a node at the beginning of the list", () => { + const newNode = new Node("hi"); + oneItemList.insert(0, newNode); + + expect(oneItemList.head).toBe(newNode); + expect(oneItemList.head.next).toBe(justOne); + expect(oneItemList.head.next.next).toBe(null); + }); + + test("can insert a node at the very end of the list (making a new tail)", () => { + const newNode = new Node("hi"); + oneItemList.insert(1, newNode); + + expect(oneItemList.head).toBe(justOne); + expect(oneItemList.head.next).toBe(newNode); + expect(oneItemList.head.next.next).toBe(null); + }); + + test("can insert a node in the middle of a list", () => { + const newNode = new Node("hi"); + linkedList.insert(2, newNode); + + expect(linkedList.head.next).toBe(nodeTwo); + expect(linkedList.head.next.next).toBe(newNode); + expect(linkedList.head.next.next.next).toBe(nodeThree); + }); + }); + + describe("remove()", () => { + let consoleOutput = []; + const mockOutput = output => consoleOutput.push(output); + beforeEach(() => (console.log = mockOutput)); + afterEach(() => (consoleOutput = [])); + + test("returns the removed node", () => { + expect(linkedList.remove(3)).toBe(nodeFour); + }); + + test("removes the correct node", () => { + const removed = linkedList.remove(1); + let found = false; + + linkedList.iterate(node => { + if (node === removed) { + founds = true; + } + }); + + expect(found).toBe(false); + }); + + test("keeps the list intact when a node is removed", () => { + linkedList.remove(2); + linkedList.print(); + + expect(consoleOutput).toEqual(['one', 'two', 'four']); + }); + }); + + describe("clear()", () => { + test("empties the list", () => { + linkedList.clear(); + + expect(linkedList.head).toBe(null); + }); + }); +}); + +describe("updating prev properly", () => { + let node = new Node('one'); + let list = new DoublyLinkedList(node); + let head = new Node('one'); + let tail = new Node('two'); + let twoList = new DoublyLinkedList(head); + twoList.addLast(tail); + + beforeEach(() => { + node = new Node('one'); + list = new DoublyLinkedList(node); + head = new Node('one'); + tail = new Node('two'); + twoList = new DoublyLinkedList(head); + twoList.addLast(tail); + }); + + describe("addFirst()", () => { + test("updates the former head's previous node to the node being added", () => { + list.addFirst(new Node('zero')); + + expect(list.head.next).toBe(node); + expect(node.prev.value).toBe('zero'); + }); + }); + + describe("addLast()", () => { + test("updates the former head's previous node to the node being added", () => { + const lastNode = new Node('two'); + list.addLast(lastNode); + + expect(lastNode.prev).toBe(list.head); + }); + }); + + describe("removeFirst()", () => { + test("updates the new head's prev to null", () => { + twoList.removeFirst(); + + expect(twoList.head.prev).toBe(null); + }); + }); + + describe("replace()", () => { + test("head's prev is null when the head is replaced", () => { + twoList.replace(0, new Node('new one')); + + expect(twoList.head.prev).toBe(null); + }); + + test("node after head's prev is new head when the head is replaced", () => { + twoList.replace(0, new Node('new one')); + + expect(twoList.head.next.prev.value).toBe('new one'); + }); + + test("sets the tail's prev when the tail is replaced", () => { + twoList.replace(1, new Node('new one')); + + expect(twoList.head.next.prev.value).toBe('one'); + }); + + test("sets the middle node's prev when it is replaced", () => { + twoList.addFirst(new Node('zero')); + twoList.replace(1, new Node('special')); + + expect(twoList.head.next.prev.value).toBe('zero'); + }); + + test("sets the next node's prev when a node replaced", () => { + twoList.addFirst(new Node('zero')); + twoList.replace(1, new Node('special')); + + expect(twoList.head.next.next.prev.value).toBe('special'); + }); + }); + + describe("insert", () => { + test("sets prev correctly when a node is inserted at index 0", () => { + twoList.insert(0, new Node('zero')); + + expect(twoList.head.prev).toBe(null); + expect(twoList.head.next.prev.value).toBe('zero'); + }); + + test("sets prev correctly when a node is inserted as the new tail", () => { + twoList.insert(2, new Node('tail')); + + expect(twoList.head.next.next.prev.value).toBe('two'); + }); + + test("sets prev correctly when a node is inserted in the middle", () => { + const newNode = new Node('1.5'); + twoList.insert(1, newNode); + + expect(twoList.head.next.prev).toBe(head); + expect(twoList.head.next.next.prev).toBe(newNode); + }); + }); + + describe("remove()", () => { + test("sets the new head's prev to null when removing the original head", () => { + twoList.remove(0); + + expect(twoList.head.prev).toBe(null); + }); + + test("sets the next node's prev correctly when removing a middle node", () => { + twoList.addFirst(new Node('zero')); + twoList.remove(1); + + expect(twoList.head.next.prev.value).toBe('zero'); + }); + + test("leaves prev alone when removing the tail", () => { + twoList.remove(1); + + expect(twoList.head.prev).toBe(null); + }); + }); +}); diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/linked_list.png b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/linked_list.png new file mode 100644 index 00000000..fece0439 Binary files /dev/null and b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/linked_list.png differ diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/.rspec b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/Gemfile b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/doubly_linked_list.rb b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/doubly_linked_list.rb new file mode 100644 index 00000000..69c84ea4 --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/doubly_linked_list.rb @@ -0,0 +1,134 @@ +class DoublyLinkedList + attr_accessor :head + + def initialize(head = nil) + @head = head + end + + def iterate + count = 0 + temp = @head + + until temp.nil? + yield(temp, count) + temp = temp.next_node + count += 1 + end + + @head + end + + def print + iterate { |node| puts node.value } + end + + def find(target) + iterate do |node| + return node if node.value == target + end + + nil + end + + def add_first(node) + node.next_node = @head + @head = node + end + + def add_last(node) + if @head.nil? + @head = node + return + end + + iterate do |curr_node| + if curr_node.next_node.nil? + curr_node.next_node = node + return + end + end + end + + def remove_first + old_head = @head + @head = @head.next_node unless @head.nil? + old_head + end + + def remove_last + return remove_first if @head.nil? || @head.next_node.nil? + + iterate do |node| + if node.next_node.next_node.nil? + old_tail = node.next_node + node.next_node = nil + return old_tail + end + end + end + + def replace(idx, node) + if idx.zero? + remove_first + add_first(node) + return node + end + + iterate do |curr_node, count| + if count == idx - 1 + node.next_node = curr_node.next_node.next_node + curr_node.next_node = node + return node + end + end + end + + def insert(idx, node) + if idx.zero? + add_first(node) + return + end + + iterate do |curr_node, count| + if count == idx - 1 + old_next = curr_node.next_node + curr_node.next_node = node + node.next_node = old_next + return + end + end + end + + def remove(idx) + if idx.zero? + return remove_first + end + + iterate do |node, count| + if count == idx - 1 + old_node = node.next_node + node.next_node = node.next_node.next_node + return old_node + end + end + end + + def clear + @head = nil + end +end + +class Node + attr_accessor :value, :next_node, :prev_node + + def initialize(value = nil, next_node = nil) + @value = value + @next_node = next_node + end +end + +if __FILE__ == $PROGRAM_NAME + # Don't forget to add your tests! +end + + diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/spec/doubly_linked_list_spec.rb b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/spec/doubly_linked_list_spec.rb new file mode 100644 index 00000000..7f577fb1 --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/spec/doubly_linked_list_spec.rb @@ -0,0 +1,384 @@ +require "./doubly_linked_list" + +RSpec.describe "Node" do + let(:node) { Node.new("hi", "there") } + + context "#initialize with arguments" do + it "sets an attribute called value to the argument" do + expect(node.value).to eq("hi") + end + + it "sets an attribute called next_node to the argument" do + expect(node.next_node).to eq("there") + end + end + + context "#initialize without arguments" do + it "sets an attribute called value to nil" do + expect(Node.new.value).to eq(nil) + end + + it "sets an attribute called next_node to nil" do + expect(Node.new.next_node).to eq(nil) + end + end + + context "setting and getting prev_node" do + it "sets prev_node to nil by default" do + expect(Node.new('hi').prev_node).to be_nil + end + + it "gets and sets the prev_node" do + node = Node.new('two') + node.prev_node = Node.new('one') + + expect(node.prev_node.value).to eq('one') + end + end +end + +RSpec.describe "LinkedList" do + let(:linked_list) { DoublyLinkedList.new(Node.new("one", Node.new("two", Node.new("three", Node.new("four"))))) } + let(:empty_list) { DoublyLinkedList.new } + let(:one_item_list) { DoublyLinkedList.new(Node.new("just one")) } + + context "#initialize" do + it "sets head to nil if no argument is provided" do + expect(empty_list.head).to eq(nil) + end + + it "sets head to the argument if an argument is provided" do + head = Node.new("hi") + list = DoublyLinkedList.new(head) + + expect(list.head).to eq(head) + end + end + + context "#iterate" do + it "calls the provided callback on every list node and provides the node as an argument to the callback" do + values = [] + linked_list.iterate { |node| values << node } + + expect(values.length).to eq(4) + expect(values.last).to be_a_kind_of(Node) + end + + it "can handle an empty list" do + values = [] + empty_list.iterate { |node| values << node } + + expect(values.length).to eq(0) + end + + it "can handle a list with only a head" do + values = [] + one_item_list.iterate { |node| values << node } + + expect(values.length).to eq(1) + expect(values.last).to be_a_kind_of(Node) + end + end + + context "#print" do + it "prints nothing at all when the list is empty" do + expect { empty_list.print }.to_not output.to_stdout + end + + it "prints a single line for a list with one node" do + expect { one_item_list.print }.to output("just one\n").to_stdout + end + + it "prints all nodes" do + expect { linked_list.print }.to output("one\ntwo\nthree\nfour\n").to_stdout + end + end + + context "#find" do + it "returns nil when the list is empty" do + expect(empty_list.find(4)).to be_nil + end + + it "returns the correct Node when a Node with that value is in the list" do + expect(linked_list.find("two")).to be(linked_list.head.next_node) + end + + it "returns nil when the value is not in the list" do + expect(linked_list.find("nope")).to be_nil + end + end + + context "#add_first" do + it "adds the given Node to the beginning of the list without removing any" do + old_head = one_item_list.head + node = Node.new("I'm new") + one_item_list.add_first(node) + + expect(one_item_list.head).to be(node) + expect(one_item_list.head.next_node).to be(old_head) + end + + it "adds the given node to an empty list" do + node = Node.new("I'm new") + empty_list.add_first(node) + + expect(empty_list.head).to be(node) + expect(empty_list.head.next_node).to be_nil + end + end + + context "#add_last" do + it "adds the given Node to the end of the list without removing any" do + old_tail = one_item_list.head + node = Node.new("I'm new") + one_item_list.add_last(node) + + expect(one_item_list.head).to be(old_tail) + expect(one_item_list.head.next_node).to be(node) + end + + it "adds the given node to an empty list" do + node = Node.new("I'm new") + empty_list.add_last(node) + + expect(empty_list.head).to be(node) + expect(empty_list.head.next_node).to be_nil + end + end + + context "#remove_first" do + it "removes and returns the head of the list" do + old_head = linked_list.head + + expect(linked_list.remove_first).to be(old_head) + end + + it "updates the head to the correct Node" do + old_head = linked_list.head + linked_list.remove_first + + expect(linked_list.head).to be(old_head.next_node) + expect(linked_list.head.next_node).to be(old_head.next_node.next_node) + end + + it "does not produce an error when called on an empty list" do + expect { empty_list.remove_first }.to_not raise_exception + end + end + + context "#remove_last" do + it "removes and returns the tail of the list" do + old_tail = linked_list.head.next_node.next_node.next_node + + expect(linked_list.remove_last).to be(old_tail) + expect(linked_list.head.next_node.next_node.next_node).to be_nil + end + + it "makes the node before the old tail the new tail" do + new_tail = linked_list.head.next_node.next_node + + expect(linked_list.head.next_node.next_node).to be(new_tail) + end + + it "does not produce an error when called on an empty list" do + expect { empty_list.remove_last }.to_not raise_exception + end + end + + context "#replace" do + it "returns the inserted node" do + node = Node.new("replacing") + + expect(linked_list.replace(0, node)).to be(node) + end + + it "replaces the correct nodes at the correct indexes" do + node_zero = Node.new("replace at 0") + node_one = Node.new("replace at 1") + node_two = Node.new("replace at 2") + node_three = Node.new("replace at 3") + + [node_zero, node_one, node_two, node_three].each_with_index { |node, idx| linked_list.replace(idx, node) } + + expect(linked_list.head).to be(node_zero) + expect(linked_list.head.next_node).to be(node_one) + expect(linked_list.head.next_node.next_node).to be(node_two) + expect(linked_list.head.next_node.next_node.next_node).to be(node_three) + expect(linked_list.head.next_node.next_node.next_node.next_node).to be_nil + end + end + + context "#insert" do + it "can insert a node at the beginning of the list" do + old_head = one_item_list.head + node = Node.new('hi') + one_item_list.insert(0, node) + + expect(one_item_list.head).to be(node) + expect(one_item_list.head.next_node).to be(old_head) + expect(one_item_list.head.next_node.next_node).to be_nil + end + + it "can insert a node at the very end of the list (making a new tail)" do + old_head = one_item_list.head + node = Node.new('hi') + one_item_list.insert(1, node) + + expect(one_item_list.head).to be(old_head) + expect(one_item_list.head.next_node).to be(node) + expect(one_item_list.head.next_node.next_node).to be_nil + end + + it "can insert a node in the middle of a list" do + node = Node.new('hi') + linked_list.insert(2, node) + + expect { linked_list.print }.to output("one\ntwo\nhi\nthree\nfour\n").to_stdout + end + end + + context "#remove" do + it "returns the removed node" do + to_remove = linked_list.head.next_node.next_node.next_node + + expect(linked_list.remove(3)).to be(to_remove) + end + + it "removes the correct node" do + removed = linked_list.remove(1) + + found = false + linked_list.iterate { |node| found = true if node == removed } + + expect(found).to be false + end + + it "keeps the list intact when a node is removed" do + linked_list.remove(2) + + expect { linked_list.print }.to output("one\ntwo\nfour\n").to_stdout + end + end + + context "#clear" do + it "empties the list" do + linked_list.clear + + expect(linked_list.head).to be_nil + end + end +end + +RSpec.describe "updating prev_node appropriately" do + let(:node) { Node.new('one') } + let(:list) { DoublyLinkedList.new(node) } + let(:head) { Node.new('one') } + let(:tail) { Node.new('two') } + let(:two_list) do + list = DoublyLinkedList.new(head) + list.add_last(tail) + list + end + + context "#add_first" do + it "updates the former head's previous node to the node being added" do + list.add_first(Node.new('zero')) + + expect(list.head.next_node).to be(node) + expect(node.prev_node.value).to eq('zero') + end + end + + context "#add_last" do + it "updates the former head's previous node to the node being added" do + last_node = Node.new('two') + list.add_last(last_node) + + expect(last_node.prev_node).to be(list.head) + end + end + + context "#remove_first" do + it "updates the new head's prev_node to nil" do + two_list.remove_first + + expect(two_list.head.prev_node).to be_nil + end + end + + context "#replace" do + it "is nil when the head is replaced" do + two_list.replace(0, Node.new('new one')) + + expect(two_list.head.prev_node).to be_nil + end + + it "sets the tail's prev_node when the tail is replaced" do + two_list.replace(1, Node.new('new one')) + + expect(two_list.head.next_node.prev_node.value).to eq('one') + end + + it "sets the middle node's prev_node when it is replaced" do + two_list.add_first(Node.new('zero')) + two_list.replace(1, Node.new('special')) + + # zero -> special -> two + expect(two_list.head.next_node.prev_node.value).to eq('zero') + end + + it "sets the next node's prev_node when a node replaced" do + two_list.add_first(Node.new('zero')) + two_list.replace(1, Node.new('special')) + + # zero -> special -> two + expect(two_list.head.next_node.next_node.prev_node.value).to eq('special') + end + end + + context "#insert" do + it "sets prev_node correctly when a node is inserted at index 0" do + two_list.insert(0, Node.new('zero')) + + expect(two_list.head.prev_node).to be_nil + expect(two_list.head.next_node.prev_node.value).to eq('zero') + end + + it "sets prev_node correctly when a node is inserted as the new tail" do + two_list.insert(2, Node.new('tail')) + + expect(two_list.head.next_node.next_node.prev_node.value).to eq('two') + end + + it "sets prev_node correctly when a node is inserted in the middle" do + new_node = Node.new('1.5') + two_list.insert(1, new_node) + + expect(two_list.head.next_node.prev_node).to be(head) + expect(two_list.head.next_node.next_node.prev_node).to be(new_node) + end + end + + context "#remove" do + it "sets the new head's prev_node to nil when removing the original head" do + two_list.remove(0) + + expect(two_list.head.prev_node).to be_nil + end + + it "sets the next node's prev_node correctly when removing a middle node" do + two_list.add_first(Node.new('zero')) + # zero -> one -> two + two_list.remove(1) + # zero -> two + + expect(two_list.head.next_node.prev_node.value).to eq('zero') + end + + it "leaves the prev_node alone when removing the tail" do + two_list.remove(1) + + expect(two_list.head.prev_node).to be_nil + end + end +end diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/spec/spec_helper.rb b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/solutions/doubly_linked_list.js b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/solutions/doubly_linked_list.js new file mode 100644 index 00000000..bf296838 --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/solutions/doubly_linked_list.js @@ -0,0 +1,301 @@ +class DoublyLinkedList { + constructor(head = null) { + this.head = head; + } + + iterate(callback) { + let count = 0; + let temp = this.head; + + while (temp !== null) { + const result = callback(temp, count); + + if (result === true) { + return temp; + } + + ++count; + temp = temp.next; + } + + return this.head; + } + + // print each node's value on its own line + // use your iterate method to be DRY! Don't get caught in the code rain, brrr. + print() { + this.iterate(node => console.log(node.value)); + } + + // find the node with the target value and return it + // if not found return null, use your iterate method to be DRY! + find(target) { + let result = null; + + this.iterate(node => { + if (node.value === target) { + result = node; + + return true; + } + }); + + return result; + } + + // add the node to the start of the list, no nodes should be removed + // MODIFY TO UPDATE PREV + addFirst(node) { + node.next = this.head; + + if (this.head !== null) { + this.head.prev = node; + } + + this.head = node; + } + + // add node to end of list, no nodes should be removed + // you may wish to use the iterate method + // MODIFY TO UPDATE PREV + addLast(node) { + if (this.head === null) { + this.head = node; + return; + } + + this.iterate(currNode => { + if (currNode.next === null) { + currNode.next = node; + node.prev = currNode; + return true; + } + }); + } + + // remove the first Node in the list and update head + // and return the removed node + // MODIFY TO UPDATE PREV + removeFirst() { + const oldHead = this.head; + + if (this.head !== null) { + this.head = this.head.next; + } + + if (this.head !== null) { + this.head.prev = null; + } + + return oldHead; + } + + // remove the tail node, iterate may be helpful + // return the node you just removed + removeLast() { + if (this.head === null || this.head.next === null) { + return this.removeFirst(); + } + + let oldTail = null; + + this.iterate(node => { + if (node.next.next === null) { + oldTail = node.next; + node.next = null; + return true; + } + }); + + return oldTail; + } + + // replace the node at the given index with the given node + // MODIFY TO UPDATE PREV + replace(idx, node) { + if (idx === 0) { + this.removeFirst(); + this.addFirst(node); + return node; + } + + this.iterate((currNode, count) => { + if (count === idx - 1) { + node.next = currNode.next.next; + currNode.next = node; + node.prev = currNode; + + if (currNode.next.next !== null) { + currNode.next.next.prev = node; + } + + return true; + } + }); + + return node; + } + + // insert the node at the given index + // no existing nodes should be removed or replaced + // MODIFY TO UPDATE PREV + insert(idx, node) { + if (idx === 0) { + this.addFirst(node); + return; + } + + this.iterate((currNode, count) => { + if (count === idx - 1) { + const oldNext = currNode.next; + currNode.next = node; + node.next = oldNext; + node.prev = currNode; + + if (oldNext !== null) { + oldNext.prev = node; + } + + return true; + } + }); + } + + // remove the node at the given index, and return it + // MODIFY TO UPDATE PREV + remove(idx) { + if (idx === 0) { + return this.removeFirst(); + } + + let oldNode = null; + + this.iterate((node, count) => { + if (count === idx - 1) { + oldNode = node.next; + node.next = node.next.next; + + if (node.next !== null) { + node.next.prev = node; + } + + return true; + } + }); + + return oldNode; + } + + clear() { + this.head = null; + } +} + +class Node { + constructor(value = null, next = null, prev = null) { + this.value = value; + this.next = next; + this.prev = prev; + } +} + +if (require.main === module) { + let node = new Node('one'); + node.prev = new Node('zero'); + + console.log("NODE TESTING PREV VALUES"); + console.log(`Node prev value is zero: ${node.prev.value}`); + console.log(`Node prev.prev is null: ${node.prev.prev}`); + console.log("---------------------------------------------------------"); + + node = new Node('one'); + let list = new DoublyLinkedList(node); + list.addFirst(new Node('zero')); + + console.log("TEST PREV WHEN CALLING ADD FIRST"); + console.log(`Node prev value is zero: ${node.prev.value}`); + console.log(`Node prev.next value is one: ${node.prev.next.value}`); + console.log("---------------------------------------------------------"); + + let lastNode = new Node('last'); + list.addLast(lastNode); + console.log("TEST PREV WHEN CALLING ADD LAST"); + console.log(`Last node prev value is one: ${lastNode.prev.value}`); + console.log("---------------------------------------------------------"); + + console.log("TEST PREV WHEN CALLING REMOVE FIRST"); + list.removeFirst(); + console.log(`Head node's prev should be null: ${list.head.prev}`); + console.log("---------------------------------------------------------"); + + // one -> last + list.replace(0, new Node('zero')); + console.log("TEST PREV WHEN REPLACING HEAD"); + console.log(`Head node's prev is null: ${list.head.prev}`); + + // zero -> last + list.replace(1, new Node('one')); + console.log("TEST PREV WHEN REPLACING TAIL"); + console.log(`Tail node's prev is zero: ${list.head.next.prev.value}`); + + let head = new Node('zero'); + let one = new Node('one'); + let two = new Node('two'); + head.next = one + one.next = two + one.prev = head + two.prev = one + list = new DoublyLinkedList(head); + + // zero -> one -> two + replacing = new Node('replacing'); + list.replace(1, replacing) + // zero -> replacing -> two + console.log("TEST PREV WHEN REPLACING MIDDLE NODE"); + console.log(`Middle node prev value is zero: ${replacing.prev.value}`); + console.log(`Last node prev value is replacing: ${two.prev.value}`); + console.log("---------------------------------------------------------"); + + // zero -> replacing -> two + node = new Node('insert'); + list.insert(0, node); + // insert -> zero -> replacing -> two + console.log("TEST PREV WHEN INSERTING 0TH NODE"); + console.log(`Head node prev is null: ${list.head.prev}`); + console.log(`Head node next.prev value is insert: ${list.head.next.prev.value}`); + + node = new Node('at tail'); + list.insert(4, node); + // insert -> zero -> replacing -> two -> at tail + console.log("TEST PREV WHEN INSERTING TAIL NODE"); + console.log(`Tail node prev is two: ${node.prev.value}`); + + node = new Node('in middle'); + list.insert(2, node); + // insert -> zero -> in middle -> replacing -> two -> at tail + console.log("TEST PREV WHEN INSERTING IN MIDDLE"); + console.log(`Middle node prev value is zero: ${node.prev.value}`); + console.log(`Middle node next.prev is in middle: ${node.next.prev.value}`); + console.log("---------------------------------------------------------"); + + // insert -> zero -> in middle -> replacing -> two -> at tail + list.remove(0); + // zero -> in middle -> replacing -> two -> at tail + console.log("TEST PREV WHEN REMOVING 0TH NODE"); + console.log(`Head node's prev value is null: ${list.head.prev}`); + + list.remove(2); + // zero -> in middle -> two -> at tail + console.log("TEST PREV WHEN REMOVING IN MIDDLE"); + console.log(`Node at index 2 prev's value is in middle: ${list.head.next.next.prev.value}`); + + list.remove(3); + // zero -> in middle -> two + console.log("TEST PREV WHEN REMOVING TAIL"); + console.log(`Tail node's prev should still point to node with value in middle: ${list.head.next.next.prev.value}`); + console.log("---------------------------------------------------------"); +} + +module.exports = { + Node, DoublyLinkedList +}; diff --git a/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/solutions/doubly_linked_list.rb b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/solutions/doubly_linked_list.rb new file mode 100644 index 00000000..d9477ecc --- /dev/null +++ b/07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list/solutions/doubly_linked_list.rb @@ -0,0 +1,249 @@ +class DoublyLinkedList + attr_accessor :head + + def initialize(head = nil) + @head = head + end + + def iterate + count = 0 + temp = @head + + until temp.nil? + yield(temp, count) + temp = temp.next_node + count += 1 + end + + @head + end + + def print + iterate { |node| puts node.value } + end + + def find(target) + iterate do |node| + return node if node.value == target + end + + nil + end + + # MODIFY TO UPDATE PREV_NODE + def add_first(node) + node.next_node = @head + @head.prev_node = node unless @head.nil? + @head = node + end + + # MODIFY TO UPDATE PREV_NODE + def add_last(node) + if @head.nil? + @head = node + return + end + + iterate do |curr_node| + if curr_node.next_node.nil? + curr_node.next_node = node + node.prev_node = curr_node + return + end + end + end + + # MODIFY TO UPDATE PREV_NODE + def remove_first + old_head = @head + @head = @head.next_node unless @head.nil? + @head.prev_node = nil unless @head.nil? + old_head + end + + def remove_last + return remove_first if @head.nil? || @head.next_node.nil? + + iterate do |node| + if node.next_node.next_node.nil? + old_tail = node.next_node + node.next_node = nil + return old_tail + end + end + end + + # MODIFY TO UPDATE PREV_NODE + def replace(idx, node) + if idx.zero? + remove_first + add_first(node) + return node + end + + iterate do |curr_node, count| + if count == idx - 1 + node.next_node = curr_node.next_node.next_node + curr_node.next_node = node + node.prev_node = curr_node + + unless curr_node.next_node.next_node.nil? + curr_node.next_node.next_node.prev_node = node + end + + return node + end + end + end + + # MODIFY TO UPDATE PREV_NODE + def insert(idx, node) + if idx.zero? + add_first(node) + return + end + + iterate do |curr_node, count| + if count == idx - 1 + old_next = curr_node.next_node + curr_node.next_node = node + node.next_node = old_next + old_next.prev_node = node unless old_next.nil? + node.prev_node = curr_node + + return + end + end + end + + # MODIFY TO UPDATE PREV_NODE + def remove(idx) + if idx.zero? + return remove_first + end + + iterate do |node, count| + if count == idx - 1 + old_node = node.next_node + node.next_node = node.next_node.next_node + node.next_node.prev_node = node unless node.next_node.nil? + + return old_node + end + end + end + + def clear + @head = nil + end +end + +class Node + attr_accessor :value, :next_node, :prev_node + + def initialize(value = nil, next_node = nil, prev_node = nil) + @value = value + @next_node = next_node + @prev_node = nil + end +end + +if __FILE__ == $PROGRAM_NAME + node = Node.new('one') + node.prev_node = Node.new('zero') + + puts "NODE TESTING PREV VALUES" + puts "Node prev value is zero: #{node.prev_node.value}" + puts "Node prev.prev is null: #{node.prev_node.prev_node}" + puts "---------------------------------------------------------" + + node = Node.new('one') + list = DoublyLinkedList.new(node) + list.add_first(Node.new('zero')) + + puts "TEST PREV WHEN CALLING ADD FIRST" + puts "Node prev value is zero: #{node.prev_node.value}" + puts "Node prev.next value is one: #{node.prev_node.next_node.value}" + puts "---------------------------------------------------------" + + last_node = Node.new('last') + list.add_last(last_node) + puts "TEST PREV WHEN CALLING ADD LAST" + puts "Last node prev value is one: #{last_node.prev_node.value}" + puts "---------------------------------------------------------" + + puts "TEST PREV WHEN CALLING REMOVE FIRST" + list.remove_first + puts "Head node's prev should be nil: #{list.head.prev_node}" + puts "---------------------------------------------------------" + + # one -> last + list.replace(0, Node.new('zero')) + puts "TEST PREV WHEN REPLACING HEAD" + puts "Head node's prev is null: #{list.head.prev_node}" + + # zero -> last + list.replace(1, Node.new('one')) + puts "TEST PREV WHEN REPLACING TAIL" + puts "Tail node's prev is zero: #{list.head.next_node.prev_node.value}" + + head = Node.new('zero') + one = Node.new('one') + two = Node.new('two') + head.next_node = one + one.next_node = two + one.prev_node = head + two.prev_node = one + list = DoublyLinkedList.new(head) + + # zero -> one -> two + replacing = Node.new('replacing') + list.replace(1, replacing) + # zero -> replacing -> two + puts "TEST PREV WHEN REPLACING MIDDLE NODE" + puts "Middle node prev value is zero: #{replacing.prev_node.value}" + puts "Last node prev value is replacing: #{two.prev_node.value}" + puts "---------------------------------------------------------" + + # zero -> replacing -> two + node = Node.new('insert') + list.insert(0, node) + # insert -> zero -> replacing -> two + puts "TEST PREV WHEN INSERTING 0TH NODE" + puts "Head node prev is null: #{list.head.prev_node}" + puts "Head node next.prev value is insert: #{list.head.next_node.prev_node.value}" + + node = Node.new('at tail') + list.insert(4, node) + # insert -> zero -> replacing -> two -> at tail + puts "TEST PREV WHEN INSERTING TAIL NODE" + puts "Tail node prev is two: #{node.prev_node.value}" + + node = Node.new('in middle') + list.insert(2, node) + # insert -> zero -> in middle -> replacing -> two -> at tail + puts "TEST PREV WHEN INSERTING IN MIDDLE" + puts "Middle node prev value is zero: #{node.prev_node.value}" + puts "Middle node next.prev is in middle: #{node.next_node.prev_node.value}" + puts "---------------------------------------------------------" + + # insert -> zero -> in middle -> replacing -> two -> at tail + list.remove(0) + # zero -> in middle -> replacing -> two -> at tail + puts "TEST PREV WHEN REMOVING 0TH NODE" + puts "Head node's prev value is nil: #{list.head.prev_node}" + + list.remove(2) + # zero -> in middle -> two -> at tail + puts "TEST PREV WHEN REMOVING IN MIDDLE" + puts "Node at index 2 prev's value is in middle: #{list.head.next_node.next_node.prev_node.value}" + + list.remove(3) + # zero -> in middle -> two + puts "TEST PREV WHEN REMOVING TAIL" + puts "Tail node's prev should still point to node with value in middle: #{list.head.next_node.next_node.prev_node.value}" + puts "---------------------------------------------------------" +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/08-pairing-exercise-2/00-whiteboard-big-o/README.md b/08-pairing-exercise-2/00-whiteboard-big-o/README.md new file mode 100644 index 00000000..fb22aa86 --- /dev/null +++ b/08-pairing-exercise-2/00-whiteboard-big-o/README.md @@ -0,0 +1,22 @@ +# Whiteboard Big O + +## Introduction + +For this activity, you and your partner will take turns calculating the time +complexity using Big O notation for a problem you solved previously. Think of +this exercise as being more collaborative and less formal than other +whiteboarding exercises. In other words, you and your partner should communicate +freely and work together to come up with the appropriate calculation. + +## Instructions + +- You and your partner should each choose a different problem that you've + already solved +- Share your solution to the chosen problem either on a whiteboard or via a + screenshare +- Determine the time complexity for your solution + - Explain your thinking and work with your partner to come up with the answer + - Take turns typing or whiteboarding + - Plan to start the conversation and whiteboard/type when evaluating your + own solution + - Remember to use the correct notation, e.g. O(n) or O(1) diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/.gitignore b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/README.md b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/README.md new file mode 100644 index 00000000..8ec5081d --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/README.md @@ -0,0 +1,91 @@ +# Days 1-2: Bubble Sort + +Bubble sort is considered one of the simpler sorting algorithms, and it is highly efficient at handling already sorted lists. While it performs well for sorted lists and short lists, it does not perform well for longer lists. As a result, the sorting functions built into programming languages do not use bubble sort. + +``` +Input: [3, 2, 1, 4] +Output: [1, 2, 3, 4] + +Input: [] +Output: [] +``` + +## How Does Bubble Sort Work? + +Bubble sort sorts a list in place. In other words, it does not create a new Array; instead, it modifies the Array that was passed to the function as an argument. + +To achieve this in-place sorting, bubble sort swaps elements when they are in the incorrect order. When no swaps occur, the Array is considered sorted. This algorithm iterates over an Array over and over until it is sorted. If the input Array is already sorted, it iterates over it only once because no swaps occur. + +Let's look at a step-by-step example with an unsorted list: + +``` +Input: [2, 3, 1] +// 2 is less than 3, so it stays where it is +// But 1 is less than 3 so those two values are swapped +Pass 1: [2, 1, 3] + +// 1 is less than 2, so those values are swapped +// 2 is less than 3, so those values stay as is +Pass 2: [1, 2, 3] + +// On this final pass, no swaps occur, so Array is sorted +Pass 3: [1, 2, 3] +``` + +And here's what happens with a sorted list: + +``` +Input: [1, 2, 3] + +// No swaps occur when iterating over Array +// Input Array is returned as is +Pass 1: [1, 2, 3] +``` + +## Implement Bubble Sort + +Given what you know about bubble sort, implement your own version. Remember, we need to sort the Array in place, so there's no need to declare a new Array and push to it: be sure to return the input Array with its values in order. + +Lastly, you may wish to look up how to swap values in whichever language you choose to solve the problem in. Some languages, like Ruby and JavaScript, provide shortcuts! + +When you write up the explanation of your solution, don't forget to calculate Big O for its time complexity. + +Also take some time to think about what you learned by solving this challenge. What problem solving techniques did you use, coding or otherwise? How can they help you solve other problems? + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/javascript/bubble_sort.js b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/javascript/bubble_sort.js new file mode 100644 index 00000000..bf6d925d --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/javascript/bubble_sort.js @@ -0,0 +1,29 @@ +function bubbleSort(arr) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [1, 2, 3, 4]"); + console.log("=>", bubbleSort([3, 2, 1, 4])); + + console.log(""); + + console.log("Expecting: [1, 2, 3]"); + console.log("=>", bubbleSort([1, 2, 3])); + + console.log(""); + + console.log("Expecting: []"); + console.log("=>", bubbleSort([])); + + console.log(""); + + console.log("Expecting: [1, 2, 3]"); + console.log("=>", bubbleSort([2, 3, 1])); +} + +module.exports = bubbleSort; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/javascript/package.json b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/javascript/package.json new file mode 100644 index 00000000..0ff1defc --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "bubble_sort", + "version": "1.0.0", + "description": "bubble sort", + "main": "bubble_sort.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/javascript/tests/bubble_sort.test.js b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/javascript/tests/bubble_sort.test.js new file mode 100644 index 00000000..82f8ca1e --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/javascript/tests/bubble_sort.test.js @@ -0,0 +1,40 @@ +const bubbleSort = require("../bubble_sort"); + +test("returns the provided empty Array when it's provided as an argument", () => { + const input = []; + + expect(bubbleSort(input)).toBe(input); + expect(bubbleSort(input)).toStrictEqual([]); +}); + +test("can handle an Array containing a single element", () => { + const input = [4]; + + expect(bubbleSort(input)).toBe(input); + expect(bubbleSort(input)).toStrictEqual([4]); +}); + +test("can handle an Array containing two elements", () => { + const input = [5, 2]; + + expect(bubbleSort(input)).toBe(input); + expect(bubbleSort(input)).toStrictEqual([2, 5]); +}); + +test("can handle an Array containing three elements", () => { + const input = [5, 2, 1]; + + expect(bubbleSort(input)).toBe(input); + expect(bubbleSort(input)).toStrictEqual([1, 2, 5]); +}); + +test("can handle an Array containing many elements", () => { + const input = [6, -2, 0, 8, 7, 8, 6, 0, 5, 1]; + + expect(bubbleSort(input)).toBe(input); + expect(bubbleSort(input)).toStrictEqual([-2, 0, 0, 1, 5, 6, 6, 7, 8, 8]); +}); + +test("can handle a sorted Array", () => { + expect(bubbleSort([-10, 1, 2, 3, 4])).toStrictEqual([-10, 1, 2, 3, 4]); +}); diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/.rspec b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/Gemfile b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/bubble_sort.rb b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/bubble_sort.rb new file mode 100644 index 00000000..7b53493b --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/bubble_sort.rb @@ -0,0 +1,32 @@ +def bubble_sort(arr) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [1, 2, 3, 4]" + print "=> " + print bubble_sort([3, 2, 1, 4]) + + puts + + puts "Expecting: [1, 2, 3]" + print "=> " + print bubble_sort([1, 2, 3]) + + puts + + puts "Expecting: []" + print "=> " + print bubble_sort([]) + + puts + + puts "Expecting: [1, 2, 3]" + print "=> " + print bubble_sort([2, 3, 1]) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/spec/bubble_sort_spec.rb b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/spec/bubble_sort_spec.rb new file mode 100644 index 00000000..702b99a9 --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/spec/bubble_sort_spec.rb @@ -0,0 +1,45 @@ +require "./bubble_sort.rb" + +RSpec.describe "bubble_sort" do + it "returns the provided empty Array when it's provided as an argument" do + input = [] + + expect(bubble_sort(input)).to be(input) + end + + it "returns an empty Array when the given an empty Array as an argument" do + expect(bubble_sort([])).to eq([]) + end + + it "can handle an Array containing a single element" do + input = [4] + + expect(bubble_sort(input)).to be(input) + expect(bubble_sort(input)).to eq([4]) + end + + it "can handle an Array containing two elements" do + input = [5, 2] + + expect(bubble_sort(input)).to be(input) + expect(bubble_sort(input)).to eq([2, 5]) + end + + it "can handle an Array containing three elements" do + input = [5, 2, 1] + + expect(bubble_sort(input)).to be(input) + expect(bubble_sort(input)).to eq([1, 2, 5]) + end + + it "can handle an Array containing many elements" do + input = [6, -2, 0, 8, 7, 8, 6, 0, 5, 1] + + expect(bubble_sort(input)).to be(input) + expect(bubble_sort(input)).to eq([-2, 0, 0, 1, 5, 6, 6, 7, 8, 8]) + end + + it "can handle a sorted Array" do + expect(bubble_sort([-10, 1, 2, 3, 4])).to eq([-10, 1, 2, 3, 4]) + end +end \ No newline at end of file diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/spec/spec_helper.rb b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/solutions/bubble_sort.js b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/solutions/bubble_sort.js new file mode 100644 index 00000000..90e1087b --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/solutions/bubble_sort.js @@ -0,0 +1,91 @@ +function bubbleSort(arr) { + let sorted = false; + + while (!sorted) { + sorted = true; + + arr.forEach((num, idx) => { + if (idx === arr.length - 1) { + return; + } + + if (num > arr[idx + 1]) { + [arr[idx], arr[idx + 1]] = [arr[idx + 1], arr[idx]]; + sorted = false; + } + }); + } + + return arr; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [1, 2, 3, 4]"); + console.log(bubbleSort([3, 2, 1, 4])); + + console.log(""); + + console.log("Expecting: [1, 2, 3]"); + console.log(bubbleSort([1, 2, 3])); + + console.log(""); + + console.log("Expecting: []"); + console.log(bubbleSort([])); + + console.log(""); + + console.log("Expecting: [1, 2, 3]"); + console.log(bubbleSort([2, 3, 1])); + + console.log(""); + + console.log("Expecting: [1]"); + console.log(bubbleSort([1])); + + console.log(""); + + console.log("Expecting: [1, 3]"); + console.log(bubbleSort([3, 1])); + + console.log(""); + + console.log("Expecting: [-2, 0, 0, 1, 5, 6, 6, 7, 8, 8]"); + console.log(bubbleSort([6, -2, 0, 8, 7, 8, 6, 0, 5, 1])); +} + +module.exports = bubbleSort; + +// Please add your pseudocode to this file +// And a written explanation of your solution +// Please add your pseudocode to this file +/************************************************************************************/ +// initialize boolean sorted to false +// +// while sorted is false: +// sorted = true +// iterate over array with index tracking: +// if current element is larger than next element: +// swap those elements in place +// sorted = false +// +// return input array +/************************************************************************************/ + +// And a written explanation of your solution +/************************************************************************************/ +// Since we need to iterate over the array over and over until it's sorted, we need +// to track if it's sorted, so we initialize a Boolean to false to do exactly that. +// Next we need to use a while loop (or something similar) that runs until the Array +// is sorted. This allows us to iterate over the Array as many times as needed. +// Since we want to set sorted to false only if a swap happens, we'll set it to true +// before iterating over the Array. When we iterate over the Array, we always go +// over the whole thing. We compare the value we're iterating over to the next one, +// and if the first one is more than the next one, we swap them. We also set sorted +// to false because of the swap. We'll eventually get to a point where there are no +// swaps and at that point, sorted will remain true and when the iteration ends, +// we'll exit the outer loop and return the Array. +// Big O for time complexity is O(n^2) quadratic time because of the loop within a loop. +// In the worst case we'll end up going over the whole Array roughly once per element. +/************************************************************************************/ \ No newline at end of file diff --git a/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/solutions/bubble_sort.rb b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/solutions/bubble_sort.rb new file mode 100644 index 00000000..4e032df5 --- /dev/null +++ b/09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort/solutions/bubble_sort.rb @@ -0,0 +1,86 @@ +def bubble_sort(arr) + sorted = false + + until sorted + sorted = true + + arr.each_with_index do |num, idx| + break if idx == arr.length - 1 + + if num > arr[idx + 1] + arr[idx], arr[idx + 1] = arr[idx + 1], arr[idx] + sorted = false + end + end + end + + arr +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [1, 2, 3, 4]" + print bubble_sort([3, 2, 1, 4]) + + puts + + puts "Expecting: [1, 2, 3]" + print bubble_sort([1, 2, 3]) + + puts + + puts "Expecting: []" + print bubble_sort([]) + + puts + + puts "Expecting: [1, 2, 3]" + print bubble_sort([2, 3, 1]) + + # Don't forget to add your own! + + puts + + puts "Expecting: [1]" + print bubble_sort([1]) + + puts + + puts "Expecting: [1, 3]" + print bubble_sort([3, 1]) + + puts + + puts "Expecting: [-2, 0, 0, 1, 5, 6, 6, 7, 8, 8]" + print bubble_sort([6, -2, 0, 8, 7, 8, 6, 0, 5, 1]) +end + +# Please add your pseudocode to this file +################################################################################## +# initialize boolean sorted to false +# +# while sorted is false: +# sorted = true +# iterate over array with index tracking: +# if current element is larger than next element: +# swap those elements in place +# sorted = false +# +# return input array +################################################################################## + +# And a written explanation of your solution +################################################################################## +# Since we need to iterate over the array over and over until it's sorted, we need +# to track if it's sorted, so we initialize a Boolean to false to do exactly that. +# Next we need to use a while loop (or something similar) that runs until the Array +# is sorted. This allows us to iterate over the Array as many times as needed. +# Since we want to set sorted to false only if a swap happens, we'll set it to true +# before iterating over the Array. When we iterate over the Array, we always go +# over the whole thing. We compare the value we're iterating over to the next one, +# and if the first one is more than the next one, we swap them. We also set sorted +# to false because of the swap. We'll eventually get to a point where there are no +# swaps and at that point, sorted will remain true and when the iteration ends, +# we'll exit the outer loop and return the Array. +# Big O for time complexity is O(n^2) quadratic time because of the loop within a loop. +# In the worst case we'll end up going over the whole Array roughly once per element. +################################################################################## \ No newline at end of file diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/.gitignore b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/README.md b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/README.md new file mode 100644 index 00000000..feb61cf2 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/README.md @@ -0,0 +1,224 @@ +# Days 3-5: Merge Sort + +Merge sort is an efficient sorting algorithm that uses a divide and conquer approach to sorting elements. In other words, it sorts small subsets of the list, merges those subsets, sorts those, and carries on until the whole list has been sorted. It is commonly implemented as a recursive algorithm. + +![Merge Sort animation](./merge_sort.gif) + +## How Does Merge Sort Work? + +![Merge Sort image](./merge_sort.png) + +Let's say we have an Array consisting of four elements. First, we divide that into two Arrays of two elements each. Since this is not the smallest possible division, we then divide those into four Arrays total consisting of one element each: + +``` +[4, 3, 2, 1] +[4, 3] [2, 1] +[4] [3] [2] [1] +``` + +Next, we sort and combine those elements until we have a merged and sorted list: + +``` +Sort [4] with [3] => [3, 4] +Sort [2] with [1] => [1, 2] +Sort [3, 4] with [1, 2] => [1, 2, 3, 4] +``` + +It is common to declare two functions (or more) when implementing a merge sort. The main function is recursive: it divides the list and merges the sorted pieces. It calls a helper function, which might be recursive or iterative. The helper function merges the pieces of the list into a sorted list, and returns it. + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +You should also think of what the time complexity is for this algorithm. This is a tough one because you have to think about both the recursive process and the iterative process to come up with an answer for Big O. You might want to search the web for this one. Check out this [StackExchange discussion](https://softwareengineering.stackexchange.com/questions/297160/why-is-mergesort-olog-n). + +## 1. Implement the Helper Function `merge` + +`merge` takes two arguments, both of which are Arrays. **Each of those Arrays will already be sorted.** It then merges those Arrays into one sorted Array and returns it. It merges and sorts them by comparing the first element of each Array. The smallest one is removed and pushed onto a new result Array. It does this until one of the Arrays is empty, and then pushes any remaining values onto the result. + +Here is an example of what happens during this process: + +``` +input 1 = [1, 2] +input 2 = [0, 6] +result = [] + +Is 1 < 0? + No. Remove 0 and push onto result. + +input 1 = [1, 2] +input 2 = [6] +result = [0] + +Is 1 < 6? + Yes. Remove 1 and push onto result. + +input 1 = [2] +input 2 = [6] +result = [0, 1] + +Is 2 < 6? + Yes. Remove 2 and push onto result. + +input 1 = [] +input 2 = [6] +result = [0, 1, 2] + +input 1 is empty, add remainder of input 2 to result: +result = [0, 1, 2, 6] +``` + +Be sure to test your `merge` method with Arrays of different sizes to ensure it's working. What sizes might those Arrays be? Will both inputs always be the same size? If your `merge` method is working, it'll return a sorted list: + +``` +merge([0, 1], [-2, 4]) +=> [-2, 0, 1, 4] +``` + +And remember, each argument Array to `merge` will already be sorted: + +``` +// Valid inputs +merge([2, 10], [9, 10, 12]) + +// Invalid inputs +merge([10, 3], [10, 9, 2]) +``` + +Do not worry about invalid inputs! Just ensure your method works for valid inputs before moving on. You'll need to rely on your own testing skills here! You've got this! + +## 2. Implement the Main Function `merge_sort`/`mergeSort` + +The main function recursively divides the unsorted input array into pieces until those pieces are as small as possible. It then feeds those pieces to the `merge` helper method. It then combines the results from calling `merge` into one final sorted list that is returned. + +Let's take this step by step by breaking this process down into chunks we can test. Keep in mind that you'll be responsible for testing each step. Our tests will only check the final result of calling the main method. + +**1. Add the base case** + +We want to divide the input into its smallest possible subsets. What length or lengths might the smallest possible subset be? You can also ask yourself: "What might the smallest possible input be?" or "When do I want to stop dividing the Array and return it?" or "What inputs would already be considered sorted no matter what their contents are?" + +If you're feeling rusty on recursion: the base case, often an `if` statement, is responsible for stopping the recursive calls. + +Test your code. If it's working you'll get the following result: + +``` +Input [] +Output: [] + +Input: [1] +Output: [1] + +Input: [1, 2] +Output: undefined or nil (i.e. default return value for the language you're using) +``` + +**2. Find the middle** + +Store the middle index of the input Array in a variable. Print or return it to check that it's correct. Make sure it's an integer! + +``` +Input: [] +Output: [] // never reaches the middle calculation + +Input: [1] +Output: [1] // never reaches the middle calculation + +Input: [1, 2] +Output: 1 + +Input: [1, 2, 3] +Output: 1 +``` + +Once that's working, be sure to remove any print or return statements you used to test your code. + +**3. Divide the Array** + +Divide the Array into two parts: one part stores all the values up to the middle, and the other part stores all the values from the middle onwards. These are often called `left` and `right`. + +Test your work by printing or returning the two parts. Make sure all of the values from the input are present. + +``` +Input: [1, 2, 3, 4] +Left: [1, 2] +Right: [3, 4] + +Input: [1, 2, 3] +Left: [1] +Right: [2, 3] +// Left [1, 2] and Right [3] is also valid +``` + +Once that's working, be sure to remove any print or return statements you used to test your code. + +**4. Divide more!** + +Earlier, we said that we need to keep dividing the input until it is divided into the smallest possible pieces. Another way of thinking of this is that we need to keep dividing the input until we hit the base case we declared earlier. + +Right now, we are only dividing the list into a `left` side and a `right` side once. How can we keep dividing the list until it's as small as possible? What do we need to do? + +You can test your code by printing the values stored in left and right. If the print statement/s are the very last line/s in your code, you should see the following print out: + +``` +merge_sort([1, 2, 3, 4]) +=> [1] +=> [2] +=> [3] +=> [4] +``` + +Once that's working, be sure to remove any print or return statements you used to test your code. + +**5. Sort and merge** + +We still haven't called our helper method `merge`, and it's getting lonely. If you recall from earlier in this README, `merge` takes two sorted lists and sorts and merges them into one list. What might we do with this method? What might we provide to it as arguments? + +At the end of this step, you should be done! + +``` +merge_sort([1, 2, 3]) +=> [1, 2, 3] + +merge_sort([-10, 5, 100, -100]) +=> [-100, -10, 5, 100] +``` + +**6. Take some time to think** + +Take some time to understand what's happening as the algorithm recurses. What does the stack look like? What's in each frame? Can you plot it out on paper for small inputs? Try plotting it out when the input Array has 2 elements, 3 elements, or 4 elements. You can also watch your solution in action using this [tool](http://pythontutor.com/visualize.html#mode=edit): don't forget to choose the right language, and then call your function with an argument! + +Also think about the problem solving techniques you used. How can you use what you've learned to solve other problems? What did you learn about recursion/recursive programming? + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/javascript/merge_sort.js b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/javascript/merge_sort.js new file mode 100644 index 00000000..2b75a606 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/javascript/merge_sort.js @@ -0,0 +1,28 @@ +function merge(arr1, arr2) { + // type your code here +} + +function mergeSort(arr) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [1, 2]"); + console.log("=>", mergeSort([2, 1])); + + console.log(""); + + console.log("Expecting: [1, 2, 3]"); + console.log("=>", mergeSort([1, 2, 3])); + + console.log(""); + + console.log("Expecting: [-10, 0, 2, 2, 5, 10, 20]"); + console.log("=>", mergeSort([10, -10, 0, 2, 20, 5, 2])); +} + +module.exports = mergeSort; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/javascript/package.json b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/javascript/package.json new file mode 100644 index 00000000..ed686838 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "merge_sort", + "version": "1.0.0", + "description": "merge sort", + "main": "merge_sort.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/javascript/tests/merge_sort.test.js b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/javascript/tests/merge_sort.test.js new file mode 100644 index 00000000..8de5ea94 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/javascript/tests/merge_sort.test.js @@ -0,0 +1,35 @@ +const mergeSort = require("../merge_sort"); + +test("can sort an empty Array", () => { + expect(mergeSort([])).toEqual([]); +}); + +test("can sort an Array with one element", () => { + expect(mergeSort([2])).toEqual([2]); +}); + +test("can sort an Array with two elements", () => { + expect(mergeSort([5, 3])).toEqual([3, 5]); +}); + +test("can sort an Array with three elements", () => { + expect(mergeSort([10, -1, 5])).toEqual([-1, 5, 10]); +}); + +test("can sort a large Array with an even number of elements", () => { + const arr = [90, 4, 5, -100, 5, 78, 3, 19, 1000, -900, 54, 34, 3, 5]; + + expect(mergeSort(arr)).toEqual(arr.sort((a, b) => a - b)); +}); + +test("can sort a large Array with an odd number of elements", () => { + const arr = [90, 4, 5, -100, 5, 78, 19, 1000, -900, 54, 34, 3, 5]; + + expect(mergeSort(arr)).toEqual(arr.sort((a, b) => a - b)); +}); + +test("can handle an already sorted Array", () => { + const arr = [-10, -5, 4, 6, 7]; + + expect(mergeSort(arr)).toEqual(arr); +}); diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/merge_sort.gif b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/merge_sort.gif new file mode 100644 index 00000000..daa0c86b Binary files /dev/null and b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/merge_sort.gif differ diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/merge_sort.png b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/merge_sort.png new file mode 100644 index 00000000..f6256be6 Binary files /dev/null and b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/merge_sort.png differ diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/.rspec b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/Gemfile b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/merge_sort.rb b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/merge_sort.rb new file mode 100644 index 00000000..565a7e31 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/merge_sort.rb @@ -0,0 +1,27 @@ +def merge(arr1, arr2) + # type your code in here +end + +def merge_sort(arr) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [1, 2]" + puts "=>", merge_sort([2, 1]) + + puts + + puts "Expecting: [1, 2, 3]" + puts "=>", merge_sort([1, 2, 3]) + + puts + + puts "Expecting: [-10, 0, 2, 2, 5, 10, 20]" + puts "=>", merge_sort([10, -10, 0, 2, 20, 5, 2]) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/spec/merge_sort_spec.rb b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/spec/merge_sort_spec.rb new file mode 100644 index 00000000..f0e263e7 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/spec/merge_sort_spec.rb @@ -0,0 +1,37 @@ +require "./merge_sort.rb" + +RSpec.describe "merge_sort" do + it "can sort an empty Array" do + expect(merge_sort([])).to eq([]) + end + + it "can sort an Array with one element" do + expect(merge_sort([2])).to eq([2]) + end + + it "can sort an Array with two elements" do + expect(merge_sort([5, 3])).to eq([3, 5]) + end + + it "can sort an Array with three elements" do + expect(merge_sort([10, -1, 5])).to eq([-1, 5, 10]) + end + + it "can sort a large Array with an even number of elements" do + arr = [90, 4, 5, -100, 5, 78, 3, 19, 1000, -900, 54, 34, 3, 5] + + expect(merge_sort(arr)).to eq(arr.sort) + end + + it "can sort a large Array with an odd number of elements" do + arr = [90, 4, 5, -100, 5, 78, 19, 1000, -900, 54, 34, 3, 5] + + expect(merge_sort(arr)).to eq(arr.sort) + end + + it "can handle an already sorted Array" do + arr = [-10, -5, 4, 6, 7] + + expect(merge_sort(arr)).to eq(arr) + end +end \ No newline at end of file diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/spec/spec_helper.rb b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/solutions/merge_sort.js b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/solutions/merge_sort.js new file mode 100644 index 00000000..ed8c5fd4 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/solutions/merge_sort.js @@ -0,0 +1,89 @@ +function merge(arr1, arr2) { + const result = []; + + while (arr1.length && arr2.length) { + result.push((arr1[0] < arr2[0]) ? arr1.shift() : arr2.shift()); + } + + return [...result, ...arr1, ...arr2]; +} + +function mergeSort(arr) { + if (arr.length < 2) { + return arr; + } + + const middle = Math.floor(arr.length / 2); + const left = mergeSort(arr.slice(0, middle)); + const right = mergeSort(arr.slice(middle)); + + return merge(left, right); +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [1, 2]"); + console.log(mergeSort([2, 1])); + + console.log(""); + + console.log("Expecting: [1, 2, 3]"); + console.log(mergeSort([1, 2, 3])); + + console.log(""); + + console.log("Expecting: [-10, 0, 2, 2, 5, 10, 20]"); + console.log(mergeSort([10, -10, 0, 2, 20, 5, 2])); + + console.log(""); + + console.log("Expecting: []"); + console.log(mergeSort([])); + + console.log(""); + + console.log("Expecting: [3]"); + console.log(mergeSort([3])); +} + +module.exports = mergeSort; + +// Please add your pseudocode to this file +/**************************************************************************** + * return array if length < 2 + * + * initialize middle by rounding down length / 2 + * initialize left with first half of array (0 to middle, exclusive) + * initialize right with second half (middle to end inclusive) + * + * recursively divide left and store it in left + * recursively divide right and store it in right + * + * return the result of sorting and merging left and right + * **************************************************************************/ + +// And a written explanation of your solution +/**************************************************************************** + * I started by thinking about when I'd want to return the input as is. If the + * input is 1 element or less, I can just return it, so I made that my base case. + * Next I divided the input into the left half and the right half. Since it needs + * to be divided until each side of the list is as small as possible, I placed + * my recursive calls there, so that the left and right would be divided until + * they hit the base case. If I think about a small input, such as a list with + * only 2 numbers, it would recurse like so: + * Initial call: mergeSort([2, 1]) + * left = mergeSort([2]) + * // Pause ^, and then return [2] up the stack, and store in left + * right = mergeSort([1]) + * // Pause ^, and then return [1] up the stack, and store in right + * + * My list still needs to be sorted and merged. So now I can call my helper + * function: merge(left, right) => merge([2], [1]) => [1, 2] + * + * Since my helper function sorts and merges lists, I can return its result. + * If the list is larger than 2 elements, it'll return the sorted portion up + * the stack to the previous frame, where it'll then be merged with the other + * portion of the list that was sorted and merged. It'll keep on doing this + * until there are no frames left on the stack, resulting in it returning the + * entire list as one sorted array. + * **************************************************************************/ \ No newline at end of file diff --git a/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/solutions/merge_sort.rb b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/solutions/merge_sort.rb new file mode 100644 index 00000000..b19f07f4 --- /dev/null +++ b/09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort/solutions/merge_sort.rb @@ -0,0 +1,85 @@ +def merge(arr1, arr2) + result = [] + + while arr1.length > 0 && arr2.length > 0 + result.push arr1.first < arr2.first ? arr1.shift : arr2.shift + end + + result + arr1 + arr2 +end + +def merge_sort(arr) + return arr if arr.length < 2 + + middle = arr.length / 2 + left = merge_sort(arr[0...middle]) + right = merge_sort(arr[middle..-1]) + + merge(left, right) +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [1, 2]" + print merge_sort([2, 1]) + + puts + + puts "Expecting: [1, 2, 3]" + print merge_sort([1, 2, 3]) + + puts + + puts "Expecting: [-10, 0, 2, 2, 5, 10, 20]" + print merge_sort([10, -10, 0, 2, 20, 5, 2]) + + # Don't forget to add your own! + puts + + puts "Expecting: []" + print merge_sort([]) + + puts + + puts "Expecting: [3]" + print merge_sort([3]) +end + +# Please add your pseudocode to this file +################################################################################# +# return array if length < 2 +# +# initialize middle by rounding down length / 2 +# initialize left with first half of array (0 to middle, exclusive) +# initialize right with second half (middle to end inclusive) +# +# recursively divide left and store it in left +# recursively divide right and store it in right +# +# return the result of sorting and merging left and right +################################################################################# + +# And a written explanation of your solution +################################################################################# +# I started by thinking about when I'd want to return the input as is. If the +# input is 1 element or less, I can just return it, so I made that my base case. +# Next I divided the input into the left half and the right half. Since it needs +# to be divided until each side of the list is as small as possible, I placed +# my recursive calls there, so that the left and right would be divided until +# they hit the base case. If I think about a small input, such as a list with +# only 2 numbers, it would recurse like so: +# Initial call: mergeSort([2, 1]) +# left = mergeSort([2]) +# // Pause ^, and then return [2] up the stack, and store in left +# right = mergeSort([1]) +# // Pause ^, and then return [1] up the stack, and store in right +# +# My list still needs to be sorted and merged. So now I can call my helper +# function: merge(left, right) => merge([2], [1]) => [1, 2] +# +# Since my helper function sorts and merges lists, I can return its result. +# If the list is larger than 2 elements, it'll return the sorted portion up +# the stack to the previous frame, where it'll then be merged with the other +# portion of the list that was sorted and merged. It'll keep on doing this +# until there are no frames left on the stack, resulting in it returning the +# entire list as one sorted array. +################################################################################# diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/.gitignore b/10-week-8--searching/00-days-1-to-3--binary-search/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/README.md b/10-week-8--searching/00-days-1-to-3--binary-search/README.md new file mode 100644 index 00000000..2e92f3b6 --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/README.md @@ -0,0 +1,153 @@ +# Days 1-3: Binary Search + +Binary search is a recursive algorithm that searches a sorted list for a target value. For a sorted list, it is faster than iterating over the entire list until the target is found, since a binary search will not visit every single element in the list. Keep in mind that we perform binary searches on sorted datasets only! + +## How Does It Work? + +Hopefully, you are old enough to remember the phone book (a large alphabetically-ordered directory of businesses and residents of a county or town and their phone numbers) because we're about to dive deep into its pages! + +![Phone Book](./phone_book.jpeg) + +Let's imagine we're searching for Deirdre Xanadu and we're trying to find the page with the "X" names (let's pretend there's only one page of them). Since Deirdre's last name starts with an "X", she'll probably be close to the end of the phonebook. With our good ol' iterative search, we can start at page 1 or the last page, and look at every page until we find our Xanadacious friend. Starting at page 1 will be quick if there are barely any people whose last names start with the letters "A" through "X", and starting at the last page will only be quick if barely any residents' last names start with letters "Z" through "X". In the worst case, Deirdre will be on the last page or first page, depending on where we start our search, and we will have visited every page. + +![Binary Search](./binary_search.gif) + +Now let's use a binary search. Instead of starting on the first or last page, we start in the middle of the book. Let's say the last names on this page start with "M". "M" comes before "X", so we take the second half of the book in our hands, locate the middle page of that portion, and go straight there. Let's say we land on the page of "Y" names. "X" comes before "Y", so we take the part of the book with "M" names up to the part with the "Y" names in our hands, find the middle and go there. Let's say we land on "X": we can check that page for Deirdre Xanadu. We found our friend without going to every page! + +Here's a summary of the process we just used to find Xanadu: + +``` +Go to middle of phone book +Last names start with "M" +Is "M" == "X": + No. +Is "M" < "X": + Yes. Need to search half going from "M" to "Z". + +Go to middle of portion of phone book ("M" to "Z"): +Last names start with "Y" +Is "Y" == "X": + No. +Is "Y" < "X": + No. Need to search first half of portion from "M" to "Y" + +Go to middle of portion of phone book ("M" to "Y") +Last names start with "X" +Is "X" == "X" + Yes! Find Xanadu's entry! +``` + +From the above example, you may notice that we keep dividing the input into two parts: a left side containing everything that comes before the middle, and a right side containing everything after the middle. We then check if the target value we're searching for is less or more than the middle. If the target is less than the middle value, we search the left side. If it's more than the middle, we search the right side. As we search, our input gets smaller and smaller until we either find the target or there is nothing left to search. + +Let's take a look at another example: + +``` +List: [1, 2, 3, 4, 5] +Target: 1 + +Middle: 3 +Is Middle == Target: + No. +Is Middle < Target: + No. +Left: [1, 2] +Search Left for Target + +Middle: 2 +Is Middle == Target: + No. +Is Middle < Target: + No. +Left: [1] +Search Left for Target + +Middle: 1 +Is Middle == Target: + Yes! Return true +``` + +## Implement a Binary Search That Returns True or False + +Let's try something different today. Below you'll find pseudocode for binary search. Your job is to remove the pseudo part! Assume we're only searching for integers. + +``` +function binary_search(array, target): + return false if array is empty + + initialize integer variable middle with middle index of input array + initialize integer variable middle_value with middle value from array + + return true if middle_value == target + + if middle_value > target: + return binary_search(left half of input, target) + else: + return binary_search(right half of input, target) +``` + +And here are some test cases to consider: + +``` +Input: arr = [1, 2, 3], target = 3 +Output: true + +Input: arr = [3, 5, 9], target = 10 +Output: false +``` + +Once you've got that working, take a moment to think. What is the worst-case time complexity for this algorithm? How does that compare to an iterative search. Why does this only work with sorted inputs? + +Also take some time to plot out what's happening on paper using a small input as an example. Maybe try to plot the following: + +- Input: arr = [-10, 4, 9, 30, 31], target: 30 +- Input: arr = [1, 2, 3], target: 5 + +## Bonus: Return the Index of the Target + +Can you modify the algorithm to return the index of the target? If the target isn't in the input Array, return -1. For the bonus, copy your code from earlier into the function called `binary_search_index`/`binarySearchIndex`, and then modify it. That way all of the tests will run correctly. + +``` +Input: arr = [1, 2, 3], target = 1 +Output: 0 + +Input: arr = [4, 7, 20], target = 100 +Output: -1 +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/binary_search.gif b/10-week-8--searching/00-days-1-to-3--binary-search/binary_search.gif new file mode 100644 index 00000000..8f701de0 Binary files /dev/null and b/10-week-8--searching/00-days-1-to-3--binary-search/binary_search.gif differ diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/javascript/binary_search.js b/10-week-8--searching/00-days-1-to-3--binary-search/javascript/binary_search.js new file mode 100644 index 00000000..2aab6800 --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/javascript/binary_search.js @@ -0,0 +1,36 @@ +function binarySearch(arr, target) { + // type your code here +} + +// BONUS: MODIFY YOUR CODE TO RETURN THE INDEX OF THE TARGET, -1 IF NOT FOUND +function binarySearchIndex(arr, target) { + +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: true"); + console.log("=>", binarySearch([1, 2, 3], 3)); + + console.log(""); + + console.log("Expecting: false"); + console.log("=>", binarySearch([3, 5, 9], 10)); + + // UNCOMMENT FOR BONUS + // console.log(""); + // console.log("Expecting: 0"); + // console.log("=>", binarySearchIndex([1, 2, 3], 1)); + + // console.log(""); + + // console.log("Expecting: -1"); + // console.log("=>", binarySearchIndex([4, 7, 20], 100)); +} + +module.exports = { + binarySearch, + binarySearchIndex +}; + +// Add a written explanation of your solution diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/javascript/package.json b/10-week-8--searching/00-days-1-to-3--binary-search/javascript/package.json new file mode 100644 index 00000000..3a21ca0e --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "binary_search", + "version": "1.0.0", + "description": "binary search", + "main": "binary_search.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/javascript/tests/binary_search.test.js b/10-week-8--searching/00-days-1-to-3--binary-search/javascript/tests/binary_search.test.js new file mode 100644 index 00000000..a1918ea8 --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/javascript/tests/binary_search.test.js @@ -0,0 +1,68 @@ +const { binarySearch, binarySearchIndex} = require("../../solutions/binary_search"); + +describe("binarySearch", () => { + test("can handle an empty input list", () => { + expect(binarySearch([], 10)).toBe(false); + }); + + test("can handle a list with one item", () => { + expect(binarySearch([10], 10)).toBe(true); + expect(binarySearch([9], 10)).toBe(false); + }); + + test("can handle a list with two items", () => { + expect(binarySearch([1, 5], 5)).toBe(true); + expect(binarySearch([1, 5], 10)).toBe(false); + }); + + test("can find a value on the left edge of a long list", () => { + expect(binarySearch([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 1)).toBe(true); + }); + + test("can find a value on the right edge of a long list", () => { + expect(binarySearch([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 12)).toBe(true); + }); + + test("can find a value somewhere outside the middle in a long list", () => { + expect(binarySearch([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 10)).toBe(true); + expect(binarySearch([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 3)).toBe(true); + }); + + test("returns false when the item isn't in a long list", () => { + expect(binarySearch([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 100)).toBe(false); + }); +}); + + +describe("binarySearchIndex", () => { + test("can handle an empty input list", () => { + expect(binarySearchIndex([], 10)).toBe(-1); + }); + + test("can handle a list with one item", () => { + expect(binarySearchIndex([10], 10)).toBe(0); + expect(binarySearchIndex([9], 10)).toBe(-1); + }); + + test("can handle a list with two items", () => { + expect(binarySearchIndex([1, 5], 5)).toBe(1); + expect(binarySearchIndex([1, 5], 10)).toBe(-1); + }); + + test("can find a value on the left edge of a long list", () => { + expect(binarySearchIndex([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 1)).toBe(0); + }); + + test("can find a value on the right edge of a long list", () => { + expect(binarySearchIndex([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 12)).toBe(11); + }); + + test("can find a value somewhere outside the middle in a long list", () => { + expect(binarySearchIndex([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 10)).toBe(9); + expect(binarySearchIndex([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 3)).toBe(2); + }); + + test("returns -1 when the item isn't in a long list", () => { + expect(binarySearchIndex([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 100)).toBe(-1); + }); +}); diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/phone_book.jpeg b/10-week-8--searching/00-days-1-to-3--binary-search/phone_book.jpeg new file mode 100644 index 00000000..38132a46 Binary files /dev/null and b/10-week-8--searching/00-days-1-to-3--binary-search/phone_book.jpeg differ diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/ruby/.rspec b/10-week-8--searching/00-days-1-to-3--binary-search/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/ruby/Gemfile b/10-week-8--searching/00-days-1-to-3--binary-search/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/ruby/binary_search.rb b/10-week-8--searching/00-days-1-to-3--binary-search/ruby/binary_search.rb new file mode 100644 index 00000000..5f6d7bd1 --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/ruby/binary_search.rb @@ -0,0 +1,32 @@ +def binary_search(arr, target) + # type your code in here +end + +# BONUS: MODIFY YOUR CODE TO RETURN THE INDEX OF THE TARGET, -1 IF NOT FOUND +def binary_search_index(arr, target) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: true" + puts "=>", binary_search([1, 2, 3], 3) + + puts + + puts "Expecting: false" + puts "=>", binary_search([3, 5, 9], 10) + + # Don't forget to add your own! + + # UNCOMMENT FOR BONUS + # puts + # puts "Expecting: 0" + # puts "=>", binary_search_index([1, 2, 3], 1) + + # puts + + # puts "Expecting: -1" + # puts "=>", binary_search_index([4, 7, 20], 100) +end + +# Add a written explanation of your solution diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/ruby/spec/binary_search_spec.rb b/10-week-8--searching/00-days-1-to-3--binary-search/ruby/spec/binary_search_spec.rb new file mode 100644 index 00000000..0e8b612a --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/ruby/spec/binary_search_spec.rb @@ -0,0 +1,67 @@ +require "../solutions/binary_search.rb" + +RSpec.describe "binary_search" do + it "can handle an empty input list" do + expect(binary_search([], 10)).to be false + end + + it "can handle a list with one item" do + expect(binary_search([10], 10)).to be true + expect(binary_search([9], 10)).to be false + end + + it "can handle a list with two items" do + expect(binary_search([1, 5], 5)).to be true + expect(binary_search([1, 5], 10)).to be false + end + + it "can find a value on the left edge of a long list" do + expect(binary_search([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 1)).to be true + end + + it "can find a value on the right edge of a long list" do + expect(binary_search([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 12)).to be true + end + + it "can find a value somewhere outside the middle in a long list" do + expect(binary_search([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 10)).to be true + expect(binary_search([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 3)).to be true + end + + it "returns false when the item isn't in a long list" do + expect(binary_search([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 100)).to be false + end +end + +RSpec.describe "binary_search_index" do + it "can handle an empty input list" do + expect(binary_search_index([], 10)).to eq(-1) + end + + it "can handle a list with one item" do + expect(binary_search_index([10], 10)).to eq(0) + expect(binary_search_index([9], 10)).to eq(-1) + end + + it "can handle a list with two items" do + expect(binary_search_index([1, 5], 5)).to eq(1) + expect(binary_search_index([1, 5], 10)).to eq(-1) + end + + it "can find a value on the left edge of a long list" do + expect(binary_search_index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 1)).to eq(0) + end + + it "can find a value on the right edge of a long list" do + expect(binary_search_index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 12)).to eq(11) + end + + it "can find a value somewhere outside the middle in a long list" do + expect(binary_search_index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 10)).to eq(9) + expect(binary_search_index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 3)).to eq(2) + end + + it "returns -1 when the item isn't in a long list" do + expect(binary_search_index([1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12], 100)).to eq(-1) + end +end \ No newline at end of file diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/ruby/spec/spec_helper.rb b/10-week-8--searching/00-days-1-to-3--binary-search/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/solutions/binary_search.js b/10-week-8--searching/00-days-1-to-3--binary-search/solutions/binary_search.js new file mode 100644 index 00000000..733edfb1 --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/solutions/binary_search.js @@ -0,0 +1,164 @@ +function binarySearch(arr, target) { + if (arr.length === 0) { + return false; + } + + const middle = Math.floor(arr.length / 2); + const middleValue = arr[middle]; + + if (middleValue === target) { + return true; + } + + const searchSide = middleValue > target ? + arr.slice(0, middle) : arr.slice(middle + 1); + + return binarySearch(searchSide, target); +} + +// BONUS: MODIFY YOUR CODE TO RETURN THE INDEX OF THE TARGET, -1 IF NOT FOUND +function binarySearchIndex(arr, target) { + if (arr.length === 0) { + return -1; + } + + const middle = Math.floor(arr.length / 2); + const middleValue = arr[middle]; + + if (middleValue === target) { + return middle; + } + + if (middleValue > target) { + return binarySearchIndex(arr.slice(0, middle), target); + } else { + const idx = binarySearchIndex(arr.slice(middle + 1), target); + + if (idx === -1) { + return -1; + } + + return idx + middle + 1; + } +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: true"); + console.log("=>", binarySearch([1, 2, 3], 3)); + + console.log(""); + + console.log("Expecting: false"); + console.log("=>", binarySearch([3, 5, 9], 10)); + + console.log(""); + + console.log("Expecting: true"); + console.log("=>", binarySearch([3, 5, 9, 20], 3)); + + console.log(""); + + console.log("Expecting: false"); + console.log("=>", binarySearch([3, 5, 9, 20], -10)); + + console.log(""); + + console.log("Expecting: true"); + console.log("=>", binarySearch([3, 5, 9, 20], 9)); + + console.log(""); + + console.log("Expecting: false"); + console.log("=>", binarySearch([], 9)); + + // UNCOMMENT FOR BONUS + console.log(""); + console.log("Expecting: 0"); + console.log("=>", binarySearchIndex([1, 2, 3], 1)); + + console.log(""); + + console.log("Expecting: -1"); + console.log("=>", binarySearchIndex([4, 7, 20], 100)); + + console.log(""); + + console.log("Expecting: 6"); + console.log("=>", binarySearchIndex([1, 2, 3, 4, 5, 6, 7, 8], 7)); + + console.log(""); + + console.log("Expecting: 4"); + console.log("=>", binarySearchIndex([4, 7, 20, 30, 40], 40)); + + console.log(""); + + console.log("Expecting: 2"); + console.log("=>", binarySearchIndex([1, 2, 3, 4, 5, 6, 7, 8], 3)); +} + +module.exports = { + binarySearch, + binarySearchIndex +}; + +// Add a written explanation of your solution +/**************************************************************************************** + * Big O time complexity is O(log n) because the input is divided on each recursive call. + * Let's assume we're searching for a value not in the list. The following number of + * recursive calls are made for the following input sizes: + * Input size 1 => Recursive calls 1 + * Input size 2 => Recursive calls 2 + * Input size 3 => Recursive calls 2 + * Input size 4 => Recursive calls 3 + * Input size 10 => Recursive calls 4 + * + * The true/false algorithm is already explained in the README so I'll explain the bonus. + * To find the index, we have to return the middle index when the target is found, otherwise + * return -1. This is simple when the function only searches left. We can just return the middle, + * or rather the result of calling the function on the left side. Things get a little complicated + * when we search the right side. Since we're always dividing the input into smaller and smaller + * subsets, it means the middle on each recursive call will no longer reflect the actual location + * of the target when searching the right side. This is because the input only contains the right + * half of the previous input. We also need to make sure we return -1 if the target isn't found. + * + * So for the right side, we store the result of the recursive call in a variable. This will be the + * middle idx when the target is found or -1. If it's -1, we need to return that value. Otherwise, + * we return the result of adding the idx to the middle value in that frame and then add one. We add + * one because we removed the middle value from the input when we made the recursive call, and we need + * to make up for that. Let's take a look at a small example: + * + * arr = [1, 2, 3], target = 3, expected output = 2 + * + * If we don't add the middle or 1, we'll get 0 as the result, which is incorrect: + * Initial call: [1, 2, 3] + * Middle: 1 + * Middle value: 2 + * Go Right + * + * Recursive call 1: [3] + * Middle: 0 + * Middle value: 3 + * Found + * Return Middle up stack: 0 + * + * Initial call receives 0 + * Return 0 + * + * Now let's see what happens when we add the middle + 1 every time we go right: + * Initial call: [1, 2, 3] + * Middle: 1 + * Middle value: 2 + * Go Right + * + * Recursive call 1: [3] + * Middle: 0 + * Middle value: 3 + * Found + * Return Middle up stack: 0 + * + * Initial call receives 0 + * Adds Middle and 1 to 0: 0 + 1 (<- value of Middle at initial call) + 1 + * Returns 2 + * ****************************************************************************************/ \ No newline at end of file diff --git a/10-week-8--searching/00-days-1-to-3--binary-search/solutions/binary_search.rb b/10-week-8--searching/00-days-1-to-3--binary-search/solutions/binary_search.rb new file mode 100644 index 00000000..86426756 --- /dev/null +++ b/10-week-8--searching/00-days-1-to-3--binary-search/solutions/binary_search.rb @@ -0,0 +1,150 @@ +def binary_search(arr, target) + return false if arr.empty? + + middle = arr.length / 2 + middle_value = arr[middle] + + return true if middle_value == target + + search_side = middle_value > target ? arr[0...middle] : arr[middle + 1..-1] + + return binary_search(search_side, target) +end + +# BONUS: MODIFY YOUR CODE TO RETURN THE INDEX OF THE TARGET, -1 IF NOT FOUND +def binary_search_index(arr, target) + return -1 if arr.empty? + + middle = arr.length / 2 + middle_value = arr[middle] + + return middle if middle_value == target + + if middle_value > target + return binary_search_index(arr[0...middle], target) + else + idx = binary_search_index(arr[middle + 1..-1], target) + + return -1 if idx == -1 + + return idx + middle + 1 + end +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: true" + puts "=>", binary_search([1, 2, 3], 3) + + puts + + puts "Expecting: false" + puts "=>", binary_search([3, 5, 9], 10) + + puts + + puts "Expecting: true" + puts "=>", binary_search([3, 5, 9, 20], 3) + + puts + + puts "Expecting: false" + puts "=>", binary_search([3, 5, 9, 20], -10) + + puts + + puts "Expecting: true" + puts "=>", binary_search([3, 5, 9, 20], 9) + + puts + + puts "Expecting: false" + puts "=>", binary_search([], 9) + + # Don't forget to add your own! + + # UNCOMMENT FOR BONUS + puts + + puts "Expecting: 0" + puts "=>", binary_search_index([1, 2, 3], 1) + + puts + + puts "Expecting: -1" + puts "=>", binary_search_index([4, 7, 20], 100) + + puts + + puts "Expecting: 6" + puts "=>", binary_search_index([1, 2, 3, 4, 5, 6, 7, 8], 7) + + puts + + puts "Expecting: 4" + puts "=>", binary_search_index([4, 7, 20, 30, 40], 40) + + puts + + puts "Expecting: 2" + puts "=>", binary_search_index([1, 2, 3, 4, 5, 6, 7, 8], 3) +end + +# Add a written explanation of your solution +######################################################################################################## + # Big O time complexity is O(log n) because the input is divided on each recursive call. + # Let's assume we're searching for a value not in the list. The following number of + # recursive calls are made for the following input sizes: + # Input size 1 => Recursive calls 1 + # Input size 2 => Recursive calls 2 + # Input size 3 => Recursive calls 2 + # Input size 4 => Recursive calls 3 + # Input size 10 => Recursive calls 4 + # + # The true/false algorithm is already explained in the README so I'll explain the bonus. + # To find the index, we have to return the middle index when the target is found, otherwise + # return -1. This is simple when the function only searches left. We can just return the middle, + # or rather the result of calling the function on the left side. Things get a little complicated + # when we search the right side. Since we're always dividing the input into smaller and smaller + # subsets, it means the middle on each recursive call will no longer reflect the actual location + # of the target when searching the right side. This is because the input only contains the right + # half of the previous input. We also need to make sure we return -1 if the target isn't found. + # + # So for the right side, we store the result of the recursive call in a variable. This will be the + # middle idx when the target is found or -1. If it's -1, we need to return that value. Otherwise, + # we return the result of adding the idx to the middle value in that frame and then add one. We add + # one because we removed the middle value from the input when we made the recursive call, and we need + # to make up for that. Let's take a look at a small example: + # + # arr = [1, 2, 3], target = 3, expected output = 2 + # + # If we don't add the middle or 1, we'll get 0 as the result, which is incorrect: + # Initial call: [1, 2, 3] + # Middle: 1 + # Middle value: 2 + # Go Right + # + # Recursive call 1: [3] + # Middle: 0 + # Middle value: 3 + # Found + # Return Middle up stack: 0 + # + # Initial call receives 0 + # Return 0 + # + # Now let's see what happens when we add the middle + 1 every time we go right: + # Initial call: [1, 2, 3] + # Middle: 1 + # Middle value: 2 + # Go Right + # + # Recursive call 1: [3] + # Middle: 0 + # Middle value: 3 + # Found + # Return Middle up stack: 0 + # + # Initial call receives 0 + # Adds Middle and 1 to 0: 0 + 1 (<- value of Middle at initial call) + 1 + # Returns 2 +######################################################################################################## \ No newline at end of file diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/.gitignore b/10-week-8--searching/01-day-4--manual-binary-tree/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/README.md b/10-week-8--searching/01-day-4--manual-binary-tree/README.md new file mode 100644 index 00000000..ce461e06 --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/README.md @@ -0,0 +1,182 @@ +# Day 4: What Is a Binary Search Tree? + +![Valid tree](./valid_tree.png) + +A binary search tree (BST) is a data structure consisting of nodes: it's called a Binary Search Tree because each node has at most two child Nodes and because its nodes are arranged in a specific order that makes it possible to search quickly. + +Each node has two attributes, often called `left` and `right`, and an additional attribute that stores the `value` of that node. The `left` Node contains a `value` less than the parent node, and the `right` node contains a value that is greater than or equal to the parent node. Any node that has at least one child node is called a parent node, and any node that doesn't have any children is called a leaf node. The node at the top of the tree is called the root node. + +``` +left_node = new Node(1) +=> Node with value 1, left is null or nil, right is null or nil + +right_node = new Node(3) +=> Node with value 3, left is null or nil, right is null or nil + +// Both left_node and right_node are leaf nodes since they have no children + +root_node = new Node(2, left_node, right_node) +=> Node with value 2, left is Node with value 1, right is Node with value 3 +// This is the top of the tree, so it is the root, and because it has children, it's also a parent node +``` + +It is important to note that a BST is not the same as a binary tree. A BST is a type of binary tree that follows specific rules, such as the ability to traverse it in such a way that its nodes' values are output in sorted order. A binary tree, in contrast, can have nodes that are in no specific order whatsoever. + +``` +bst_in_order_traversal(bst_root) +=> [-1, 3, 5, 10] + +binary_tree_in_order_traversal(bt_root) +=> [10, -20, 3, 0, 7] +``` + +## Important Terms + +- root: The top node of the tree, the one where we begin traversing the tree (similar to the zeroth element in an Array). May or may not have children. +- parent: Any node that has at least one child, i.e. its `left` or `right` attribute, or both, point to another node. +- left: An attribute on every node in the tree. The node will have a value lesser than its parent. If it doesn't point to another node, it will be null or nil or some other falsy value. +- right: An attribute on every node in the tree. The node will have a value greater than or equal to its parent. If it doesn't point to another node, it will be null or nil or some other falsy value. +- leaf: A node that has no children. +- subtree: Any time we see a parent node, other than the root node, we can refer to that node and all that branch off of it as a subtree. This is similar to how you think of a portion of an Array as a subset of that Array. + +## BST Rules + +For a BST to be valid, it must adhere to certain rules: + +1. Each node has a maximum of two children (`left` and/or `right` nodes). +2. Every parent node contains a value that is greater than the value of its `left` node and less than the value of its `right` node. +3. All nodes of a left subtree are less than the root node. +4. All nodes of a right subtree are greater than the root node. +5. All subtrees are also valid BSTs. This is just another way of rephrasing points 3 and 4 above. + +It's important to note that a tree with only a root node is considered valid. An empty tree is also valid - weird, but true. + +``` +root = new Node(7) +is_valid_bst(root) +=> true + +is_valid_bst(null) +=> true + +is_valid_bst("If this is true, then I've gone bananas!") +=> false +// guess we've not gone bananas! +``` + +### Examples of Valid BSTs + +![valid trees](./valid_trees.png) + +Both of these binary trees are valid BSTs. + +The left tree is valid because: + +- Every node value to the left of the root is less than the value of the root. Every node value to the right of the root is greater than the root's value. +- The same is true for all subtrees. For example, if we start at the node with value 5, the node to its left is less than 5 and the node to its right is greater than 5. + +The right tree is valid because: + +- All of the nodes to the right of the root (value 5) are greater than the root. +- As we go down the right side, each right node is greater than its parent node. + +### Examples of Invalid BSTs + +![invalid trees](./invalid_trees.png) + +Both of these trees are invalid BSTs. + +The left tree is invalid because: + +- The node with value 11 is greater than the root node with value 10. The node with value 11 is to the left of the root and needs to be less than 10 and greater than 5. +- The node with value 1 is less than the root node. Since it's on the right side of the tree, it needs to be greater than 10 and less than 16. If we consider the node 12 and all of its child nodes as a subtree, then we also notice that the node with value of 1 needs to be greater than 12. In summary, this misplaced node needs to be greater than 12 and less than 16. + +The right tree is invalid because: + +- The node with value 25 is to the right of the node with value 30, meaning that its value must be greater than 30. + +## Implement a BST + +For this challenge, we'll be creating the `Node` class with the appropriate attributes and then we'll manually create valid BSTs to ensure that we understand the rules of BSTs. Our tests will check that your manually created BSTs are valid. We will not be creating a parent class to track the `root`. + +Use the language of your choosing. We've included starter files for some languages where you can optionally pseudocode, explain your solution and code. + +**_Feel free to run our tests whenever you like for this challenge! It can be hard to validate a BST on your own, especially if you're a newbie._** + +### Create the `Node` class + +A `Node` should have three attributes: `left`, `right`, `value`. When initializing a new Node, it should take three arguments: `value`, `left`, and `right`. + +When a new `Node` is initialized without a `left` node, `left` should be set to an appropriate falsy value by default. The same applies to the `right` node. Expect that every `Node` will be initialized with a `value`. + +``` +node = new Node(7) +=> Node: value = 7, left = null / nil, right = null / nil + +left = new Node(4) +right = new Node(10) +root = new Node(7, left, right) +=> Node: value = 7, left = Node with value 4, right = Node with value 10 +``` + +### Manually Create Valid BSTs + +Let's manually create some BSTs using the `Node` class we just declared. For this part, we'll have several methods with unique names, and it'll be your job to create the BST and return the `root` node. Our tests will then traverse the tree and determine if it's valid. + +You will be given an Array of values in sorted order, which you'll use to create your tree. Drawing the tree on paper can make it easier to understand how the nodes connect to one another. Be aware, that there is almost always more than one way to create a valid BST from a list of values. You get to decide how! In other words, you get to decide which node in the list to use as the root. + +Example: + +``` +list = [1, 2, 3] +/* i want my tree to look like this: + 2 + 1 3 +*/ + +function one_to_three_bst() { + left = new Node(1) + right = new Node(3) + root = new Node(2, left, right) + + return root +} +``` + +You can find the methods and their accompanying lists in the starter files. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/invalid_trees.png b/10-week-8--searching/01-day-4--manual-binary-tree/invalid_trees.png new file mode 100644 index 00000000..f73482e2 Binary files /dev/null and b/10-week-8--searching/01-day-4--manual-binary-tree/invalid_trees.png differ diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/javascript/binary_tree.js b/10-week-8--searching/01-day-4--manual-binary-tree/javascript/binary_tree.js new file mode 100644 index 00000000..14dd9232 --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/javascript/binary_tree.js @@ -0,0 +1,35 @@ +class Node { + constructor() { + // add your Node class code + } +} + +// list = [1, 4, 7] +function oneToSeven() { + // manually create the BST + // then return the root node +} + +// list = [10, 40, 45, 46, 50] +function tenToFifty() { + +} + +// list = [-20, -19, -17, -15, 0, 1, 2, 10] +function negativeToPositive() { + +} + +if (require.main === module) { + // add your own tests in here if you want +} + +module.exports = { + Node, + oneToSeven, + tenToFifty, + negativeToPositive +}; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/javascript/package.json b/10-week-8--searching/01-day-4--manual-binary-tree/javascript/package.json new file mode 100644 index 00000000..b18a5a25 --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "binary_tree", + "version": "1.0.0", + "description": "binary tree", + "main": "binary_tree.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/javascript/tests/binary_tree.test.js b/10-week-8--searching/01-day-4--manual-binary-tree/javascript/tests/binary_tree.test.js new file mode 100644 index 00000000..c17ce3fc --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/javascript/tests/binary_tree.test.js @@ -0,0 +1,65 @@ +const { Node, oneToSeven, tenToFifty, negativeToPositive } = require("../binary_tree"); + +describe("Node", () => { + const node = new Node(10); + + test("sets the value when a new Node is initialized", () => { + expect(node.value).toBe(10); + }); + + test("sets the left and right node attributes to null when no nodes are provided", () => { + expect(node.left).toBe(null); + expect(node.right).toBe(null); + }); + + + test("sets the left and right nodes attributes when they are provided", () => { + const left = new Node(1); + const right = new Node(10); + const root = new Node(5, left, right); + + expect(root.left).toBe(left); + expect(root.right).toBe(right); + }); +}); + +function bstToArray(root) { + if (root === null) { + return []; + } + + const stack = [root]; + const sorted = []; + + while (stack.length) { + const node = stack[stack.length - 1]; + + if (node.left !== null) { + stack.push(node.left); + node.left = null; + continue; + } + + sorted.push(stack.pop().value); + + if (node.right !== null) { + stack.push(node.right); + } + } + + return sorted; +} + +describe("Manual BSTs", () => { + test("oneToSeven() returns the root node of a valid BST", () => { + expect(bstToArray(oneToSeven())).toEqual([1, 4, 7]); + }); + + test("tenToFifty() returns the root node of a valid BST", () => { + expect(bstToArray(tenToFifty())).toEqual([10, 40, 45, 46, 50]); + }); + + test("negativeToPositive()", () => { + expect(bstToArray(negativeToPositive())).toEqual([-20, -19, -17, -15, 0, 1, 2, 10]); + }); +}); diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/ruby/.rspec b/10-week-8--searching/01-day-4--manual-binary-tree/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/ruby/Gemfile b/10-week-8--searching/01-day-4--manual-binary-tree/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/ruby/binary_tree.rb b/10-week-8--searching/01-day-4--manual-binary-tree/ruby/binary_tree.rb new file mode 100644 index 00000000..4c10eb41 --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/ruby/binary_tree.rb @@ -0,0 +1,30 @@ +class Node + attr_accessor :value, :left, :right + + def initialize() + # add your Node class code + end +end + +# list = [1, 4, 7] +def one_to_seven + # manually create the BST + # then return the root node +end + +# list = [10, 40, 45, 46, 50] +def ten_to_fifty + +end + +# list = [-20, -19, -17, -15, 0, 1, 2, 10] +def negative_to_positive + +end + +if __FILE__ == $PROGRAM_NAME + # Add your own tests if you want +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/ruby/spec/binary_tree_spec.rb b/10-week-8--searching/01-day-4--manual-binary-tree/ruby/spec/binary_tree_spec.rb new file mode 100644 index 00000000..1b7cefbb --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/ruby/spec/binary_tree_spec.rb @@ -0,0 +1,70 @@ +require "./binary_tree" + +describe "Node" do + let(:node) { Node.new(10) } + + it "sets the value when a new Node is initialized" do + expect(node.value).to eq(10) + end + + it "sets the left and right node attributes to nil when no nodes are provided" do + expect(node.left).to be_nil + expect(node.right).to be_nil + end + + it "sets the left and right nodes attributes when they are provided" do + left = Node.new(1) + right = Node.new(10) + root = Node.new(5, left, right) + + expect(root.left).to be(left) + expect(root.right).to be(right) + end +end + +def bst_to_array(root) + if (root == nil) + return [] + end + + stack = [root] + sorted = [] + + while stack.length > 0 + node = stack.last + + if (node.left != nil) + stack.push(node.left) + node.left = nil + next + end + + sorted.push(stack.pop().value) + + if (node.right != nil) + stack.push(node.right) + end + end + + sorted +end + +describe "Manual BSTs" do + describe "one_to_seven" do + it "returns the root node of a valid BST" do + expect(bst_to_array(one_to_seven)).to eq([1, 4, 7]) + end + end + + describe "ten_to_fifty" do + it "returns the root node of a valid BST" do + expect(bst_to_array(ten_to_fifty)).to eq([10, 40, 45, 46, 50]) + end + end + + describe "negative_to_positive" do + it "returns the root node of a valid BST" do + expect(bst_to_array(negative_to_positive)).to eq([-20, -19, -17, -15, 0, 1, 2, 10]) + end + end +end diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/ruby/spec/spec_helper.rb b/10-week-8--searching/01-day-4--manual-binary-tree/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/solutions/binary_tree.js b/10-week-8--searching/01-day-4--manual-binary-tree/solutions/binary_tree.js new file mode 100644 index 00000000..e31f6bdf --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/solutions/binary_tree.js @@ -0,0 +1,77 @@ +class Node { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } +} + +// list = [1, 4, 7] +// 4 +// 1 7 +function oneToSeven() { + const left = new Node(1); + const right = new Node(7); + + return new Node(4, left, right); +} + +// list = [10, 40, 45, 46, 50] +// 45 +// 40 50 +// 10 46 +function tenToFifty() { + const tenNode = new Node(10); + const fortyNode = new Node(40, tenNode); + const fortySixNode = new Node(46); + const fiftyNode = new Node(50, fortySixNode); + + return new Node(45, fortyNode, fiftyNode); +} + +// YOU COULD ALSO DO THIS (THERE ARE MORE WAYS STILL!) +// BUT WE GENERALLY DON'T WANT TO MAKE TREES LIKE THIS, YOU'LL FIND OUT WHY LATER +// 10 +// 40 +// 45 +// 46 +// 50 +// function tenToFifty() { +// const fifty = new Node(50); +// const fortySix = new Node(46, null, fifty); +// const fortyFive = new Node(45, null, fortySix); +// const forty = new Node(40, null, fortyFive); + +// return new Node(10, null, forty); +// } + +// list = [-20, -19, -17, -15, 0, 1, 2, 10] +// -15 +// -19 2 +// -20 -17 0 10 +// 1 +function negativeToPositive() { + const nTwenty = new Node(-20); + const nSeventeen = new Node(-17); + const nNineteen = new Node(-19, nTwenty, nSeventeen); + const one = new Node(1); + const zero = new Node(0, null, one); + const ten = new Node(10); + const two = new Node(2, zero, ten); + + return new Node(-15, nNineteen, two); +} + +if (require.main === module) { + // add your own tests in here if you want +} + +module.exports = { + Node, + oneToSeven, + tenToFifty, + negativeToPositive +}; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/solutions/binary_tree.rb b/10-week-8--searching/01-day-4--manual-binary-tree/solutions/binary_tree.rb new file mode 100644 index 00000000..1517def2 --- /dev/null +++ b/10-week-8--searching/01-day-4--manual-binary-tree/solutions/binary_tree.rb @@ -0,0 +1,72 @@ +class Node + attr_accessor :value, :left, :right + + def initialize(value, left = nil, right = nil) + @value = value + @left = left + @right = right + end +end + +# list = [1, 4, 7] +# 4 +# 1 7 +def one_to_seven + left = Node.new(1) + right = Node.new(7) + + Node.new(4, left, right) +end + +# list = [10, 40, 45, 46, 50] +# 45 +# 40 50 +# 10 46 +def ten_to_fifty + tenNode = Node.new(10) + fortyNode = Node.new(40, tenNode) + fortySixNode = Node.new(46) + fiftyNode = Node.new(50, fortySixNode) + + Node.new(45, fortyNode, fiftyNode) +end + +# YOU COULD ALSO DO THIS (THERE ARE MORE WAYS STILL!) +# BUT WE GENERALLY DON'T WANT TO MAKE TREES LIKE THIS, YOU'LL FIND OUT WHY LATER +# 10 +# 40 +# 45 +# 46 +# 50 +# def tenToFifty +# fifty = Node.new(50) +# fortySix = Node.new(46, nil, fifty) +# fortyFive = Node.new(45, nil, fortySix) +# forty = Node.new(40, nil, fortyFive) + +# Node.new(10, nil, forty) +# end + +# list = [-20, -19, -17, -15, 0, 1, 2, 10] +# -15 +# -19 2 +# -20 -17 0 10 +# 1 +def negative_to_positive + nTwenty = Node.new(-20) + nSeventeen = Node.new(-17) + nNineteen = Node.new(-19, nTwenty, nSeventeen) + one = Node.new(1) + zero = Node.new(0, nil, one) + ten = Node.new(10) + two = Node.new(2, zero, ten) + + Node.new(-15, nNineteen, two) +end + +if __FILE__ == $PROGRAM_NAME + # Add your own tests if you want +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/valid_tree.png b/10-week-8--searching/01-day-4--manual-binary-tree/valid_tree.png new file mode 100644 index 00000000..a7b3f284 Binary files /dev/null and b/10-week-8--searching/01-day-4--manual-binary-tree/valid_tree.png differ diff --git a/10-week-8--searching/01-day-4--manual-binary-tree/valid_trees.png b/10-week-8--searching/01-day-4--manual-binary-tree/valid_trees.png new file mode 100644 index 00000000..3bf1fe45 Binary files /dev/null and b/10-week-8--searching/01-day-4--manual-binary-tree/valid_trees.png differ diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/.gitignore b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/README.md b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/README.md new file mode 100644 index 00000000..730a4e5a --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/README.md @@ -0,0 +1,106 @@ +# Day 5: Balanced and Unbalanced Binary Trees + +![Balanced and unbalanced tree](./valid_trees.png) + +Binary trees can be balanced or unbalanced. In the image above, the left tree is balanced, while the right tree is unbalanced. For the left tree, the left subtree is the same height as the right subtree, making it balanced. For the right tree, there is only a right subtree, making it unbalanced. + +## How to Calculate the Height of a Tree + +We can look at a visualization of a tree and intuitively determine whether its left and right subtrees are the same height, much the same way as we determine whether two people are the same height. But, when it comes to trees, we actually have a more specific definition of height and it can be measured numerically, just like your own height! + +![Tree height](./tree_height.png) + +For a binary tree, the height is defined as the distance from the root node (where the tree starts) to the furthest leaf node (a node without any children). To calculate the height we count up the layers (or depth) of nodes (not all of the nodes), and then subtract 1, since we aren't supposed to include the root node in the height. This means that the height for both trees in the image at the top of this reading is 2. + +## How to Determine If a Tree Is Balanced or Unbalanced + +A tree is considered balanced if its right subtree and left subtree are the same height or they have a difference of 1. All other trees are unbalanced. Here are two examples of balanced trees: + +``` + 0 10 + / \ / \ +-10 20 0 20 + / + 19 +``` + +Visually, it is quite easy to determine whether a tree is balanced or not. We look at the left and right sides and can almost immediately tell that they're the same, or roughly the same, height. In code, however, we have to traverse the left subtree and determine its depth, and then traverse the right subtree and determine its depth. Lastly, we compare those depths. We won't be coding this today, but we will be revisiting this in a later challenge. + +## Why Should We Balance a Tree? + +![Two trees to compare](./tree_compare.png) + +Take a look at the two trees above. Let's imagine that we want to see if the value 16 is in these trees. + +For the left tree, which is balanced, we can do the following: + +- Go to 10. +- Is 10 more or less than 16? It's less! +- Go right, to 12. +- Is 12 more or less than 16? It's less! +- Go right, to 16! found it. + +Notice that we didn't have to visit the left subtree at all, which means this algorithm would have a Big O run time of less than O(n)! We'll let you think about what the specific run time might be :) Or Google it. Or if you're familiar with the binary search algorithm, does this remind you of that? (Shh, writer, you're giving the answer away!) + +For the right tree, which is unabalanced, we have to visit every single node before we get to 16. This has a Big O run time of O(n), which is not as good as the balanced tree. + +So why do we want to aim for balanced trees!? All together now: To save time! (<- You can scream it in your head if you want, or out loud. You do you.) + +## Practice Building Balanced Trees Manually + +For practice, let's manually build balanced trees from lists. We'll include several methods in the starter files which you'll fill out. For each method, return the root node. Our tests will then check if the tree is balanced or unbalanced, and if it's a valid BST. Remember, a tree is balanced if the left and right subtrees have a height difference of 0 or 1. Note that there may be more than one way to create a balanced BST from the lists. + +Example: + +``` +list = [1, 2, 3] +/* i want my tree to look like this: + 2 + 1 3 +*/ + +function one_to_three_bst(): + left = new Node(1) + right = new Node(3) + root = new Node(2, left, right) + + return root +``` + +Use the language of your choosing. We've included starter files for some languages where you can optionally pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/balancing.js b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/balancing.js new file mode 100644 index 00000000..db989d7d --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/balancing.js @@ -0,0 +1,30 @@ +class Node { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } +} + +// list = [3, 5, 6, 9, 10, 20] +function threeToTwenty() { + +} + +// list = [10, 11, 30, 100, 200] +function tenToTwoHundred() { + +} + +if (require.main === module) { + // add tests in here if you need them +} + +module.exports = { + Node, + threeToTwenty, + tenToTwoHundred +}; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/package.json b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/package.json new file mode 100644 index 00000000..9f821b16 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "balancing", + "version": "1.0.0", + "description": "balanced vs unbalanced trees", + "main": "balancing.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/tests/balancing.test.js b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/tests/balancing.test.js new file mode 100644 index 00000000..83a1ac39 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/tests/balancing.test.js @@ -0,0 +1,24 @@ +const { threeToTwenty, tenToTwoHundred } = require("../../solutions/balancing"); +const { bstToArray, isBalanced } = require("./validation_methods"); + +describe("balanced BSTs", () => { + describe("threeToTwenty", () => { + test("returns the root node of a valid BST", () => { + expect(bstToArray(threeToTwenty())).toStrictEqual([3, 5, 6, 9, 10, 20]); + }); + + test("returns the root of a balanced BST", () => { + expect(isBalanced(threeToTwenty())).toBe(true); + }); + }); + + describe("tenToTwoHundred", () => { + test("returns the root node of a valid BST", () => { + expect(bstToArray(tenToTwoHundred())).toStrictEqual([10, 11, 30, 100, 200]); + }); + + test("returns the root of a balanced BST", () => { + expect(isBalanced(tenToTwoHundred())).toBe(true); + }); + }); +}); diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/tests/validation_methods.js b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/tests/validation_methods.js new file mode 100644 index 00000000..1bd6e0e6 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/javascript/tests/validation_methods.js @@ -0,0 +1,65 @@ +function bstToArray(root) { + if (root === null) { + return []; + } + + const stack = [root]; + const sorted = []; + + while (stack.length) { + const node = stack[stack.length - 1]; + + if (node.left !== null) { + stack.push(node.left); + node.left = null; + continue; + } + + sorted.push(stack.pop().value); + + if (node.right !== null) { + stack.push(node.right); + } + } + + return sorted; +} + +function isBalanced(root) { + if (root === null) { + return true; + } + + const leftHeight = branchHeight(root.left); + const rightHeight = branchHeight(root.right); + + return Math.abs(leftHeight - rightHeight) < 2; +} + +function branchHeight(root) { + let queue = root === null ? [] : [root]; + let count = 0; + + while(queue.length) { + ++count; + + queue = queue.reduce((accum, node) => { + if (node.left) { + accum.push(node.left); + } + + if (node.right) { + accum.push(node.right); + } + + return accum; + }, []); + } + + return count; +} + +module.exports = { + bstToArray, + isBalanced +}; \ No newline at end of file diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/.rspec b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/Gemfile b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/balancing.rb b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/balancing.rb new file mode 100644 index 00000000..d32b5995 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/balancing.rb @@ -0,0 +1,26 @@ +class Node + attr_accessor :value, :left, :right + + def initialize(value, left = nil, right = nil) + @value = value + @left = left + @right = right + end +end + +# list = [3, 5, 6, 9, 10, 20] +def three_to_twenty + +end + +# list = [10, 11, 30, 100, 200] +def ten_to_two_hundred + +end + +if __FILE__ == $PROGRAM_NAME + # Add tests if you need them +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/spec/balancing_spec.rb b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/spec/balancing_spec.rb new file mode 100644 index 00000000..bf7cda12 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/spec/balancing_spec.rb @@ -0,0 +1,24 @@ +require "./balancing" +require "./spec/validation_methods" + +RSpec.describe "balanced BSTs" do + context "three_to_twenty" do + it "returns the root node of a valid BST" do + expect(Validator.bst_to_array(three_to_twenty)).to eq([3, 5, 6, 9, 10, 20]) + end + + it "returns the root of a balanced BST" do + expect(Validator.balanced?(three_to_twenty)).to be true + end + end + + context "ten_to_two_hundred" do + it "returns the root node of a valid BST" do + expect(Validator.bst_to_array(ten_to_two_hundred)).to eq([10, 11, 30, 100, 200]) + end + + it "returns the root of a balanced BST" do + expect(Validator.balanced?(ten_to_two_hundred)).to be true + end + end +end \ No newline at end of file diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/spec/spec_helper.rb b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/spec/validation_methods.rb b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/spec/validation_methods.rb new file mode 100644 index 00000000..fe480346 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/ruby/spec/validation_methods.rb @@ -0,0 +1,56 @@ +class Validator + def self.bst_to_array(root) + if (root == nil) + return [] + end + + stack = [root] + sorted = [] + + while stack.length > 0 + node = stack.last + + if (node.left != nil) + stack.push(node.left) + node.left = nil + next + end + + sorted.push(stack.pop().value) + + if (node.right != nil) + stack.push(node.right) + end + end + + sorted + end + + def self.balanced?(root) + return true if root.nil? + + left_height = branch_height(root.left) + right_height = branch_height(root.right) + + (left_height - right_height).abs < 2 + end + + def self.branch_height(root) + queue = root.nil? ? [] : [root] + count = 0 + + until queue.empty? + count += 1 + + queue = queue.reduce([]) do |accum, node| + accum << node.left if node.left + accum << node.right if node.right + accum + end + end + + count + end +end + + diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/solutions/balancing.js b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/solutions/balancing.js new file mode 100644 index 00000000..c034a8da --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/solutions/balancing.js @@ -0,0 +1,47 @@ +class Node { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } +} + +// list = [3, 5, 6, 9, 10, 20] +// 6 +// 5 10 +// 3 9 20 +function threeToTwenty() { + const three = new Node(3); + const five = new Node(5, three); + const nine = new Node(9); + const twenty = new Node(20); + const ten = new Node(10, nine, twenty); + + return new Node(6, five, ten); +} + +// list = [10, 11, 30, 100, 200] +// 30 +// 11 100 +// 10 200 +function tenToTwoHundred() { + const ten = new Node(10); + const eleven = new Node(11, ten); + const two_hundred = new Node(200); + const hundred = new Node(100, null, two_hundred); + + return new Node(30, eleven, hundred); +} + +if (require.main === module) { + // add tests in here if you need them +} + +module.exports = { + Node, + threeToTwenty, + tenToTwoHundred +}; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/solutions/balancing.rb b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/solutions/balancing.rb new file mode 100644 index 00000000..eb3eff19 --- /dev/null +++ b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/solutions/balancing.rb @@ -0,0 +1,43 @@ +class Node + attr_accessor :value, :left, :right + + def initialize(value, left = nil, right = nil) + @value = value + @left = left + @right = right + end +end + +# list = [3, 5, 6, 9, 10, 20] +# 6 +# 5 10 +# 3 9 20 +def three_to_twenty + three = Node.new(3) + five = Node.new(5, three) + nine = Node.new(9) + twenty = Node.new(20) + ten = Node.new(10, nine, twenty) + + Node.new(6, five, ten) +end + +# list = [10, 11, 30, 100, 200] +# 30 +# 11 100 +# 10 200 +def ten_to_two_hundred + ten = Node.new(10) + eleven = Node.new(11, ten) + two_hundred = Node.new(200) + hundred = Node.new(100, nil, two_hundred) + + Node.new(30, eleven, hundred) +end + +if __FILE__ == $PROGRAM_NAME + # Add tests if you need them +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/tree_compare.png b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/tree_compare.png new file mode 100644 index 00000000..1b5f9bc7 Binary files /dev/null and b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/tree_compare.png differ diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/tree_height.png b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/tree_height.png new file mode 100644 index 00000000..85d085c0 Binary files /dev/null and b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/tree_height.png differ diff --git a/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/valid_trees.png b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/valid_trees.png new file mode 100644 index 00000000..3bf1fe45 Binary files /dev/null and b/10-week-8--searching/02-day-5--build-a-binary-tree---balancing/valid_trees.png differ diff --git a/11-pairing-exercise-3/00-pair-programming/README.md b/11-pairing-exercise-3/00-pair-programming/README.md new file mode 100644 index 00000000..3f2b486c --- /dev/null +++ b/11-pairing-exercise-3/00-pair-programming/README.md @@ -0,0 +1,26 @@ +# Pair Programming + +## Introduction + +For this activity, you will pair up and complete a challenge from the previous +exercises pair-programming style! In other words, one of you will be the driver +and the other will be the navigator, and then you will switch. Remember to be +patient with one another and to keep the lines of communication open. If one of +you becomes quiet for an extended period of time, ask a question to help keep +things moving. + +## Instructions + +- Choose a problem from the previous exercises that you were unable to solve, + have not tried yet, or struggled with a lot +- If you are the navigator, you will guide the driver by doing the following: + - Communicate in simple and small steps what code should be written + - Explain why you're making those decisions + - Inform the driver when they're misunderstanding your directions or are making syntactical errors + - Point out any improvements that can be made to the code + - If time allows and problem has been solved, guide the driver through a refactor to improve the code +- If you are the driver, you will write code according to the navigator's instructions: + - Ask questions when you don't understand the what or the why + - If you disagree with a decision, diplomatically state your disagreement and why + - Be the typist + - Stay focused on the current task diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/.gitignore b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/README.md b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/README.md new file mode 100644 index 00000000..0e4359c0 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/README.md @@ -0,0 +1,83 @@ +# Days 1 to 2: Level-Order aka Breadth-First Tree Traversal + +![Trees](./trees.png) + +There are a number of different ways to traverse a tree, or in other words, travel down a tree from its root node all the way through to its leaf nodes or to a target node. Today we'll be focusing on a type of traversal called level-order or breadth-first tree traversal, which can be used with any type of tree data structure. Typically, it is implemented iteratively. + +## What is level-order tree traversal? + +With level-order traversal we start at the root and then move down the tree one level, or layer, at a time. You can think of it as being similar to a printer: it outputs its ink in rows until the image is complete. You can also think of it as being similar to typing a document: you type one line going from left to right, and then move down to the next and repeat. + +Take a moment to look at the trees at the top of this README. If we were to perform a level-order traversal of the left tree and print out each value, we would get: 10, 5, 12, 3, 6, 16. For the tree on the right: 5, 30, 60. + +## But Why? + +Let's say we had our data stored in a tree, but it wasn't a binary search tree (a BST requires its nodes to be ordered). In this tree the values of our nodes can be stored in any order: + +![Trees](./unordered_tree.png) + +Now, let's say we want to find the shortest path to the node with a value of 30. That node could be anywhere! On the left side or the right side. If we do a level-order traversal (breadth-first search aka BFS), we can stop searching once we reach the correct depth. In contrast, if we were to go all the way down one path, before coming back up to the root and going down a different path, we could end up going much further down the tree than we need to. + +There are other uses for a level-order tree traversal, but we'll let you research that when you're ready to do so. + +## Perform a Level-Order Tree Traversal + +For this challenge, we're going to write a method that takes the root node of a tree, performs a level-order traversal, and returns an array representing each layer of the tree. The array will contain the values of the nodes, rather than the nodes themselves: + +``` + 1 + / \ + 2 3 + +level_order_traversal(root) +=> [1, 2, 3] +``` + +``` + 10 + / \ + 20 30 + / \ + 9 22 + +level_order_traversal(root) +=> [10, 20, 30, 9, 22] +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/javascript/package.json b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/javascript/package.json new file mode 100644 index 00000000..a59017f0 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "level_order_tree_traversal", + "version": "1.0.0", + "description": "level order tree traversal", + "main": "tree_traversal_bfs.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/javascript/tests/tree_traversal_bfs.test.js b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/javascript/tests/tree_traversal_bfs.test.js new file mode 100644 index 00000000..43c3c911 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/javascript/tests/tree_traversal_bfs.test.js @@ -0,0 +1,40 @@ +const { Node, levelOrderTraversal } = require("../tree_traversal_bfs"); + +describe("levelOrderTraversal", () => { + test("can handle an empty tree", () => { + expect(levelOrderTraversal(null)).toEqual([]); + }); + + test("can handle a single node, e.g. just the root", () => { + expect(levelOrderTraversal(new Node(5))).toEqual([5]); + }); + + test("can handle a tree with several nodes", () => { + expect(levelOrderTraversal(new Node(1, new Node(2), new Node(3)))).toEqual([1, 2, 3]); + expect(levelOrderTraversal(new Node(10, new Node(20, new Node(9), new Node(22)), new Node(30)))).toEqual([10, 20, 30, 9, 22]); + expect(levelOrderTraversal(new Node(10, + new Node(9, + new Node(8, + new Node(7, + new Node(32)), + new Node(6, null, + new Node(33))), + new Node(12, + new Node(11), + new Node(40))), + new Node(11, + new Node(20, + new Node(4), + new Node(90)), + new Node(30, + new Node(9), + new Node(89, null, + new Node(90, null, + new Node(34)))))))).toEqual([10, 9, 11, 8, 12, 20, 30, 7, 6, 11, 40, 4, 90, 9, 89, 32, 33, 90, 34]); + }); + + test("can handle a one-sided tree", () => { + expect(levelOrderTraversal(new Node(10, null, new Node(11, null, new Node(12, null, new Node(12)))))).toEqual([10, 11, 12, 12]); + expect(levelOrderTraversal(new Node(1, new Node(2, new Node(3, new Node(4)))))).toEqual([1, 2, 3, 4]); + }); +}); \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/javascript/tree_traversal_bfs.js b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/javascript/tree_traversal_bfs.js new file mode 100644 index 00000000..92e0cd4b --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/javascript/tree_traversal_bfs.js @@ -0,0 +1,33 @@ +class Node { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } +} + +function levelOrderTraversal(root) { + // type your code here +} + +if (require.main === module) { + let root = new Node(1, new Node(2), new Node(3)); + // add your own tests in here + console.log("Expecting: [[1], [2, 3]]"); + console.log(levelOrderTraversal(root)); + + console.log(""); + + root = new Node(10, new Node(20, new Node(9), new Node(22)), new Node(30)); + + console.log("Expecting: [[10], [20, 30], [9, 22]]"); + console.log(levelOrderTraversal(root)); +} + +module.exports = { + Node, + levelOrderTraversal +}; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/.rspec b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/Gemfile b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/spec/spec_helper.rb b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/spec/tree_traversal_bfs_spec.rb b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/spec/tree_traversal_bfs_spec.rb new file mode 100644 index 00000000..c1691ee3 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/spec/tree_traversal_bfs_spec.rb @@ -0,0 +1,40 @@ +require "./tree_traversal_bfs" + +RSpec.describe "level_order_traversal" do + it "can handle an empty tree" do + expect(level_order_traversal(nil)).to eq([]) + end + + it "can handle a tree that has only a root node" do + expect(level_order_traversal(Node.new(5))).to eq([5]) + end + + it "can handle a tree with several nodes" do + expect(level_order_traversal(Node.new(1, Node.new(2), Node.new(3)))).to eq([1, 2, 3]) + expect(level_order_traversal(Node.new(10, Node.new(20, Node.new(9), Node.new(22)), Node.new(30)))).to eq([10, 20, 30, 9, 22]) + expect(level_order_traversal(Node.new(10, + Node.new(9, + Node.new(8, + Node.new(7, + Node.new(32)), + Node.new(6, nil, + Node.new(33))), + Node.new(12, + Node.new(11), + Node.new(40))), + Node.new(11, + Node.new(20, + Node.new(4), + Node.new(90)), + Node.new(30, + Node.new(9), + Node.new(89, nil, + Node.new(90, nil, + Node.new(34)))))))).to eq([10, 9, 11, 8, 12, 20, 30, 7, 6, 11, 40, 4, 90, 9, 89, 32, 33, 90, 34]) + end + + it "can handle a one-sided tree" do + expect(level_order_traversal(Node.new(10, nil, Node.new(11, nil, Node.new(12, nil, Node.new(12)))))).to eq([10, 11, 12, 12]) + expect(level_order_traversal(Node.new(1, Node.new(2, Node.new(3, Node.new(4)))))).to eq([1, 2, 3, 4]) + end +end \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/tree_traversal_bfs.rb b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/tree_traversal_bfs.rb new file mode 100644 index 00000000..614c0f0e --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/ruby/tree_traversal_bfs.rb @@ -0,0 +1,36 @@ +class Node + attr_accessor :value, :left, :right + + def initialize(value, left = nil, right = nil) + @value = value + @left = left + @right = right + end +end + +def level_order_traversal(root) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + root = Node.new(1, Node.new(2), Node.new(3)); + + puts "Expecting: [[1], [2, 3]]" + print level_order_traversal(root) + + puts + puts + + root = Node.new(10, Node.new(20, Node.new(9), Node.new(22)), Node.new(30)) + + puts "Expecting: [[10], [20, 30], [9, 22]]" + print level_order_traversal(root) + + puts + puts + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/solutions/tree_traversal_bfs.js b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/solutions/tree_traversal_bfs.js new file mode 100644 index 00000000..b47ed64d --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/solutions/tree_traversal_bfs.js @@ -0,0 +1,99 @@ +class Node { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } +} + +function levelOrderTraversal(root) { + let queue = root !== null ? [root] : []; + let result = []; + + while (queue.length) { + const node = queue.shift(); + + result.push(node); + + if (node.left) { + queue.push(node.left); + } + + if (node.right) { + queue.push(node.right); + } + } + + return result.map(node => node.value); +} + +if (require.main === module) { + let root = new Node(1, new Node(2), new Node(3)); + // add your own tests in here + console.log("Expecting: [1, 2, 3]"); + console.log(levelOrderTraversal(root)); + + console.log(""); + + root = new Node(10, new Node(20, new Node(9), new Node(22)), new Node(30)); + + console.log("Expecting: [10, 20, 30, 9, 22]"); + console.log(levelOrderTraversal(root)); + + console.log(""); + + root = null; + + console.log("Expecting: []"); + console.log(levelOrderTraversal(root)); + + console.log(""); + + root = new Node(10); + + console.log("Expecting: [10]"); + console.log(levelOrderTraversal(root)); + + console.log(""); + + root = new Node(10, new Node(9, new Node(8, new Node(7, new Node(32)), new Node(6, null, new Node(33))), new Node(12, new Node(11), new Node(40))), new Node(11, new Node(20, new Node(4), new Node(90)), new Node(30, new Node(9), new Node(89, null, new Node(90, null, new Node(34)))))); + + console.log("Expecting: [10, 9, 11, 8, 12, 20, 30, 7, 6, 11, 40, 4, 90, 9, 89, 32, 33, 90, 34]"); + console.log(levelOrderTraversal(root)); + + console.log(""); + + root = new Node(10, null, new Node(11, null, new Node(12, null, new Node(12)))); + + console.log("Expecting: [10, 11, 12, 12]"); + console.log(levelOrderTraversal(root)); +} + +module.exports = { + Node, + levelOrderTraversal +}; + +// Please add your pseudocode to this file +/****************************************************************************************** */ +// if root is nil: return empty array +// else: initialize a queue with the root +// initialize an empty array to store result +// +// loop until the queue is empty: +// store first node in queue in variable node +// add node to result array +// add node's left and right nodes to queue if not falsy, in that order +// +// return result +/****************************************************************************************** */ + +// And a written explanation of your solution +/****************************************************************************************** */ +// We can solve this problem by using a queue. Each time we process a node, we puts its left +// and right nodes into the queue for future processing. So starting at the root, we put it +// in the queue. We iterate over the queue: the node is added to a result array. If the node +// being processed has a left and/or right node, those are pushed onto the queue. We continue +// until the queue is empty. To return the values associated with the nodes in the result, we +// use map to return an array containing the values in the correct order. +/****************************************************************************************** */ diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/solutions/tree_traversal_bfs.rb b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/solutions/tree_traversal_bfs.rb new file mode 100644 index 00000000..7f193682 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/solutions/tree_traversal_bfs.rb @@ -0,0 +1,99 @@ +class Node + attr_accessor :value, :left, :right + + def initialize(value, left = nil, right = nil) + @value = value + @left = left + @right = right + end +end + +def level_order_traversal(root) + queue = root.nil? ? [] : [root] + result = [] + + while queue.length > 0 + node = queue.shift + + result << node + queue << node.left unless node.left.nil? + queue << node.right unless node.right.nil? + end + + result.map { |node| node.value } +end + + +if __FILE__ == $PROGRAM_NAME + root = Node.new(1, Node.new(2), Node.new(3)); + + puts "Expecting: [1, 2, 3]" + print level_order_traversal(root) + + puts + puts + + root = Node.new(10, Node.new(20, Node.new(9), Node.new(22)), Node.new(30)) + + puts "Expecting: [10, 20, 30, 9, 22]" + print level_order_traversal(root) + + # Don't forget to add your own! + puts + puts + + root = nil + + puts "Expecting: []" + print level_order_traversal(root) + + puts + puts + + root = Node.new(10) + + puts "Expecting: [10]" + print level_order_traversal(root) + + puts + puts + + root = Node.new(10, Node.new(9, Node.new(8, Node.new(7, Node.new(32)), Node.new(6, nil, Node.new(33))), Node.new(12, Node.new(11), Node.new(40))), Node.new(11, Node.new(20, Node.new(4), Node.new(90)), Node.new(30, Node.new(9), Node.new(89, nil, Node.new(90, nil, Node.new(34)))))) + + puts "Expecting: [10, 9, 11, 8, 12, 20, 30, 7, 6, 11, 40, 4, 90, 9, 89, 32, 33, 90, 34]" + print level_order_traversal(root) + + puts + puts + + root = Node.new(10, nil, Node.new(11, nil, Node.new(12, nil, Node.new(12)))) + + puts "Expecting: [10, 11, 12, 12]" + print level_order_traversal(root) + + puts +end + +# Please add your pseudocode to this file +############################################################################################## +# if root is nil: return empty array +# else: initialize a queue with the root +# initialize an empty array to store result +# +# loop until the queue is empty: +# store first node in queue in variable node +# add node to result array +# add node's left and right nodes to queue if not falsy, in that order +# +# return result +############################################################################################## + +# And a written explanation of your solution +############################################################################################## +# We can solve this problem by using a queue. Each time we process a node, we puts its left +# and right nodes into the queue for future processing. So starting at the root, we put it +# in the queue. We iterate over the queue: the node is added to a result array. If the node +# being processed has a left and/or right node, those are pushed onto the queue. We continue +# until the queue is empty. To return the values associated with the nodes in the result, we +# use map to return an array containing the values in the correct order. +############################################################################################## \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/trees.png b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/trees.png new file mode 100644 index 00000000..3bf1fe45 Binary files /dev/null and b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/trees.png differ diff --git a/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/unordered_tree.png b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/unordered_tree.png new file mode 100644 index 00000000..1c44303e Binary files /dev/null and b/12-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first/unordered_tree.png differ diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/.gitignore b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/README.md b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/README.md new file mode 100644 index 00000000..b5c979eb --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/README.md @@ -0,0 +1,71 @@ +# Days 3 to 4: In-Order Tree Traversal + +![Binary search trees](./trees.png) + +In-order tree traversal requires an algorithm that visits the left subtree first followed by the root, and then the right subtree. When using in-order traversal with a binary search tree (BST), the value of each node will be output in order from least to greatest. For the left tree above the output would be: 3, 5, 6, 10, 12, 16. For the tree on the right: 5, 30, 60. This algorithm can be programmed recursively or iteratively. + +## Implement In-Order Traversal + +For this exercise, your function will be called with the root node of a BST. Your algorithm should return an array containing the nodes' values in sorted order. Note that your algorithm should not be calling sort! It should traverse the tree nodes in order and add each value to the array that will be returned as it travels through the tree. + +What is the time complexity of your solution? + +_Hint: It might help to add a default parameter if solving this recursively._ + +``` + 2 + / \ + -10 20 + +in_order_traversal(root) +=> [-10, 2, 20] +``` + +``` + 10 + / \ + 0 20 + \ \ + 5 30 + +in_order_traversal(root) +=> [0, 5, 10, 20, 30] +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/javascript/package.json b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/javascript/package.json new file mode 100644 index 00000000..dfabab9b --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "inorder_traversal", + "version": "1.0.0", + "description": "in order traversal", + "main": "tree_traversal_inorder.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/javascript/tests/tree_traversal_inorder.test.js b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/javascript/tests/tree_traversal_inorder.test.js new file mode 100644 index 00000000..18905c42 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/javascript/tests/tree_traversal_inorder.test.js @@ -0,0 +1,33 @@ +const { Node, treeTraversalInorder } = require("../tree_traversal_inorder"); + +describe("treeTraversalInorder", () => { + test("can handle an empty tree", () => { + expect(treeTraversalInorder(null)).toEqual([]); + }); + + test("can handle a tree with a single root node", () => { + expect(treeTraversalInorder(new Node(5))).toEqual([5]); + }); + + test("can handle small balanced trees", () => { + const root = new Node(2, new Node(-10), new Node(20)); + const rootTwo = new Node(10, new Node(0, null, new Node(5)), new Node(20, null, new Node(30))); + + expect(treeTraversalInorder(root)).toEqual([-10, 2, 20]); + expect(treeTraversalInorder(rootTwo)).toEqual([0, 5, 10, 20, 30]); + }); + + test("can handle unbalanced trees", () => { + const root = new Node(0, null, new Node(1, null, new Node(2, null, new Node(3, null, new Node(4))))); + const rootTwo = new Node(10, new Node(9, new Node(8, new Node(7, new Node(6, new Node(5)))))); + + expect(treeTraversalInorder(root)).toEqual([0, 1, 2, 3, 4]); + expect(treeTraversalInorder(rootTwo)).toEqual([5, 6, 7, 8, 9, 10]); + }); + + test("can handle a larger tree", () => { + const root = new Node(30, new Node(10, null, new Node(20, null, new Node(25, new Node(24)))), new Node(50, new Node(40, new Node(39)), new Node(55, new Node(54), new Node(56, null, new Node(60))))); + + expect(treeTraversalInorder(root)).toEqual([10, 20, 24, 25, 30, 39, 40, 50, 54, 55, 56, 60]); + }); +}); diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/javascript/tree_traversal_inorder.js b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/javascript/tree_traversal_inorder.js new file mode 100644 index 00000000..0ac4c12a --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/javascript/tree_traversal_inorder.js @@ -0,0 +1,32 @@ +class Node { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } +} + +function treeTraversalInorder(root) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + let root = new Node(2, new Node(-10), new Node(20)); + console.log("Expecting: [-10, 2, 20]"); + console.log(treeTraversalInorder(root)); + + console.log(""); + + root = new Node(10, new Node(0, null, new Node(5)), new Node(20, null, new Node(30))); + console.log("Expecting: [0, 5, 10, 20, 30] "); + console.log(treeTraversalInorder(root)); +} + +module.exports = { + Node, + treeTraversalInorder +}; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/.rspec b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/Gemfile b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/spec/spec_helper.rb b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/spec/tree_traversal_inorder_spec.rb b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/spec/tree_traversal_inorder_spec.rb new file mode 100644 index 00000000..8ce92551 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/spec/tree_traversal_inorder_spec.rb @@ -0,0 +1,33 @@ +require "./tree_traversal_inorder" + +RSpec.describe "tree_traversal_inorder" do + it "can handle an empty tree" do + expect(tree_traversal_inorder(nil)).to eq([]) + end + + it "can handle a tree with a single root node" do + expect(tree_traversal_inorder(Node.new(5))).to eq([5]) + end + + it "can handle small balanced trees" do + root = Node.new(2, Node.new(-10), Node.new(20)) + root_two = Node.new(10, Node.new(0, nil, Node.new(5)), Node.new(20, nil, Node.new(30))) + + expect(tree_traversal_inorder(root)).to eq([-10, 2, 20]) + expect(tree_traversal_inorder(root_two)).to eq([0, 5, 10, 20, 30]) + end + + it "can handle unbalanced trees" do + root = Node.new(0, nil, Node.new(1, nil, Node.new(2, nil, Node.new(3, nil, Node.new(4))))) + root_two = Node.new(10, Node.new(9, Node.new(8, Node.new(7, Node.new(6, Node.new(5)))))) + + expect(tree_traversal_inorder(root)).to eq([0, 1, 2, 3, 4]) + expect(tree_traversal_inorder(root_two)).to eq([5, 6, 7, 8, 9, 10]) + end + + it "can handle a larger tree" do + root = Node.new(30, Node.new(10, nil, Node.new(20, nil, Node.new(25, Node.new(24)))), Node.new(50, Node.new(40, Node.new(39)), Node.new(55, Node.new(54), Node.new(56, nil, Node.new(60))))) + + expect(tree_traversal_inorder(root)).to eq([10, 20, 24, 25, 30, 39, 40, 50, 54, 55, 56, 60]) + end +end \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/tree_traversal_inorder.rb b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/tree_traversal_inorder.rb new file mode 100644 index 00000000..b595696e --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/ruby/tree_traversal_inorder.rb @@ -0,0 +1,32 @@ +class Node + attr_accessor :value, :left, :right + + def initialize(value, left = nil, right = nil) + @value = value + @left = left + @right = right + end +end + +def tree_traversal_inorder(root) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + root = Node.new(2, Node.new(-10), Node.new(20)) + puts "Expecting: [-10, 2, 20]" + print tree_traversal_inorder(root) + puts + + puts + + root = Node.new(10, Node.new(0, nil, Node.new(5)), Node.new(20, nil, Node.new(30))) + puts "Expecting: [0, 5, 10, 20, 30] " + print tree_traversal_inorder(root) + puts + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/solutions/tree_traversal_inorder.js b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/solutions/tree_traversal_inorder.js new file mode 100644 index 00000000..fd617374 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/solutions/tree_traversal_inorder.js @@ -0,0 +1,138 @@ +class Node { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } +} + +// Recursive solution +function treeTraversalInorder(root, result = []) { + if (root === null) { + return []; + } + + treeTraversalInorder(root.left, result); + result.push(root.value); + treeTraversalInorder(root.right, result); + + return result; +} + +// Iterative solution +// function treeTraversalInorder(root) { +// const queue = root ? [root] : []; +// const result = []; + +// while (queue.length > 0) { +// const node = queue[0]; + +// if (node.left && !node.done) { +// queue.unshift(node.left); +// node.done = true; +// continue; +// } + +// delete node.done; +// result.push(node.value); +// queue.shift(); + +// if (node.right) { +// queue.unshift(node.right); +// } +// } + +// return result; +// } + +if (require.main === module) { + // add your own tests in here + let root = new Node(2, new Node(-10), new Node(20)); + console.log("Expecting: [-10, 2, 20]"); + console.log(treeTraversalInorder(root)); + + console.log(""); + + root = new Node(10, new Node(0, null, new Node(5)), new Node(20, null, new Node(30))); + console.log("Expecting: [0, 5, 10, 20, 30] "); + console.log(treeTraversalInorder(root)); + + console.log(""); + + root = new Node(1, null, new Node(3, new Node(2))); + console.log("Expecting: [1, 2, 3]"); + console.log(treeTraversalInorder(root)); + + console.log(""); + + console.log("Expecting: []"); + console.log(treeTraversalInorder(null)); + + console.log(""); + + root = new Node(30, new Node(10, null, new Node(20)), new Node(50, new Node(40))); + console.log("Expecting: [10, 20, 30, 40, 50]"); + console.log(treeTraversalInorder(root)); + + console.log(""); +} + +module.exports = { + Node, + treeTraversalInorder +}; + +// Please add your pseudocode to this file (recursive) +/*************************************************************************** + * initialize result to empty array + * + * function in_order(root): + * return empty array if root is falsy + * + * in_order(left side) + * push root value onto result + * in_order(right side) + * + * return result + * *************************************************************************/ + +// Please add your pseudocode to this file (iterative) +/*************************************************************************** + * return empty array if root is falsy + * initialize queue with array containing root + * initialize result to empty array + * + * while queue contains nodes: + * node = first node in queue + * + * if node has left node and not yet seen / done: + * add left node to beginning of queue + * add attribute done and set to true (to mark as visited) + * continue with loop + * + * remove done attribute from node + * add node value to end of result + * remove node from start of queue + * + * if node has right node: + * add right node to beginning of queue + * + * return result + * *************************************************************************/ + +// And a written explanation of your solution (recursive + iterative) +/*************************************************************************** + * If we continuously go left from the root until there is no left node left + * to visit, we will have traveled to the lowest possible value that branches + * off of that node. Once we've done that we can push that value onto an array + * and we can then start looking at the right nodes. We repeat this process of + * always going as far to the left as possible before going right until there + * are no nodes left to visit. For the iterative version, we added an attribute + * to mark that we had already visited a node's left node to avoid an infinite + * loop. Otherwise, if we visited a node's left node, then processed that same + * node again, we'd go left all over again in an endless loop. I'm sure there are + * other ways to solve this iteratively, such as putting the node in a set, which + * I did in the Ruby version, but this is how I did it to shake things up a bit. + * *************************************************************************/ + + // Big O for both solutions is O(n). Each node will be visited at least once. \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/solutions/tree_traversal_inorder.rb b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/solutions/tree_traversal_inorder.rb new file mode 100644 index 00000000..1e4d34d6 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/solutions/tree_traversal_inorder.rb @@ -0,0 +1,140 @@ +class Node + attr_accessor :value, :left, :right + + def initialize(value, left = nil, right = nil) + @value = value + @left = left + @right = right + end +end + +# Recursive solution +def tree_traversal_inorder(root, result = []) + return [] if root.nil? + + tree_traversal_inorder(root.left, result) + result << root.value + tree_traversal_inorder(root.right, result) + + result +end + +# Iterative solution +# require 'set' + +# def tree_traversal_inorder(root) +# return [] if root.nil? +# queue = [root] +# result = [] +# visited = Set.new + +# until queue.empty? +# node = queue.first + +# if !node.left.nil? && !visited.include?(node) +# queue.unshift(node.left) +# visited.add(node) +# next +# end + +# result << node.value +# queue.shift + +# unless node.right.nil? +# queue.unshift(node.right) +# end +# end + +# result +# end + +if __FILE__ == $PROGRAM_NAME + root = Node.new(2, Node.new(-10), Node.new(20)) + puts "Expecting: [-10, 2, 20]" + print tree_traversal_inorder(root) + puts + + puts + + root = Node.new(10, Node.new(0, nil, Node.new(5)), Node.new(20, nil, Node.new(30))) + puts "Expecting: [0, 5, 10, 20, 30]" + print tree_traversal_inorder(root) + puts + + # Don't forget to add your own! + puts + + root = Node.new(1, nil, Node.new(3, Node.new(2))) + puts "Expecting: [1, 2, 3]" + print tree_traversal_inorder(root) + puts + + puts + + root = nil + puts "Expecting: []" + print tree_traversal_inorder(root) + puts + + puts + + root = Node.new(30, Node.new(10, nil, Node.new(20)), Node.new(50, Node.new(40))) + puts "Expecting: [10, 20, 30, 40, 50]" + print tree_traversal_inorder(root) + puts +end + +## Please add your pseudocode to this file (recursive) +############################################################################ + # initialize result to empty array + # + # function in_order(root): + # return empty array if root is falsy + # + # in_order(left side) + # push root value onto result + # in_order(right side) + # + # return result + # ########################################################################## + +## Please add your pseudocode to this file (iterative) +############################################################################ + # return empty array if root is falsy + # initialize queue with array containing root + # initialize result to empty array + # initialize visited to new set + # + # while queue contains nodes: + # node = first node in queue + # + # if node has left node and set does not contain node: + # add left node to beginning of queue + # add node to set + # continue with loop + # + # add node value to end of result + # remove node from start of queue + # + # if node has right node: + # add right node to beginning of queue + # + # return result + # ########################################################################## + +## And a written explanation of your solution (recursive + iterative) +############################################################################ + # If we continuously go left from the root until there is no left node left + # to visit, we will have traveled to the lowest possible value that branches + # off of that node. Once we've done that we can push that value onto an array + # and we can then start looking at the right nodes. We repeat this process of + # always going as far to the left as possible before going right until there + # are no nodes left to visit. For the iterative version, we add the node to a + # set to mark that we had already visited a node's left node to avoid an infinite + # loop. Otherwise, if we visited a node's left node, then processed that same + # node again, we'd go left all over again in an endless loop. I'm sure there are + # other ways to solve this iteratively, but this is how I did it. + # ########################################################################## + + + # Big O for both solutions is O(n). Each node will be visited at least once. \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/trees.png b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/trees.png new file mode 100644 index 00000000..3bf1fe45 Binary files /dev/null and b/12-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order/trees.png differ diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/.gitignore b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/README.md b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/README.md new file mode 100644 index 00000000..61162a94 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/README.md @@ -0,0 +1,55 @@ +# Day 5: Find a Target Value in a Binary Search Tree + +Given a binary search tree (BST), find the node with the target value and return the node. If the node does not exist in the tree return a falsy value, such as `null` or `nil`. + +``` + 1 + / \ +-1 2 + +Input: root node, target = 2 +Output: Node with value 2 + +Input: root node, target = 5 +Output: null or nil +``` + +What is the time complexity of your solution? How does the complexity differ for a balanced tree versus an unbalanced tree? + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/javascript/find_target.js b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/javascript/find_target.js new file mode 100644 index 00000000..98597f62 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/javascript/find_target.js @@ -0,0 +1,28 @@ +class Node { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } +} + +function findTarget(root, target) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + const root = new Node(1, new Node(-1), new Node(2)); + console.log("Expecting: Node with value 2"); + console.log(findTarget(root, 2)); + + console.log(""); + + console.log("Expecting: null"); + console.log(findTarget(root, 5)); +} + +module.exports = { findTarget, Node }; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/javascript/package.json b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/javascript/package.json new file mode 100644 index 00000000..3e4a311b --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "find_target", + "version": "1.0.0", + "description": "find target node in BST", + "main": "find_target.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/javascript/tests/find_target.test.js b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/javascript/tests/find_target.test.js new file mode 100644 index 00000000..ae40a688 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/javascript/tests/find_target.test.js @@ -0,0 +1,35 @@ +const { Node, findTarget } = require("../find_target"); + +describe("findTarget", () => { + test("can handle an empty tree", () => { + expect(findTarget(null, 5)).toBe(null); + }); + + test("can handle a tree with only a root node", () => { + const root = new Node(5); + + expect(findTarget(root, 5)).toBe(root); + expect(findTarget(root, 7)).toBe(null); + }); + + test("can find the target node in a small balanced tree", () => { + const root = new Node(1, new Node(-1), new Node(2)); + + expect(findTarget(root, 2)).toBe(root.right); + }); + + test("returns the correct result for unbalanced trees", () => { + const root = new Node(10, new Node(9, new Node(8, new Node(7)))); + const root_two = new Node(1,null, new Node(2,null, new Node(3,null, new Node(4,null, new Node(5))))); + + expect(findTarget(root, 8)).toBe(root.left.left); + expect(findTarget(root_two, 5)).toBe(root_two.right.right.right.right); + }); + + test("returns the correct result for a larger tree", () => { + const root = new Node(10, new Node(7, new Node(6, new Node(4)), new Node(8)), new Node(14, new Node(11), new Node(18,null, new Node(20)))) + + expect(findTarget(root, 20)).toBe(root.right.right.right); + expect(findTarget(root, 5)).toBe(null); + }); +}); diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/.rspec b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/Gemfile b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/find_target.rb b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/find_target.rb new file mode 100644 index 00000000..d5d579f5 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/find_target.rb @@ -0,0 +1,29 @@ +class Node + attr_accessor :value, :left, :right + + def initialize(value, left = nil, right = nil) + @value = value + @left = left + @right = right + end +end + +def find_target(root, target) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + root = Node.new(1, Node.new(-1), Node.new(2)) + puts "Expecting: Node with value 2" + puts find_target(root, 2).inspect + + puts + + puts "Expecting: nil" + puts find_target(root, 5).inspect + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/spec/find_target_spec.rb b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/spec/find_target_spec.rb new file mode 100644 index 00000000..c5c9b061 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/spec/find_target_spec.rb @@ -0,0 +1,35 @@ +require "./find_target" + +RSpec.describe "find_target" do + it "can handle an empty tree" do + expect(find_target(nil, 5)).to be nil + end + + it "can handle a tree with only a root node" do + root = Node.new(5) + + expect(find_target(root, 5)).to be(root) + expect(find_target(root, 7)).to be nil + end + + it "can find the target node in a small balanced tree" do + root = Node.new(1, Node.new(-1), Node.new(2)) + + expect(find_target(root, 2)).to be root.right + end + + it "returns the correct result for unbalanced trees" do + root = Node.new(10, Node.new(9, Node.new(8, Node.new(7)))) + root_two = Node.new(1, nil, Node.new(2, nil, Node.new(3, nil, Node.new(4, nil, Node.new(5))))) + + expect(find_target(root, 8)).to be root.left.left + expect(find_target(root_two, 5)).to be root_two.right.right.right.right + end + + it "returns the correct result for a larger tree" do + root = Node.new(10, Node.new(7, Node.new(6, Node.new(4)), Node.new(8)), Node.new(14, Node.new(11), Node.new(18, nil, Node.new(20)))) + + expect(find_target(root, 20)).to be root.right.right.right + expect(find_target(root, 5)).to be nil + end +end \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/spec/spec_helper.rb b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/solutions/find_target.js b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/solutions/find_target.js new file mode 100644 index 00000000..2e6d263f --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/solutions/find_target.js @@ -0,0 +1,91 @@ +class Node { + constructor(value, left = null, right = null) { + this.value = value; + this.left = left; + this.right = right; + } +} + +function findTarget(root, target) { + if (!root) { + return null; + } + + if (root.value === target) { + return root; + } + + if (root.value > target) { + return findTarget(root.left, target); + } else { + return findTarget(root.right, target); + } +} + +if (require.main === module) { + // add your own tests in here + let root = new Node(1, new Node(-1), new Node(2)); + console.log("Expecting: Node with value 2"); + console.log(findTarget(root, 2)); + + console.log(""); + + console.log("Expecting: null"); + console.log(findTarget(root, 5)); + + console.log(""); + + root = null; + console.log("Expecting: null"); + console.log(findTarget(root, 2)); + + console.log(""); + + root = new Node(10, new Node(9, new Node(8, new Node(7)))); + console.log("Expecting: Node with value 8"); + console.log(findTarget(root, 8)); + + console.log(""); + + root = new Node(1, null, new Node(2, null, new Node(3, null, new Node(4, null, new Node(5))))); + console.log("Expecting: Node with value 5"); + console.log(findTarget(root, 5)); + + console.log(""); + + root = new Node(10, new Node(7, new Node(6, new Node(4)), new Node(8)), new Node(14, new Node(11), new Node(18, null, new Node(20)))); + console.log("Expecting: Node with value 20"); + console.log(findTarget(root, 20)); + + console.log(""); + + console.log("Expecting: null"); + console.log(findTarget(root, 5)); +} + +module.exports = { findTarget, Node }; + +// Please add your pseudocode to this file +/**************************************************************************** + * return null or nil if root is falsy + * + * return node if node value == target value + * + * if node value > target: + * return result of recursing with node.left + * else: + * return result of recursing with node.right + * *************************************************************************/ + +// And a written explanation of your solution +/**************************************************************************** + * This is very similar to using a binary search on a sorted array. First we + * check if the root is a node. If it isn't we just return null or nil. If it + * is a node and its value is the same as the target, we return the node. If + * the value is higher than target, we recursively traverse the left side of + * the tree. Otherwise we traverse the right side if the value is less than + * the target. + * **************************************************************************/ + + // Time complexity for a balanced tree is O (log n), since we are dividing the input on each recursion + // For an unbalanced tree, it's O(n), since we must visit all nodes in the worst case \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/solutions/find_target.rb b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/solutions/find_target.rb new file mode 100644 index 00000000..af22807a --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree/solutions/find_target.rb @@ -0,0 +1,80 @@ +class Node + attr_accessor :value, :left, :right + + def initialize(value, left = nil, right = nil) + @value = value + @left = left + @right = right + end +end + +def find_target(root, target) + return nil if root.nil? + return root if root.value == target + + if root.value > target + return find_target(root.left, target) + else + return find_target(root.right, target) + end +end + +if __FILE__ == $PROGRAM_NAME + root = Node.new(1, Node.new(-1), Node.new(2)) + puts "Expecting: Node with value 2" + puts find_target(root, 2).inspect + + puts + + puts "Expecting: nil" + puts find_target(root, 5).inspect + + puts + + # Don't forget to add your own! + root = Node.new(10, Node.new(9, Node.new(8, Node.new(7)))) + puts "Expecting: Node with value 8" + puts find_target(root, 8).inspect + + puts + + root = Node.new(1, nil, Node.new(2, nil, Node.new(3, nil, Node.new(4, nil, Node.new(5))))) + puts "Expecting: Node with value 5" + puts find_target(root, 5).inspect + + puts + + root = Node.new(10, Node.new(7, Node.new(6, Node.new(4)), Node.new(8)), Node.new(14, Node.new(11), Node.new(18, nil, Node.new(20)))) + puts "Expecting: Node with value 20" + puts find_target(root, 20).inspect + + puts + + puts "Expecting: nil" + puts find_target(root, 5).inspect +end + +# Please add your pseudocode to this file +############################################################################################### + # return null or nil if root is falsy + # + # return node if node value == target value + # + # if node value > target: + # return result of recursing with node.left + # else: + # return result of recursing with node.right +############################################################################################### + +# And a written explanation of your solution +############################################################################################### + # This is very similar to using a binary search on a sorted array. First we + # check if the root is a node. If it isn't we just return null or nil. If it + # is a node and its value is the same as the target, we return the node. If + # the value is higher than target, we recursively traverse the left side of + # the tree. Otherwise we traverse the right side if the value is less than + # the target. +############################################################################################### + + # Time complexity for a balanced tree is O (log n), since we are dividing the input on each recursion + # For an unbalanced tree, it's O(n), since we must visit all nodes in the worst case diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/.gitignore b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/README.md b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/README.md new file mode 100644 index 00000000..b368f684 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/README.md @@ -0,0 +1,130 @@ +# Bonus: Quicksort + +![quicksort animation](./quick_sort_partition_animation.gif) + +Quicksort is an efficient divide and conquer algorithm for sorting data. It can sort in place, i.e. it does not require us to push data to a new data structure, and it's typically implemented as a recursive algorithm. Note that there are a number of ways to implement a quicksort. + +## How Does Quicksort Work? + +The quicksort algorithm requires us to choose a pivot element in the array. The pivot can be at any point, such as the start, middle, or end. Next, we sort all of the elements that are less than the pivot to the left of the pivot, and all of the elements that are greater than the pivot to the right. Once this is done, the pivot is at the correct location. We then repeat this process on the left and right sides until the whole list is sorted. + +In the diagram below, notice how the number 4, which is the pivot, is sorted to the correct location before the recursive call is made (branching denotes the recursive calls). First, we check if 1 is more than 4. It isn't so we move to the next value: 7. 7 is more than 4, so it is swapped with the element before 4 (which is 5), and 4 is then swapped with 7. This continues until every element before the pivot has been evaulated. + +![diagram of quicksort in action](./quicksort_diagram.png) + +## Performance + +In the worst case, quicksort runs in quadratic time: O(n^2). This will happen when we choose the rightmost element as the pivot for an already sorted list. The average run time, however, is logarithmic: O(n log n). + +We can improve the performance of our quicksort algorithm by choosing the middle element as the pivot instead. We can potentially gain further improvements by sampling three or more elements and choosing the median as the pivot. However, choosing too many elements for sampling will likely degrade performance, so it really is a balancing act. + +## Implement Quicksort + +We'll implement the quicksort algorithm in two parts. First we'll make the helper function that sorts elements to the right or left of the pivot. Once that's working, we'll work on the main function that calls the helper function. Our tests will test the main function. It'll be up to you to ensure the helper function works. + +### 1. `partition(array, low, high)` + +The helper method `partition` takes three arguments: the `array` to operate upon, and a `low` and a `high`, which are integers denoting which portion of the array requires sorting. It returns the final index of the pivot element and performs the following operations: + +1. Choose the rightmost element as the pivot +2. Declare a variable called `pivotIndex` or `pivot_index` and set it equal to the value of `high` +3. Iterate over the array starting at the `low` index and ending at the `pivotIndex` + - If an element to the left of the pivot is less than or equal to the pivot, continue + - If an element to the left of the pivot is greater than the pivot: + - Swap it with the element just before the pivot, and then swap the pivot with that element (i.e. the pivot will now be at its original index - 1) + - Decrement the `pivotIndex`, since the pivot has been moved to the left by one place + - If an element is equal to the pivot, leave it in place +4. Return the `pivotIndex` + +Your function should sort the array in place. This means you should not be creating a new array at any point. You'll also need to decide how to best iterate over the array. Think about which element needs to be compared to the pivot at any given time. + +``` +array = [3, 2, 1, 4] +partition(array, 0, 3) +=> 3 +// array = [3, 2, 1, 4] + +array = [3, 2, 1, 2] +partition(array, 0, 3) +=> 2 +// array = [1, 2, 2, 3] + +array = [2, -10, 7, 0, 1, 3] +partition(array, 0, 5) +=> 4 +// array = [2, -10, 1, 0, 3, 7] + +array = [2, -10, 7, 0, 1, 3] +partition(array, 1, 3) +=> 2 +// array = [2, -10, 0, 7, 1, 3] +``` + +To determine whether your function is working, check if all elements lower than the pivot are to the pivot's left, while all elements greater than the pivot are to its right. Be sure to look at the correct subset of the array when using values other than 0 and the array's length - 1 as the `low` and `high`. Finally, the method will return the final index of the pivot element. + +### 2. `quicksort(array, low, high)` + +This is the main method that returns a sorted array. Once again, it sorts the array in place - it does not create any new arrays. It achieves this by partitioning the array using the helper method from earlier, and then recursively processing all elements to the left of the pivot and all elements to the right of the pivot. The steps are: + +1. If `low` is less than `high` + - Partition the array using the `low` and `high` values + - Store the result of calling `partition` in a variable (recall that the result is the final index of the pivot) + - Recurse with the left side of the array (use `low` and `high` to specify the starting and stopping points) + - Recurse with the right side of the array (use `low` and `high` to specify the starting and stopping points) +2. Else return the array + +``` +arr = [3, 2, 1, 4] +quicksort(arr, 0, 3) +=> [1, 2, 3, 4] + +arr = [1, 2, 2, 3, 4] +quicksort(arr, 0, 4) +=> [1, 2, 2, 3, 4] +``` + +### Bonus: Reduce the Number of Swaps + +Our algorithm for `partition` always performs two swaps when a number higher than the pivot is encountered. Can you modify the algorithm so that it performs only one swap in this case? It should then perform one final swap to put the pivot in the correct location before returning the pivot index. Take a look at the gif at the top of this README if you need to see it in action. + +### Super Bonus: Choose the Middle Element as the Pivot + +Can you modify the algorithm to use the middle element as the pivot? There's more than one way to achieve this functionality! What about using the median of several values? Or a random element? + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/javascript/package.json b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/javascript/package.json new file mode 100644 index 00000000..0530cdaa --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "quicksort", + "version": "1.0.0", + "description": "quicksort", + "main": "quicksort.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/javascript/quicksort.js b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/javascript/quicksort.js new file mode 100644 index 00000000..ef227edf --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/javascript/quicksort.js @@ -0,0 +1,23 @@ +function partition(array, low, high) { + // type your code in here +} + +function quicksort(array, low = 0, high = array.length - 1) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [1, 2, 3, 4]"); + console.log(quicksort([3, 2, 1, 4])); + + console.log(""); + + console.log("Expecting: [1, 2, 2, 3, 4]"); + console.log(quicksort([1, 2, 2, 3, 4])); +} + +module.exports = quicksort; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/javascript/tests/quicksort.test.js b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/javascript/tests/quicksort.test.js new file mode 100644 index 00000000..f2100374 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/javascript/tests/quicksort.test.js @@ -0,0 +1,49 @@ +const quicksort = require("../quicksort"); + +describe("quicksort", () => { + it("returns the same array that was passed in", () => { + const arrOne = []; + const arrTwo = [1, 2, 3, 4]; + + expect(quicksort(arrOne)).toBe(arrOne); + expect(quicksort(arrTwo)).toBe(arrTwo); + }); + + it("returns an empty array when passed an empty array", () => { + expect(quicksort([])).toEqual([]); + }); + + it("can sort an array with a single element", () => { + expect(quicksort([2])).toEqual([2]); + }); + + it("can sort an array with two elements", () => { + expect(quicksort([2, 1])).toEqual([1, 2]); + }); + + it("can sort an array with three elements", () => { + expect(quicksort([2, 1, 3])).toEqual([1, 2, 3]); + }); + + it("can sort an array with more than three elements", () => { + expect(quicksort([4, 2, 1, 3])).toEqual([1, 2, 3, 4]); + expect(quicksort([4, 2, 6, 5, 1, 3])).toEqual([1, 2, 3, 4, 5, 6]); + }); + + it("can sort a sorted array", () => { + expect(quicksort([1, 2, 3, 4, 5, 6])).toEqual([1, 2, 3, 4, 5, 6]); + }); + + it("can sort a reverse sorted array", () => { + expect(quicksort([6, 5, 4, 3, 2, 1])).toEqual([1, 2, 3, 4, 5, 6]); + }); + + it("can sort an array with many elements", () => { + const arr = []; + for (let i = 0; i < 1000; ++i) { + arr.push(Math.random() * 1000); + } + + expect(quicksort(arr)).toEqual(arr.sort((a, b) => a - b)); + }); +}); diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/quick_sort_partition_animation.gif b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/quick_sort_partition_animation.gif new file mode 100644 index 00000000..14b37f0e Binary files /dev/null and b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/quick_sort_partition_animation.gif differ diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/quicksort_diagram.png b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/quicksort_diagram.png new file mode 100644 index 00000000..9f6badea Binary files /dev/null and b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/quicksort_diagram.png differ diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/.rspec b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/Gemfile b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/quicksort.rb b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/quicksort.rb new file mode 100644 index 00000000..3e353dc8 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/quicksort.rb @@ -0,0 +1,24 @@ +def partition(array, low, high) + # type your code in here +end + +def quicksort(array, low = 0, high = array.length - 1) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [1, 2, 3, 4]" + print quicksort([3, 2, 1, 4]) + puts + + puts + + puts "Expecting: [1, 2, 2, 3, 4]" + print quicksort([1, 2, 2, 3, 4]) + puts + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/spec/quicksort_spec.rb b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/spec/quicksort_spec.rb new file mode 100644 index 00000000..c3474b85 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/spec/quicksort_spec.rb @@ -0,0 +1,47 @@ +require "./quicksort" + +RSpec.describe "quicksort" do + it "returns the same array that was passed in" do + arr_one = [] + arr_two = [1, 2, 3, 4] + + expect(quicksort(arr_one)).to be(arr_one) + expect(quicksort(arr_two)).to be(arr_two) + end + + it "returns an empty array when passed an empty array" do + expect(quicksort([])).to eq([]) + end + + it "can sort an array with a single element" do + expect(quicksort([2])).to eq([2]) + end + + it "can sort an array with two elements" do + expect(quicksort([2, 1])).to eq([1, 2]) + end + + it "can sort an array with three elements" do + expect(quicksort([2, 1, 3])).to eq([1, 2, 3]) + end + + it "can sort an array with more than three elements" do + expect(quicksort([4, 2, 1, 3])).to eq([1, 2, 3, 4]) + expect(quicksort([4, 2, 6, 5, 1, 3])).to eq([1, 2, 3, 4, 5, 6]) + end + + it "can sort a sorted array" do + expect(quicksort([1, 2, 3, 4, 5, 6])).to eq([1, 2, 3, 4, 5, 6]) + end + + it "can sort a reverse sorted array" do + expect(quicksort([6, 5, 4, 3, 2, 1])).to eq([1, 2, 3, 4, 5, 6]) + end + + it "can sort an array with many elements" do + arr = [] + 1000.times { arr << rand * 1000} + + expect(quicksort(arr)).to eq(arr.sort) + end +end \ No newline at end of file diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/spec/spec_helper.rb b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/bonus.js b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/bonus.js new file mode 100644 index 00000000..625200c5 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/bonus.js @@ -0,0 +1,111 @@ +// // BONUS: REDUCE NUMBER OF SWAPS +// function partition(array, low, high) { +// const pivot = array[high]; // choose rightmost element as pivot +// let pivotIndex = high; // store index of pivot +// let i = low; + +// while (i < pivotIndex) { +// if (array[i] <= pivot) { +// ++i; +// continue; +// } + +// const beforePivot = pivotIndex - 1; +// [array[i], array[beforePivot]] = [array[beforePivot], array[i]]; +// --pivotIndex; +// } +// // SWAP ONCE AT END OF LOOP +// [array[pivotIndex], array[high]] = [array[high], array[pivotIndex]]; + +// return pivotIndex; +// } + +// SUPER BONUS: USE MIDDLE AS PIVOT +function partition(array, low, high) { + const middle = Math.floor((high + low ) / 2); + const pivot = array[middle]; // choose middle element as pivot + [array[middle], array[high]] = [array[high], array[middle]]; // swap middle to end + + let pivotIndex = high; // store index of pivot + let i = low; + + while (i < pivotIndex) { + if (array[i] <= pivot) { + ++i; + continue; + } + + const beforePivot = pivotIndex - 1; + [array[i], array[beforePivot]] = [array[beforePivot], array[i]]; + --pivotIndex; + } + // SWAP ONCE AT END OF LOOP + [array[pivotIndex], array[high]] = [array[high], array[pivotIndex]]; + + return pivotIndex; +} + +function quicksort(array, low = 0, high = array.length - 1) { + if (low >= high) { + return array; + } + + const pivotIndex = partition(array, low, high); + quicksort(array, low, pivotIndex - 1); + quicksort(array, pivotIndex + 1, high); + + return array; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [1, 2, 3, 4]"); + console.log(quicksort([3, 2, 1, 4])); + + console.log(""); + + console.log("Expecting: [1, 2, 2, 3, 4]"); + console.log(quicksort([1, 2, 2, 3, 4])); + + console.log(""); + + console.log("Expecting: []"); + console.log(quicksort([])); + + console.log(""); + + console.log("Expecting: [1]"); + console.log(quicksort([1])); + + console.log(""); + + console.log("Expecting: [2, 4]"); + console.log(quicksort([2, 4])); + + console.log(""); + + console.log("Expecting: [1, 2, 3, 4, 5, 6, 7]"); + console.log(quicksort([1, 2, 3, 4, 5, 6, 7])); + + console.log(""); + + console.log("Expecting: [7, 6, 5, 4, 3, 2, 1]"); + console.log(quicksort([1, 2, 3, 4, 5, 6, 7])); + + console.log(""); + + console.log("Expecting: [4, 4, 4, 4]"); + console.log(quicksort([4, 4, 4, 4])); + + console.log(""); + + console.log("Expecting: [-10, -10, 0, 1, 2, 3, 4, 8, 9, 10, 87]"); + console.log(quicksort([-10, 8, 4, 3, 9, 10, -10, 87, 2, 0, 1])); + + console.log(""); +} + +module.exports = quicksort; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/bonus.rb b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/bonus.rb new file mode 100644 index 00000000..f5b4dc3b --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/bonus.rb @@ -0,0 +1,106 @@ +# BONUS: REDUCE NUMBER OF SWAPS +# def partition(array, low, high) +# pivot = array[high] # choose rightmost element as pivot +# pivot_index = high # store index of pivot +# i = low + +# while i < pivot_index +# if array[i] <= pivot +# i += 1 +# next +# end + +# before_pivot = pivot_index - 1 +# array[i], array[before_pivot] = array[before_pivot], array[i] +# pivot_index -= 1 +# end +# # SWAP ONCE AT END OF LOOP TO PUT PIVOT IN CORRECT LOCATION +# array[pivot_index], array[high] = array[high], array[pivot_index] +# pivot_index +# end + +# SUPER BONUS: USE MIDDLE AS PIVOT +def partition(array, low, high) + middle = (high + low ) / 2 + pivot = array[middle] # choose middle element as pivot + # SWAP MIDDLE TO END + array[middle], array[high] = array[high], array[middle] + + pivot_index = high # store index of pivot + i = low + + while i < pivot_index + if array[i] <= pivot + i += 1 + next + end + + before_pivot = pivot_index - 1 + array[i], array[before_pivot] = array[before_pivot], array[i] + pivot_index -= 1 + end + # SWAP ONCE AT END OF LOOP TO PUT PIVOT IN CORRECT LOCATION + array[pivot_index], array[high] = array[high], array[pivot_index] + pivot_index +end + +def quicksort(array, low = 0, high = array.length - 1) + return array if low >= high + + pivot_index = partition(array, low, high) + quicksort(array, low, pivot_index - 1) + quicksort(array, pivot_index + 1, high) + + array +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [1, 2, 3, 4]" + print quicksort([3, 2, 1, 4]) + puts + + puts + + puts "Expecting: [1, 2, 2, 3, 4]" + print quicksort([1, 2, 2, 3, 4]) + puts + + puts + + puts "Expecting: []" + print quicksort([]) + puts + + puts + + puts "Expecting: [5]" + print quicksort([5]) + puts + + puts + + puts "Expecting: [2, 4]" + print quicksort([2, 4]) + puts + + puts + + puts "Expecting: [1, 2, 3, 4, 5, 6, 7]" + print quicksort([1, 2, 3, 4, 5, 6, 7]) + puts + + puts + + puts "Expecting: [4, 4, 4, 4]" + print quicksort([4, 4, 4, 4]) + puts + + puts + + puts "Expecting: [-10, -10, 0, 1, 2, 3, 4, 8, 9, 10, 87]" + print quicksort([-10, 8, 4, 3, 9, 10, -10, 87, 2, 0, 1]) + puts +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/quicksort.js b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/quicksort.js new file mode 100644 index 00000000..ac22c745 --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/quicksort.js @@ -0,0 +1,146 @@ +function partition(array, low, high) { + const pivot = array[high]; // choose rightmost element as pivot + let pivotIndex = high; // store index of pivot + + // iterate over the portion of the array that needs processing + // use a while loop to control the index, we only want to increment it + // if we encounter an element that's less than or equal to the pivot + let i = low; + + while (i < pivotIndex) { + if (array[i] <= pivot) { + ++i; + continue; + } + + const beforePivot = pivotIndex - 1; + // swap the higher valued element with the one just before the pivot + // then swap the element just before the pivot with the pivot + // then update the pivot index since it moved + [array[i], array[beforePivot]] = [array[beforePivot], array[i]]; + [array[pivotIndex], array[beforePivot]] = [array[beforePivot], array[pivotIndex]]; + --pivotIndex; + } + + return pivotIndex; +} + +function quicksort(array, low = 0, high = array.length - 1) { + if (low >= high) { + return array; + } + + const pivotIndex = partition(array, low, high); + quicksort(array, low, pivotIndex - 1); + quicksort(array, pivotIndex + 1, high); + + return array; +} + +if (require.main === module) { + // add your own tests in here + console.log("Expecting: [1, 2, 3, 4]"); + console.log(quicksort([3, 2, 1, 4])); + + console.log(""); + + console.log("Expecting: [1, 2, 2, 3, 4]"); + console.log(quicksort([1, 2, 2, 3, 4])); + + console.log(""); + + console.log("Expecting: []"); + console.log(quicksort([])); + + console.log(""); + + console.log("Expecting: [1]"); + console.log(quicksort([1])); + + console.log(""); + + console.log("Expecting: [2, 4]"); + console.log(quicksort([2, 4])); + + console.log(""); + + console.log("Expecting: [1, 2, 3, 4, 5, 6, 7]"); + console.log(quicksort([1, 2, 3, 4, 5, 6, 7])); + + console.log(""); + + console.log("Expecting: [7, 6, 5, 4, 3, 2, 1]"); + console.log(quicksort([1, 2, 3, 4, 5, 6, 7])); + + console.log(""); + + console.log("Expecting: [4, 4, 4, 4]"); + console.log(quicksort([4, 4, 4, 4])); + + console.log(""); + + console.log("Expecting: [-10, -10, 0, 1, 2, 3, 4, 8, 9, 10, 87]"); + console.log(quicksort([-10, 8, 4, 3, 9, 10, -10, 87, 2, 0, 1])); + + console.log(""); + + // testing partition + let pArray = [3, 2, 1, 4]; + console.log("Expecting: 3, [3, 2, 1, 4]"); + console.log(partition(pArray, 0, pArray.length - 1), pArray); + + console.log(""); + + pArray = [3, 2, 1, 2]; + console.log("Expecting: 2, [1, 2, 2, 3]"); + console.log(partition(pArray, 0, pArray.length - 1), pArray); + + console.log(""); + + pArray = []; + console.log("Expecting: -1, []"); + console.log(partition(pArray, 0, pArray.length - 1), pArray); + + console.log(""); + + pArray = [3]; + console.log("Expecting: 0, [3]"); + console.log(partition(pArray, 0, pArray.length - 1), pArray); + + console.log(""); + + pArray = [1, 2]; + console.log("Expecting: 1, [1, 2]"); + console.log(partition(pArray, 0, pArray.length - 1), pArray); + + console.log(""); + + pArray = [0, 4, 2]; + console.log("Expecting: 1, [0, 2, 4]"); + console.log(partition(pArray, 0, pArray.length - 1), pArray); + + console.log(""); + + pArray = [2, -10, 7, 0, 1, 3]; + console.log("Expecting: 4, [2, -10, 1, 0, 3, 7]"); + console.log(partition(pArray, 0, pArray.length - 1), pArray); + + console.log(""); + + pArray = [2, -10, 7, 0, 1, 3]; + console.log("Expecting: 2, [2, -10, 0, 7, 1, 3]"); + console.log(partition(pArray, 1, 3), pArray); + + console.log(""); + + pArray = [2, 1, 0]; + console.log("Expecting: 0, [0, 1, 2]"); + console.log(partition(pArray, 0, 2), pArray); + + console.log(""); +} + +module.exports = quicksort; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/quicksort.rb b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/quicksort.rb new file mode 100644 index 00000000..ee6172bd --- /dev/null +++ b/12-week-9--searching-and-sorting-continued/03-bonus--quicksort/solutions/quicksort.rb @@ -0,0 +1,137 @@ +def partition(array, low, high) + pivot = array[high] # choose rightmost element as pivot + pivot_index = high # store index of pivot + + # iterate over the portion of the array that needs processing + # use a while loop to control the index, we only want to increment it + # if we encounter an element that's less than or equal to the pivot + i = low + + while i < pivot_index + if array[i] <= pivot + i += 1 + next + end + + before_pivot = pivot_index - 1 + # swap the higher valued element with the one just before the pivot + # then swap the element just before the pivot with the pivot + # then update the pivot index since it moved + array[i], array[before_pivot] = array[before_pivot], array[i] + array[pivot_index], array[before_pivot] = array[before_pivot], array[pivot_index] + pivot_index -= 1 + end + + pivot_index +end + +def quicksort(array, low = 0, high = array.length - 1) + return array if low >= high + + pivot_index = partition(array, low, high) + quicksort(array, low, pivot_index - 1) + quicksort(array, pivot_index + 1, high) + + array +end + +if __FILE__ == $PROGRAM_NAME + puts "Expecting: [1, 2, 3, 4]" + print quicksort([3, 2, 1, 4]) + puts + + puts + + puts "Expecting: [1, 2, 2, 3, 4]" + print quicksort([1, 2, 2, 3, 4]) + puts + + puts + + puts "Expecting: []" + print quicksort([]) + puts + + puts + + puts "Expecting: [2, 4]" + print quicksort([2, 4]) + puts + + puts + + puts "Expecting: [1, 2, 3, 4, 5, 6, 7]" + print quicksort([1, 2, 3, 4, 5, 6, 7]) + puts + + puts + + puts "Expecting: [4, 4, 4, 4]" + print quicksort([4, 4, 4, 4]) + puts + + puts + + puts "Expecting: [-10, -10, 0, 1, 2, 3, 4, 8, 9, 10, 87]" + print quicksort([-10, 8, 4, 3, 9, 10, -10, 87, 2, 0, 1]) + puts + + # Don't forget to add your own! + # testing partition + pArray = [3, 2, 1, 4] + puts "Expecting: 3, [3, 2, 1, 4]" + print(partition(pArray, 0, pArray.length - 1), pArray) + puts + puts + + pArray = [3, 2, 1, 2] + puts "Expecting: 2, [1, 2, 2, 3]" + print(partition(pArray, 0, pArray.length - 1), pArray) + puts + puts + + pArray = [] + puts "Expecting: -1, []" + print(partition(pArray, 0, pArray.length - 1), pArray) + puts + puts + + pArray = [3] + puts "Expecting: 0, [3]" + print(partition(pArray, 0, pArray.length - 1), pArray) + puts + puts + + pArray = [1, 2] + puts "Expecting: 1, [1, 2]" + print(partition(pArray, 0, pArray.length - 1), pArray) + puts + puts + + pArray = [0, 4, 2] + puts "Expecting: 1, [0, 2, 4]" + print(partition(pArray, 0, pArray.length - 1), pArray) + puts + puts + + pArray = [2, -10, 7, 0, 1, 3] + puts "Expecting: 4, [2, -10, 1, 0, 3, 7]" + print(partition(pArray, 0, pArray.length - 1), pArray) + puts + puts + + pArray = [2, -10, 7, 0, 1, 3] + puts "Expecting: 2, [2, -10, 0, 7, 1, 3]" + print(partition(pArray, 1, 3), pArray) + puts + puts + + pArray = [2, 1, 0] + puts "Expecting: 0, [0, 1, 2]" + print(partition(pArray, 0, 2), pArray) + puts + puts +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/.gitignore b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/README.md b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/README.md new file mode 100644 index 00000000..57abf449 --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/README.md @@ -0,0 +1,139 @@ +# Days 1 to 2: Create a Queue Class Using Nodes + +Previously, we created a Queue class using an Array as the underlying data structure. For this challenge we'll recreate the Queue Class but with Nodes instead, similar to a Linked List. We'll then calculate the time complexity using Big O notation for each of our Queue class methods, so we can see how this change affects the runtime. + +## What Is a Queue? + +A queue is a data structure where items are processed in first-in-first-out order (FIFO). It has two ends: a front and a rear. The front is where items are dequeued from first, while items at the rear are handled last. Items are added to the rear of the queue. A queue operates a lot like a queue at the checkout of a grocery store. Customers join at the end of the line and are served at the front of the line. + +## Implement the Queue Class + +Our Queue class will track its front node and its rear node. We will assume that nodes will be added to the queue one at a time. They will also be removed one at a time. Each node will have two attributes: `data`, which tracks the value the node stores, and `next`, which points to the next node in the queue. In Ruby, the next attribute will be called `next_node`, since next is a reserved keyword and we'd prefer to not use confusing syntax. + +Let's implement the following methods for the Queue class: + +### `enqueue(data)` + +`enqueue` adds a node to the back of the queue using the provided data. + +``` +queue = new Queue() + +queue.enqueue("first") +queue.front +=> Node, data: "first", next: nil + +queue.rear +=> Node, data: "first", next: nil +``` + +### `dequeue` + +`dequeue` removes the node at the front of the queue and returns it. Don't worry if `dequeue` is called on an empty queue. It's OK for it to return the default return value, such as `undefined` or `null` or `nil`. + +``` +queue = new Queue() + +queue.enqueue("first") +queue.dequeue() +=> Node, data: "first", next: nil +``` + +### `peek` + +`peek` returns the node at the front of the queue without removing it. If the queue is empty, use the default return value, e.g. `undefined` or `nil`. + +``` +queue = new Queue() + +queue.enqueue("first") +queue.peek() +=> Node, data: "first", next: nil + +queue.front +=> Node, data: "first", next: nil +``` + +### `isEmpty` + +`isEmpty` returns `true` if the queue is empty, otherwise `false`. + +``` +queue = new Queue() + +queue.isEmpty() +=> true +``` + +### `size` + +`size` returns the number of nodes currently in the queue. + +``` +queue = new Queue() +queue.size() +=> 0 + +queue.enqueue("first") +queue.size() +=> 1 +``` + +### `search(target)` + +`search` returns an Integer representing how far the target node is from the front of the queue. If the node is not in the queue, return `-1`. Example: + +``` +// queue = 1, 2, 3, 4, 5 <- rear + +queue.search(5) => 4 +queue.search(4) => 3 +queue.search(6) => -1 +``` + +## Calculate Time Complexity + +When you are done implementing the class, determine the time complexity for the following methods and compare that to the time complexity for when an Array is used as the underlying data structure: + +- `enqueue`: Time complexity when using an Array is O(1) +- `dequeue`: Time complexity when using an Array is O(n) +- `peek`: Time complexity when using an Array is O(1) +- `search`: Time complexity when using an Array is O(n) + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/javascript/package.json b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/javascript/package.json new file mode 100644 index 00000000..d1142529 --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "queue", + "version": "1.0.0", + "description": "build a queue class", + "main": "queue.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/javascript/queue.js b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/javascript/queue.js new file mode 100644 index 00000000..cf6a96f5 --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/javascript/queue.js @@ -0,0 +1,60 @@ +class Node { + constructor(data = null, next = null) { + this.data = data; + this.next = next; + } +} + +class Queue { + constructor(front = null) { + this.front = front; + this.rear = front; + } + + // ADD NODE TO BACK OF QUEUE + // USE DATA TO CREATE A NEW NODE AND ADD IT TO THE QUEUE + enqueue(data) { + + } + + // REMOVE NODE FROM FRONT OF QUEUE AND RETURN IT + dequeue() { + + } + + // RETURN NODE AT FRONT WITHOUT REMOVING IT + peek() { + + } + + // RETURN TRUE IF QUEUE IS EMPTY, OTHERWISE FALSE + isEmpty() { + + } + + // RETURN NUMBER OF NODES IN QUEUE, E.G. 10 + size() { + + } + + // RETURN INTEGER REPRESENTING HOW FAR TARGET IS FROM FRONT OF QUEUE + // IF TARGET ISN'T IN QUEUE, RETURN -1 + search(target) { + + } +} + +if (require.main === module) { + // add your own tests in here + +} + +module.exports = { + Node, + Queue +}; + +// Write your Big O findings here + +// Optional: Please add your pseudocode to this file +// Optional: And a written explanation of your solution diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/javascript/tests/queue.test.js b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/javascript/tests/queue.test.js new file mode 100644 index 00000000..d5870ca0 --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/javascript/tests/queue.test.js @@ -0,0 +1,118 @@ +const { Node, Queue } = require('../queue'); + +describe("Queue", () => { + describe("enqueue(data)", () => { + it ("adds a new node to an empty queue and sets the front and rear correctly", () => { + const queue = new Queue(); + queue.enqueue(1); + + expect(queue.front).toBeInstanceOf(Node); + expect(queue.rear).toBe(queue.front); + + const front = queue.front; + const rear = queue.rear; + + queue.enqueue(2); + + expect(queue.front).toBe(front); + expect(queue.rear).not.toBe(rear); + expect(queue.rear.data).toBe(2); + }) + }); + + describe("dequeue()", () => { + it ("removes and returns the front of the queue and sets the front and rear correctly", () => { + let queue = new Queue(); + + expect(queue.dequeue()).toBeFalsy(); + + queue = new Queue(); + queue.enqueue(1); + let removed = queue.dequeue(); + + expect(removed).toBeInstanceOf(Node); + expect(removed.data).toBe(1); + expect(queue.front).toBeFalsy(); + expect(queue.rear).toBe(queue.front); + + queue = new Queue(); + queue.enqueue(1); + queue.enqueue(2); + removed = queue.dequeue(); + + expect(removed).toBeInstanceOf(Node); + expect(removed.data).toBe(1); + expect(queue.front.data).toBe(2); + expect(queue.rear).toBe(queue.front); + + queue = new Queue(); + queue.enqueue(1); + queue.enqueue(2); + queue.enqueue(3); + removed = queue.dequeue(); + + expect(removed).toBeInstanceOf(Node); + expect(removed.data).toBe(1); + expect(queue.front.data).toBe(2); + expect(queue.rear).not.toBe(queue.front); + }); + }); + + describe("peek()", () => { + it ("returns the front of the queue without removing it", () => { + const queue = new Queue(); + queue.enqueue(1); + + expect(queue.peek()).toBe(queue.front); + }); + }); + + describe("isEmpty()", () => { + it ("returns true if the queue is empty, otherwise false", () => { + const queue = new Queue(); + + expect(queue.isEmpty()).toBe(true); + + queue.enqueue(1); + + expect(queue.isEmpty()).toBe(false); + }); + }); + + describe("size()", () => { + it ("returns the number of nodes in the queue", () => { + const queue = new Queue(); + + expect(queue.size()).toBe(0); + + queue.enqueue(1); + + expect(queue.size()).toBe(1); + + queue.enqueue(2); + + expect(queue.size()).toBe(2); + }); + }); + + describe("target(data)", () => { + it ("returns the distance from the front to the target, or -1 if not found", () => { + const queue = new Queue(); + + expect(queue.search(1)).toBe(-1); + + queue.enqueue(1); + + expect(queue.search(1)).toBe(0); + + queue.enqueue(2); + + expect(queue.search(2)).toBe(1); + expect(queue.search(5)).toBe(-1); + + queue.enqueue(3); + + expect(queue.search(3)).toBe(2); + }); + }); +}); diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/.rspec b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/Gemfile b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/queue.rb b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/queue.rb new file mode 100644 index 00000000..0cc0d5cc --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/queue.rb @@ -0,0 +1,58 @@ +class Node + attr_accessor :data, :next_node + + def initialize(data = nil, next_node = nil) + @data = data + @next_node = next_node + end +end + +class Queue + attr_reader :front, :rear + + def initialize(front = nil) + @front = front + @rear = front + end + + # ADD NODE TO BACK OF QUEUE + # USE DATA TO CREATE A NEW NODE AND ADD IT TO THE QUEUE + def enqueue(data) + + end + + # REMOVE NODE FROM FRONT OF QUEUE AND RETURN IT + def dequeue + + end + + # RETURN NODE AT FRONT WITHOUT REMOVING IT + def peek + + end + + # RETURN TRUE IF QUEUE IS EMPTY, OTHERWISE FALSE + def isEmpty + + end + + # RETURN NUMBER OF NODES IN QUEUE, E.G. 10 + def size + + end + + # RETURN INTEGER REPRESENTING HOW FAR TARGET IS FROM FRONT OF QUEUE + # IF TARGET ISN'T IN QUEUE, RETURN -1 + def search(target) + + end +end + +if __FILE__ == $PROGRAM_NAME + # Add your own tests in here +end + +# Write your Big O findings here + +# Optional: Please add your pseudocode to this file +# Optional: And a written explanation of your solution diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/spec/queue_spec.rb b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/spec/queue_spec.rb new file mode 100644 index 00000000..46f5a463 --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/spec/queue_spec.rb @@ -0,0 +1,118 @@ +require './queue' + +describe "Queue" do + context "#enqueue(data)" do + it "adds a new node to an empty queue and sets the front and rear correctly" do + queue = Queue.new + queue.enqueue(1) + + expect(queue.front).to be_a(Node) + expect(queue.rear).to be(queue.front) + + front = queue.front + rear = queue.rear + + queue.enqueue(2) + + expect(queue.front).to be(front) + expect(queue.rear).not_to be(rear) + expect(queue.rear.data).to be(2) + end + end + + context "#dequeue" do + it "removes and returns the front of the queue and sets the front and rear correctly" do + queue = Queue.new + + expect(queue.dequeue).to be_nil + + queue = Queue.new + queue.enqueue(1) + removed = queue.dequeue + + expect(removed).to be_a(Node) + expect(removed.data).to be(1) + expect(queue.front).to be_nil + expect(queue.rear).to be(queue.front) + + queue = Queue.new + queue.enqueue(1) + queue.enqueue(2) + removed = queue.dequeue + + expect(removed).to be_a(Node) + expect(removed.data).to be(1) + expect(queue.front.data).to be(2) + expect(queue.rear).to be(queue.front) + + queue = Queue.new + queue.enqueue(1) + queue.enqueue(2) + queue.enqueue(3) + removed = queue.dequeue + + expect(removed).to be_a(Node) + expect(removed.data).to be(1) + expect(queue.front.data).to be(2) + expect(queue.rear).not_to be(queue.front) + end + end + + context "#peek" do + it "returns the front of the queue without removing it" do + queue = Queue.new + queue.enqueue(1) + + expect(queue.peek).to be(queue.front) + end + end + + context "#isEmpty" do + it "returns true if the queue is empty, otherwise false" do + queue = Queue.new + + expect(queue.isEmpty).to be true + + queue.enqueue(1) + + expect(queue.isEmpty).to be false + end + end + + context "#size" do + it "returns the number of nodes in the queue" do + queue = Queue.new + + expect(queue.size).to be(0) + + queue.enqueue(1) + + expect(queue.size).to be(1) + + queue.enqueue(2) + + expect(queue.size).to be(2) + end + end + + context "#target(data)" do + it "returns the distance from the front to the target, or -1 if not found" do + queue = Queue.new + + expect(queue.search(1)).to be(-1) + + queue.enqueue(1) + + expect(queue.search(1)).to be(0) + + queue.enqueue(2) + + expect(queue.search(2)).to be(1) + expect(queue.search(5)).to be(-1) + + queue.enqueue(3) + + expect(queue.search(3)).to be(2) + end + end +end diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/spec/spec_helper.rb b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/solutions/queue.js b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/solutions/queue.js new file mode 100644 index 00000000..819f3574 --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/solutions/queue.js @@ -0,0 +1,105 @@ +class Node { + constructor(data = null, next = null) { + this.data = data; + this.next = next; + } +} + +class Queue { + constructor(front = null) { + this.front = front; + this.rear = front; + } + + // ADD NODE TO BACK OF QUEUE + // USE DATA TO CREATE A NEW NODE AND ADD IT TO THE QUEUE + enqueue(data) { + const node = new Node(data); + + if (!this.rear) { + this.front = node; + this.rear = node; + return; + } + + this.rear.next = node; + this.rear = node; + } + + // REMOVE NODE FROM FRONT OF QUEUE AND RETURN IT + dequeue() { + if (!this.front) { + return this.front; + } + + const oldFront = this.front; + + this.front = oldFront.next; + + if (!this.front || !this.front.next) { + this.rear = this.front; + } + + return oldFront; + } + + // RETURN NODE AT FRONT WITHOUT REMOVING IT + peek() { + return this.front; + } + + // RETURN TRUE IF QUEUE IS EMPTY, OTHERWISE FALSE + isEmpty() { + return !this.front; + } + + // RETURN NUMBER OF NODES IN QUEUE, E.G. 10 + size() { + let count = 0; + let node = this.front; + + while (node) { + ++count; + node = node.next; + } + + return count; + } + + // RETURN INTEGER REPRESENTING HOW FAR TARGET IS FROM FRONT OF QUEUE + // IF TARGET ISN'T IN QUEUE, RETURN -1 + search(target) { + let node = this.front; + let distance = 0; + + while (node) { + if (node.data === target) { + return distance; + } + + ++distance; + node = node.next; + } + + return -1; + } +} + +if (require.main === module) { + // add your own tests in here + +} + +module.exports = { + Node, + Queue +}; + +// Write your Big O findings here +// `enqueue`: Time complexity when using an Array is O(1), with nodes O(1) +// `dequeue`: Time complexity when using an Array is O(n), with nodes O(1) +// `peek`: Time complexity when using an Array is O(1), with nodes O(1) +// `search`: Time complexity when using an Array is O(n), with nodes O(n) + +// Optional: Please add your pseudocode to this file +// Optional: And a written explanation of your solution diff --git a/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/solutions/queue.rb b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/solutions/queue.rb new file mode 100644 index 00000000..e411770d --- /dev/null +++ b/13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes/solutions/queue.rb @@ -0,0 +1,101 @@ +class Node + attr_accessor :data, :next_node + + def initialize(data = nil, next_node = nil) + @data = data + @next_node = next_node + end +end + +class Queue + attr_reader :front, :rear + + def initialize(front = nil) + @front = front + @rear = front + end + + # ADD NODE TO BACK OF QUEUE + # USE DATA TO CREATE A NEW NODE AND ADD IT TO THE QUEUE + def enqueue(data) + node = Node.new(data) + + if @rear.nil? + @front = node + @rear = node + return + end + + @rear.next_node = node + @rear = node + end + + # REMOVE NODE FROM FRONT OF QUEUE AND RETURN IT + def dequeue + return @front if front.nil? + + oldFront = @front + + @front = oldFront.next_node + + if @front.nil? || @front.next_node.nil? + @rear = @front + end + + oldFront + end + + # RETURN NODE AT FRONT WITHOUT REMOVING IT + def peek + @front + end + + # RETURN TRUE IF QUEUE IS EMPTY, OTHERWISE FALSE + def isEmpty + @front.nil? + end + + # RETURN NUMBER OF NODES IN QUEUE, E.G. 10 + def size + count = 0 + node = @front + + while node + count += 1 + node = node.next_node + end + + count + end + + # RETURN INTEGER REPRESENTING HOW FAR TARGET IS FROM FRONT OF QUEUE + # IF TARGET ISN'T IN QUEUE, RETURN -1 + def search(target) + node = @front + distance = 0 + + while node + if node.data === target + return distance + end + + distance += 1 + node = node.next_node + end + + -1 + end +end + +if __FILE__ == $PROGRAM_NAME + # Add your own tests in here +end + +# Write your Big O findings here +# `enqueue`: Time complexity when using an Array is O(1), with nodes O(1) +# `dequeue`: Time complexity when using an Array is O(n), with nodes O(1) +# `peek`: Time complexity when using an Array is O(1), with nodes O(1) +# `search`: Time complexity when using an Array is O(n), with nodes O(n) + +# Optional: Please add your pseudocode to this file +# Optional: And a written explanation of your solution diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/.gitignore b/13-week-10/01-days-3-to-5--build-an-lru-cache/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/README.md b/13-week-10/01-days-3-to-5--build-an-lru-cache/README.md new file mode 100644 index 00000000..228a332d --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/README.md @@ -0,0 +1,247 @@ +# Days 3 to 5: LRU Cache + +A Least Recently Used (LRU) Cache is a data structure that organizes items in order of use or access from most recently accessed to least recently accessed. It has a fixed length or is allowed to take up a maximum amount of space in memory, i.e. there is a limit to the number of items it contains. You can think of it as being similar to items in a kitchen cabinet: the items you use most often will likely be easy to see and within reach when you open the cupboard. The items you use less often will be behind those items, and the items you use least may be completely out of reach. + +## Why Use an LRU Cache? + +The short story is that it's faster to read data from a cache (such as on the RAM) than it is to read it from a database or hard disk. Since there is a limit to how much data we can cache, we have to come up with a system to determine which data stays and which data gets evicted from the cache. With an LRU cache, we evict the least recently used data once the cache is full. If you'd like the long story, carry on reading this section. + +Let's say we have a website where users can access many different stories. Our website gets a lot of traffic, and each visit results in a request to the database for a story. The responses to these requests are becoming slower and slower since accessing the database is expensive. We look at which stories our users are accessing most often and notice that there are 10 stories that most users access and those specific stories shift over time, i.e. the popular stories at 5PM differ from the popular stories at 10PM. This means we can cache this data, or these stories, on the server. When we do this, requests for these stories will not result in database requests. Instead, they will be quickly retrieved directly from the server's memory. + +We decide to implement an LRU cache! When a user makes a request for a story, we check if it's in the cache. If it is, we respond with that story and reorder the data in our cache to reflect that the story was the most recently accessed. If the story isn't in the cache, we make the request to the database, and then place the story in the correct place within the LRU cache. If the cache is full, we also remove the least recently accessed story from it. + +## Trade Offs + +The typical design of an LRU cache allows for extremely fast access: checking if an item is in the cache is a constant time O(1) operation. Updating the cache is also extremely fast: O(1). + +However, the trade off is the amount of space required to store the data. An LRU cache often uses two data structures - a doubly linked list and hash map - to store the data in the correct order. This means it takes up more space than using a simpler data structure, such as an Array. Note that it is possible to implement an LRU cache with different data structures. + +## Why Two Data Structures? + +The LRU cache requires its data to be ordered. It also requires fast access and updating (insertion and deletion). Let's say we used an Array, since it's ordered. We only have fast access if we know the index of the element we're looking for in the Array, and our program is unlikely to have that information. Similarly, inserting elements at the front of an Array is an O(n) operation, so we can't use an Array for an LRU cache. + +We could also think about using a hash/object, but hashes are not ordered, so we have another problem here. However, they do provide O(1) access when you know the key for the value you're looking for. It is also extremely fast to add and remove key-value pairs from a Hash. We could also think about using a doubly linked list (a doubly linked lists contains nodes that point to the next node and previous node). Linked lists are ordered and they allow for fast insertion and deletion. However, finding a specific item takes O(n) time, since we must traverse the list. + +When we combine a hash with a doubly linked list, we get the best of both worlds: constant-time access, insertion, and deletion! ...as long as we design our LRU cache properly that is. + +## How Do We Combine These Data Structures? + +![LRU Cache](./lru_cache.svg) + +It's easier to think about how these data structures work together if we first set up some rules: + +- When we check if an item is in the cache, we always check if it's in the hash +- A key in the hash always points to a node in the doubly linked list +- Items in the doubly linked list are always ordered from most recenlty used (at the head) to least recently used (at the tail) +- If an item is being added to a cache that's full, the least recently used item is removed from both the hash and list (at the tail) + - The list tells us which item was least recently used since it will always be at the tail +- If an item is being retrieved from the cache, it is moved to the head of the list and the hash remains unchanged + +Let's say we start with an empty cache. We want the recipe associated with an item that has an ID of "cake". First, we check if "cake" is in the cache by asking the hash if it has a key of "cake". The cache is empty, so the data is retrieved from the database. Next, the LRU cache creates a new node, which stores the recipe. The node is added to the doubly linked list as its head and tail since the list contains only one item. Next, a key of "cake" is added to the hash, and its value is set to the node that was just created. At the end of this operation we have a list with a single node in it, and a hash with a key of "cake" pointing to that node. + +``` +lru_cache.hash +=> { + "cake": Node storing "cake"s recipe +} + +lru_cache.list +=> Doubly Linked List with head and tail both pointing to "cake" recipe Node +``` + +Now another user comes along asking for "cake". We check the cache's hash for "cake". It's there, so we retrieve its value and send it to the user. We don't need to do anything else because "cake" is already the most recently accessed item in the cache. + +Next a user asks for "cookies". We check the hash, but it's not in there, so we retrieve the recipe from the database. Next, we create a node using the "cookies" data and set that node as the head of the doubly linked list, since it's the most recently accessed item. This means that "cake" is now the tail. Lastly, we add a key of "cookies" to the hash and have it point to the node. + +``` +lru_cache.hash +=> { + "cake": Node storing "cake"s recipe, + "cookies": Node storing "cookie"s recipe +} + +lru_cache.list +=> Doubly Linked List with head pointing to "cookies" Node and tail pointing to "cake" Node +``` + +Let's pretend our cache can only store two recipes. This means our cache is full! A user comes along and asks for an omelette recipe. The LRU Cache's hash doesn't have an "omelette" key, so the data is retrieved from the database. A node is created using the omelette data. The cache goes to add it to the head of the list, but the list is full, so it kicks out "cake", which is at the tail. The omelette node is then added as the head of the list, and a key is created in the hash which points to the omelette node. + +``` +lru_cache.hash +=> { + "cookies": Node storing "cake"s recipe, + "omelette": Node storing "omelette"s recipe +} + +lru_cache.list +=> Doubly Linked List with head pointing to "omelette" Node and tail pointing to "cookies" Node +``` + +## Implement an LRU Cache + +We'll take this slow: first we'll create our Doubly Linked List class, and then we'll move on to creating the LRU Cache. For the Doubly Linked List class, we'll only worry about creating the methods required for our cache to work. Note that our tests will only check if the cache is functioning correctly. It'll be up to you to test that the list is working correctly. + +The Node class has already been filled out. It contains four attributes: `data`, which stores some kind of value, `key`, which stores the associated key name in the hash, `next`/`next_node`, which points to the next node in the list, and `prev`/`prev_node`, which points to the previous node in the list. + +### Implement the Doubly Linked List + +As you implement each of these methods, make sure you're correctly updating each Node's `next` and `prev` attributes. Also keep in mind that each of these operations should take constant time: O(1). + +You may assume that only valid inputs will be provided to each method. For example, if you're asked to move a node to the head of the list, it's guaranteed that the argument will contain a node and that the node will be in the list already. + +#### 1. `addHead(node)` / `add_head(node)` + +Place the given node at the head of the list. Do not remove the existing head if there is one! + +``` +list = new DoublyLinkedList +list.add_head(node1) +list.head +=> node1 + +list.add_head(node2) +list.head +=> node2 + +list.head.next +=> node1 + +list.tail +=> node1 +``` + +#### 2. `removeTail()` / `remove_tail` + +Remove the tail from the list and return it. + +``` +list = new DoublyLinkedList +list.add_head(node1) +list.head +=> node1 + +list.tail +=> node1 + +list.remove_tail() +=> node1 + +list.tail +=> null or nil +``` + +#### 3. `removeNode(node)` / `remove_node(node)` + +Remove the given node from the list and return it. + +``` +list = new DoublyLinkedList +list.add_head(node1) +list.add_head(node2) +list.add_head(node3) +// list is 3 -> 2 -> 1 + +list.remove_node(node2) +=> node2 +// list is 3 -> 1 +``` + +#### 4. `moveNodeToHead(node)` / `move_node_to_head(node)` + +Move the given node to the head of the list. + +``` +list = new DoublyLinkedList +list.add_head(node1) +list.add_head(node2) +list.add_head(node3) +// list is 3 -> 2 -> 1 + +list.move_node_to_head(node2) +// list is 2 -> 3 -> 1 +``` + +### Implement the LRU Cache + +Now that we have our list ready for use, we can implement the LRU Cache! Our cache will contain only two methods (one to get data and another to add it), but you can add helper methods if you need to. The necessary attributes - `limit`, `size`, `hash`, and `list` - have already been added to the cache. The `limit` is set to a default value, which can be overridden when instantiating a new cache. It sets the maximum number of items allowed in the cache. The `size` tracks the total number of items currently in the cache. The `hash` and `list` point to the hash/object and Doubly Linked List that store the cache's data. + +As you work on the cache, remember that you'll also need to determine when and where to update the `size` of the cache. Also keep in mind that our operations need to run in constant time. + +You may assume that the methods will only be given valid arguments. + +#### 1. `get(key)` + +Return the item from the cache using the given `key`. If the item is in the cache, move it to the head of the list to denote that it is the most recently accessed item. If the item isn't in the cache, return `-1`. + +``` +lru_cache.get("potato") +=> Node with data associated with "potato" + +lru_cache.get("notato") +=> -1 +``` + +#### 2. `put(key, value)` + +Add or update the item in the cache. If the key does not exist in the cache, add the item to the cache. If the key is in the cache, update the item with the value. In any case, move the item to the head of the list to denote that it's the most recently accessed item. If the cache is already full, remove the least recently used item from the cache before adding the new item. + +``` +lru_cache = new LRUCache(3) +lru_cache.put("cake", "cake recipe") + +lru_cache.get("cake") +=> Node with data "cake recipe" + +lru_cache.put("cookies", "cookie recipe") +lru_cache.put("cake", "fixed cake recipe") +lru_cache.put("scones", "scone recipe") +lru_cache.put("smoothie", "smoothie recipe") + +lru_cache.get("cake") +=> Node with data "fixed cake recipe" + +lru_cache.get("smoothie") +=> Node with data "smoothie recipe" + +lru_cache.get("cookies") +=> -1 +// cookies got kicked out when smoothie was added because the cache was full +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/javascript/lru_cache.js b/13-week-10/01-days-3-to-5--build-an-lru-cache/javascript/lru_cache.js new file mode 100644 index 00000000..e12ecbdb --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/javascript/lru_cache.js @@ -0,0 +1,78 @@ +class Node { + constructor(data = null, key = null, next = null, prev = null) { + this.data = data; + this.key = key; + this.next = next; + this.prev = prev; + } +} + +class DoublyLinkedList { + constructor(head = null, tail = null) { + this.head = head; + this.tail = tail; + } + + // ADD THE NODE TO THE HEAD OF THE LIST + addHead(node) { + + } + + // REMOVE THE TAIL NODE FROM THE LIST + // AND RETURN IT + removeTail() { + + } + + // REMOVE THE GIVEN NODE FROM THE LIST + // AND THEN RETURN IT + removeNode(node) { + + } + + // MOVE THE GIVEN NODE FROM ITS LOCATION TO THE HEAD + // OF THE LIST + moveNodeToHead(node) { + + } +} + +class LRUCache { + constructor(limit = 10) { + this.limit = limit; + this.size = 0; + this.hash = {}; + this.list = new DoublyLinkedList(limit); + } + + // RETRIEVE THE NODE FROM THE CACHE USING THE KEY + // IF THE NODE IS IN THE CACHE, MOVE IT TO THE HEAD OF THE LIST AND RETURN IT + // OTHERWISE RETURN -1 + get(key) { + + } + + // ADD THE GIVEN KEY AND VALUE TO THE CACHE + // IF THE CACHE ALREADY CONTAINS THE KEY, UPDATE ITS VALUE AND MOVE IT TO + // THE HEAD OF THE LIST + // IF THE CACHE DOESN'T CONTAIN THE KEY, ADD IT TO THE CACHE AND PLACE IT + // AT THE HEAD OF THE LIST + // IF THE CACHE IS FULL, REMOVE THE LEAST RECENTLY USED ITEM BEFORE ADDING + // THE NEW DATA TO THE CACHE + put(key, value) { + + } +} + +if (require.main === module) { + // add your own tests in here +} + +module.exports = { + Node, + DoublyLinkedList, + LRUCache +}; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/javascript/package.json b/13-week-10/01-days-3-to-5--build-an-lru-cache/javascript/package.json new file mode 100644 index 00000000..9eae89c5 --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "lru-cache", + "version": "1.0.0", + "description": "LRU Cache", + "main": "lru_cache.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/javascript/tests/lru_cache.test.js b/13-week-10/01-days-3-to-5--build-an-lru-cache/javascript/tests/lru_cache.test.js new file mode 100644 index 00000000..762fb36c --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/javascript/tests/lru_cache.test.js @@ -0,0 +1,108 @@ +const { Node, LRUCache } = require("../lru_cache"); + +describe("LRUCache", () => { + it ("correctly creates and updates a cache with a limit of 1 item", () => { + const cache = new LRUCache(1); + + let found = cache.get("test"); + + expect(found).toBe(-1); + + let key = "cake"; + let val = "cake recipe"; + cache.put(key, val); + found = cache.get(key); + + expect(found).toBeInstanceOf(Node); + expect(found.data).toBe(val); + + key = "cookies"; + val = "cookie recipe"; + cache.put(key, val); + found = cache.get(key); + let shouldBeGone = cache.get("cake"); + + expect(found).toBeInstanceOf(Node); + expect(found.data).toBe(val); + expect(shouldBeGone).toBe(-1); + + val = "new cookie recipe"; + cache.put(key, val); + found = cache.get(key); + + expect(found).toBeInstanceOf(Node); + expect(found.data).toBe(val); + expect(cache.size).toBe(1); + }); + + it ("correctly creates and updates a cache with a limit of 2 items", () => { + const cache = new LRUCache(2); + + let found = cache.get("test"); + + expect(found).toBe(-1); + + let key1 = "cake"; + let val1 = "cake recipe"; + cache.put(key1, val1); + let found1 = cache.get(key1); + let key2 = "cookies"; + let val2 = "cookie recipe"; + cache.put(key2, val2); + let found2 = cache.get(key2); + + expect(found1.data).toBe(val1); + expect(found2.data).toBe(val2); + expect(cache.size).toBe(2); + + val1 = "new cake recipe"; + cache.put(key1, val1); + val2 = "new cookie recipe"; + cache.put(key2, val2); + found1 = cache.get(key1); + found2 = cache.get(key2); + + expect(found1.data).toBe(val1); + expect(found2.data).toBe(val2); + expect(cache.size).toBe(2); + + let key3 = "scones"; + let val3 = "scone recipe"; + cache.put(key3, val3); + let found3 = cache.get(key3); + found1 = cache.get(key1); + found2 = cache.get(key2); + + expect(found3.data).toBe(val3); + expect(cache.size).toBe(2); + expect(found1).toBe(-1); + expect(found2.data).toBe(val2); + }); + + it ("correctly creates a cache with many items", () => { + const cache = new LRUCache(10); + const numbers = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"]; + const items = numbers.reduce((accum, num) => { + accum.push({ [num]: `item ${num}` }); + return accum; + }, []); + + items.forEach((item, idx) => { + const key = numbers[idx]; + const val = item[key]; + + cache.put(key, val); + }); + + expect(cache.size).toBe(10); + expect(cache.get(numbers[0])).toBe(-1); + expect(cache.get(numbers[1])).toBe(-1); + expect(cache.get(numbers[2]).data).toBe(items[2][numbers[2]]); + expect(cache.get(numbers[11]).data).toBe(items[11][numbers[11]]); + + const updatedVal = "whatevers"; + cache.put(numbers[4], updatedVal); + + expect(cache.get(numbers[4]).data).toBe(updatedVal); + }); +}); diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/lru_cache.svg b/13-week-10/01-days-3-to-5--build-an-lru-cache/lru_cache.svg new file mode 100644 index 00000000..c1340433 --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/lru_cache.svg @@ -0,0 +1,193 @@ + + Doubly-linked list with a dictionary where each item contains links to both its predecessor and its successor. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + most recently used + least recently used + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Head + Tail + + + + + + + + + + + + + + + + + + diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/.rspec b/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/Gemfile b/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/lru_cache.rb b/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/lru_cache.rb new file mode 100644 index 00000000..3b861c67 --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/lru_cache.rb @@ -0,0 +1,78 @@ +class Node + attr_accessor :data, :key, :next_node, :prev_node + + def initialize(data = nil, key = nil, next_node = nil, prev_node = nil) + @data = data + @key = key + @next_node = next_node + @prev_node = prev_node + end +end + +class DoublyLinkedList + attr_reader :head, :tail + + def initialize(head = nil, tail = nil) + @head = head + @tail = tail + end + + # ADD THE NODE TO THE HEAD OF THE LIST + def add_head(node) + + end + + # REMOVE THE TAIL NODE FROM THE LIST + # AND RETURN IT + def remove_tail + + end + + # REMOVE THE GIVEN NODE FROM THE LIST + # AND THEN RETURN IT + def remove_node(node) + + end + + # MOVE THE GIVEN NODE FROM ITS LOCATION TO THE HEAD + # OF THE LIST + def move_node_to_head(node) + + end +end + +class LRUCache + attr_reader :limit, :size + + def initialize(limit = 10) + @limit = limit + @size = 0 + @hash = {} + @list = DoublyLinkedList.new + end + + # RETRIEVE THE NODE FROM THE CACHE USING THE KEY + # IF THE NODE IS IN THE CACHE, MOVE IT TO THE HEAD OF THE LIST AND RETURN IT + # OTHERWISE RETURN -1 + def get(key) + + end + + # ADD THE GIVEN KEY AND VALUE TO THE CACHE + # IF THE CACHE ALREADY CONTAINS THE KEY, UPDATE ITS VALUE AND MOVE IT TO + # THE HEAD OF THE LIST + # IF THE CACHE DOESN'T CONTAIN THE KEY, ADD IT TO THE CACHE AND PLACE IT + # AT THE HEAD OF THE LIST + # IF THE CACHE IS FULL, REMOVE THE LEAST RECENTLY USED ITEM BEFORE ADDING + # THE NEW DATA TO THE CACHE + def put(key, value) + + end +end + +if __FILE__ == $PROGRAM_NAME + # Don't forget to add your own tests! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/spec/lru_cache_spec.rb b/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/spec/lru_cache_spec.rb new file mode 100644 index 00000000..9021ab25 --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/spec/lru_cache_spec.rb @@ -0,0 +1,110 @@ +require "./lru_cache" + +describe "LRUCache" do + it "correctly creates and updates a cache with a limit of 1 item" do + cache = LRUCache.new(1) + + found = cache.get("test") + + expect(found).to be -1 + + key = "cake" + val = "cake recipe" + cache.put(key, val) + found = cache.get(key) + + expect(found).to be_a Node + expect(found.data).to eq(val) + + key = "cookies" + val = "cookie recipe" + cache.put(key, val) + found = cache.get(key) + shouldBeGone = cache.get("cake") + + expect(found).to be_a Node + expect(found.data).to eq(val) + expect(shouldBeGone).to eq(-1) + + val = "new cookie recipe" + cache.put(key, val) + found = cache.get(key) + + expect(found).to be_a Node + expect(found.data).to eq(val) + expect(cache.size).to eq(1) + end + + it "correctly creates and updates a cache with a limit of 2 items" do + cache = LRUCache.new(2) + + found = cache.get("test") + + expect(found).to eq(-1) + + key1 = "cake" + val1 = "cake recipe" + cache.put(key1, val1) + found1 = cache.get(key1) + key2 = "cookies" + val2 = "cookie recipe" + cache.put(key2, val2) + found2 = cache.get(key2) + + expect(found1.data).to eq(val1) + expect(found2.data).to eq(val2) + expect(cache.size).to eq(2) + + val1 = "new cake recipe" + cache.put(key1, val1) + val2 = "new cookie recipe" + cache.put(key2, val2) + found1 = cache.get(key1) + found2 = cache.get(key2) + + expect(found1.data).to eq(val1) + expect(found2.data).to eq(val2) + expect(cache.size).to eq(2) + + key3 = "scones" + val3 = "scone recipe" + cache.put(key3, val3) + found3 = cache.get(key3) + found1 = cache.get(key1) + found2 = cache.get(key2) + + expect(found3.data).to eq(val3) + expect(cache.size).to eq(2) + expect(found1).to eq(-1) + expect(found2.data).to eq(val2) + end + + it "correctly creates a cache with many items" do + cache = LRUCache.new(10) + numbers = ["one", "two", "three", "four", "five", "six", "seven", "eight", "nine", "ten", "eleven", "twelve"] + items = numbers.reduce([]) do |accum, num| + obj = {} + obj[num] = "item #{num}" + accum << obj + accum + end + + items.each_with_index do |item, idx| + key = numbers[idx] + val = item[key] + + cache.put(key, val) + end + + expect(cache.size).to eq(10) + expect(cache.get(numbers[0])).to eq(-1) + expect(cache.get(numbers[1])).to eq(-1) + expect(cache.get(numbers[2]).data).to eq(items[2][numbers[2]]) + expect(cache.get(numbers[11]).data).to eq(items[11][numbers[11]]) + + updatedVal = "whatevers" + cache.put(numbers[4], updatedVal) + + expect(cache.get(numbers[4]).data).to eq(updatedVal) + end +end \ No newline at end of file diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/spec/spec_helper.rb b/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/solutions/lru_cache.js b/13-week-10/01-days-3-to-5--build-an-lru-cache/solutions/lru_cache.js new file mode 100644 index 00000000..60c17b3e --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/solutions/lru_cache.js @@ -0,0 +1,192 @@ +class Node { + constructor(data = null, key = null, next = null, prev = null) { + this.data = data; + this.key = key; + this.next = next; + this.prev = prev; + } +} + +class DoublyLinkedList { + constructor(head = null, tail = null) { + this.head = head; + this.tail = tail; + } + + // ADD THE NODE TO THE HEAD OF THE LIST + addHead(node) { + if (!this.head) { + this.head = node; + this.tail = node; + return; + } + + this.head.prev = node; + node.next = this.head; + this.head = node; + this.head.prev = null; + } + + // REMOVE THE TAIL NODE FROM THE LIST + // AND RETURN IT + removeTail() { + if (!this.tail) { + return this.tail; + } + + const oldTail = this.tail; + this.tail = this.tail.prev; + + if (this.tail) { + this.tail.next = null; + } else { + this.head = this.tail; + } + + return oldTail; + } + + // REMOVE THE GIVEN NODE FROM THE LIST + // AND THEN RETURN IT + removeNode(node) { + switch (node) { + case this.tail: + this.removeTail(); + break; + case this.head: + // this is a good candidate for a helper method! + this.head = node.next; + + if (this.head) { + this.head.prev = null; + } + + if (!this.head || !this.head.next) { + this.tail = this.head; + } + + break; + default: + if (node.prev) { + node.prev.next = node.next; + } + + if (node.next) { + node.next.prev = node.prev; + } + } + + return node; + } + + // MOVE THE GIVEN NODE FROM ITS LOCATION TO THE HEAD + // OF THE LIST + moveNodeToHead(node) { + const removed = this.removeNode(node); + + this.addHead(removed); + } +} + +class LRUCache { + constructor(limit = 10) { + this.limit = limit; + this.size = 0; + this.hash = {}; + this.list = new DoublyLinkedList(); + } + + // RETRIEVE THE NODE FROM THE CACHE USING THE KEY + // IF THE NODE IS IN THE CACHE, MOVE IT TO THE HEAD OF THE LIST AND RETURN IT + // OTHERWISE RETURN -1 + get(key) { + const found = this.hash[key]; + + if (found) { + this.list.moveNodeToHead(found); + return found; + } + + return -1; + } + + // ADD THE GIVEN KEY AND VALUE TO THE CACHE + // IF THE CACHE ALREADY CONTAINS THE KEY, UPDATE ITS VALUE AND MOVE IT TO + // THE HEAD OF THE LIST + // IF THE CACHE DOESN'T CONTAIN THE KEY, ADD IT TO THE CACHE AND PLACE IT + // AT THE HEAD OF THE LIST + // IF THE CACHE IS FULL, REMOVE THE LEAST RECENTLY USED ITEM BEFORE ADDING + // THE NEW DATA TO THE CACHE + put(key, value) { + const found = this.hash[key]; + + if (found) { + found.data = value; + this.list.moveNodeToHead(found); + return; + } + + if (this.limit === this.size) { + const tail = this.list.removeTail(); + delete this.hash[tail.key]; + --this.size; + } + + this._addEntry(key, value); + } + + _addEntry(key, value) { + const node = new Node(value, key); + this.list.addHead(node); + this.hash[key] = node; + ++this.size; + } +} + +if (require.main === module) { + let list = new DoublyLinkedList(); + + list.addHead(new Node(1)); + + console.log("List has same node as head and tail"); + console.log(list.head === list.tail); + console.log(list.head.data === 1); + + list.removeNode(list.head); + + console.log("List is empty"); + console.log(list.head === null && list.tail === null); + + list.addHead(new Node(1)); + list.addHead(new Node(2)); + + console.log("List head is 2 and tail is 1"); + console.log(list.head.data === 2 && list.tail.data === 1); + + list.removeTail(); + console.log("Can remove tail"); + console.log(list.head === list.tail && list.head.data === 2); + + list.moveNodeToHead(list.head); + console.log("Can move head to head"); + console.log(list.head === list.tail && list.head.data === 2); + + list.addHead(new Node(3)); + list.moveNodeToHead(list.tail); + console.log("Can move tail to head"); + console.log(list.head.data === 2 && list.tail.data === 3); + + list.addHead(new Node(1)); + list.moveNodeToHead(list.head.next); + console.log("Can move middle to head"); + console.log(list.head.data === 2 && list.tail.data === 3); +} + +module.exports = { + Node, + DoublyLinkedList, + LRUCache +}; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/13-week-10/01-days-3-to-5--build-an-lru-cache/solutions/lru_cache.rb b/13-week-10/01-days-3-to-5--build-an-lru-cache/solutions/lru_cache.rb new file mode 100644 index 00000000..6f34cb3f --- /dev/null +++ b/13-week-10/01-days-3-to-5--build-an-lru-cache/solutions/lru_cache.rb @@ -0,0 +1,178 @@ +class Node + attr_accessor :data, :key, :next_node, :prev_node + + def initialize(data = nil, key = nil, next_node = nil, prev_node = nil) + @data = data + @key = key + @next_node = next_node + @prev_node = prev_node + end +end + +class DoublyLinkedList + attr_reader :head, :tail + + def initialize(head = nil, tail = nil) + @head = head + @tail = tail + end + + # ADD THE NODE TO THE HEAD OF THE LIST + def add_head(node) + if @head.nil? + @head = node + @tail = node + return + end + + @head.prev_node = node + node.next_node = @head + @head = node + @head.prev_node = nil + end + + # REMOVE THE TAIL NODE FROM THE LIST + # AND RETURN IT + def remove_tail + return @tail if @tail.nil? + + old_tail = @tail + @tail = @tail.prev_node + + unless @tail.nil? + @tail.next_node = nil + else + @head = @tail + end + + old_tail + end + + # REMOVE THE GIVEN NODE FROM THE LIST + # AND THEN RETURN IT + def remove_node(node) + case node + when @tail + remove_tail + when @head + # this is a good candidate for a helper method! + @head = node.next_node + @head.prev_node = nil unless @head.nil? + @tail = @head if @head.nil? || @head.next_node.nil? + else + node.prev_node.next_node = node.next_node unless node.prev_node.nil? + node.next_node.prev_node = node.prev_node unless node.next_node.nil? + end + + node + end + + # MOVE THE GIVEN NODE FROM ITS LOCATION TO THE HEAD + # OF THE LIST + def move_node_to_head(node) + removed = remove_node(node) + + add_head(removed) + end +end + +class LRUCache + attr_reader :limit, :size + + def initialize(limit = 10) + @limit = limit + @size = 0 + @hash = {} + @list = DoublyLinkedList.new + end + + # RETRIEVE THE NODE FROM THE CACHE USING THE KEY + # IF THE NODE IS IN THE CACHE, MOVE IT TO THE HEAD OF THE LIST AND RETURN IT + # OTHERWISE RETURN -1 + def get(key) + found = @hash[key] + + if found + @list.move_node_to_head(found) + return found + end + + -1 + end + + # ADD THE GIVEN KEY AND VALUE TO THE CACHE + # IF THE CACHE ALREADY CONTAINS THE KEY, UPDATE ITS VALUE AND MOVE IT TO + # THE HEAD OF THE LIST + # IF THE CACHE DOESN'T CONTAIN THE KEY, ADD IT TO THE CACHE AND PLACE IT + # AT THE HEAD OF THE LIST + # IF THE CACHE IS FULL, REMOVE THE LEAST RECENTLY USED ITEM BEFORE ADDING + # THE NEW DATA TO THE CACHE + def put(key, value) + found = @hash[key] + + if found + found.data = value + @list.move_node_to_head(found) + return + end + + if @limit === @size + tail = @list.remove_tail + @hash.delete(tail.key) + @size -= 1 + end + + add_entry(key, value) + end + + private + + def add_entry(key, value) + node = Node.new(value, key) + @list.add_head(node) + @hash[key] = node + @size += 1 + end +end + +if __FILE__ == $PROGRAM_NAME + # Don't forget to add your own tests! + list = DoublyLinkedList.new + list.add_head(Node.new(1)) + + puts "List has same node as head and tail" + puts list.head === list.tail + puts list.head.data === 1 + + list.remove_node(list.head) + + puts "List is empty" + puts list.head === nil && list.tail === nil + + list.add_head(Node.new(1)) + list.add_head(Node.new(2)) + + puts "List head is 2 and tail is 1" + puts list.head.data === 2 && list.tail.data === 1 + + list.remove_tail + puts "Can remove tail" + puts list.head === list.tail && list.head.data === 2 + + list.move_node_to_head(list.head) + puts "Can move head to head" + puts list.head === list.tail && list.head.data === 2 + + list.add_head(Node.new(3)) + list.move_node_to_head(list.tail) + puts "Can move tail to head" + puts list.head.data === 2 && list.tail.data === 3 + + list.add_head(Node.new(1)) + list.move_node_to_head(list.head.next_node) + puts "Can move middle to head" + puts list.head.data === 2 && list.tail.data === 3 +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/14-week-11/00-day-1--what-is-a-graph-/.gitignore b/14-week-11/00-day-1--what-is-a-graph-/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/14-week-11/00-day-1--what-is-a-graph-/README.md b/14-week-11/00-day-1--what-is-a-graph-/README.md new file mode 100644 index 00000000..5016f179 --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/README.md @@ -0,0 +1,182 @@ +# Day 1: What Is a Graph? + +![friends network](./friends.jpg) + +If you're familiar with trees, such as the binary tree, then we've got good news for you! You're already familiar with graphs, because they're very similar! + +A graph is a data structure that represents how different points or objects are connected to one another. In the image above, we have a graph showing how different people are connected. For example, we see that Anne is connected to Bob, Bob is connected to Carl, and so on. We can also see that Diana is not directly connected to Elisa. + +## Why Use Graphs? + +Graphs can help us find the shortest distance between two points or determine if two points are even connected. Dijkstra's algorithm, for example, uses a graph to find the shortest distance between two points, and is still used today to figure out how to get from point A to point B on a map. And Facebook uses a graph database instead of a relational database because it performs better for their use case, which is all about representing relationships between people, places, and other things. + +There are other use cases for graphs, but we'll let you Google those. + +## Key Terms + +- Node / vertex: A point on the graph, similar to a node in a tree +- Edge: A connection or path between two points, which we can visualize as a line connecting two nodes, such as the line/edge from Anne to Bob in the image at the top of this README +- Adjacency: Two nodes or vertices are adjacent if they are connected by an edge (e.g. Anne and Bob are adjacent) +- Path: A sequence of edges between two points, similar to plotting a route from your favorite bakery to your home (a path from Anne to Carl: Anne -> Bob -> Carl) + +## Types of Graphs + +There are several different types of graphs. We'll mention a few here: + +- Undirected graph: The edges do not point in any specific direction (e.g. Anne can point to Bob and Bob can point back to Anne) +- Directed graph: Each edge is uni-directional (similar to a Linked List, e.g. Anne can point to Bob, but Bob cannot point back to Anne) +- Weighted graph: Each edge has a cost associated with it (e.g. if Elisa wants to talk to Diana, she can ask Carl or Anne to make that connection, but if Elisa's relationship with Carl is on the rocks, she might weight that more heavily than going through Anne, with whom she is best friends) + +You don't need to memorize all of this, nor will we be building all of these graphs. We just want you to be aware of them in case you decide to look further into graphs (there are more though!). For example, if you wanted to learn more about Dijkstra's algorithm, you might want to look into weighted graphs. We'll be focusing on undirected graphs. + +## How to Represent a Graph + +We can use a number of different underlying data structures to create a graph. For example, we can build a graph from a tree data structure, similar to a Linked List or Binary Tree. Or we can use a hash (object in JS, dictionary in Python...) or a multi-dimensional array as the underlying data structure. In this challenge, we'll be creating our own Graph class using a hash/object as the underlying data structure. The key will be to maintain the rules of the graph, or as we prefer to say its "graphiness". + +We suggest you read more about the different methods for representing a graph. Try Googling "adjacency matrix" to get yourself started. + +## Our Graph + +Today we'll be creating a Graph class that uses a hash/object as the underlying data structure. It'll be an undirected graph consisting of adjacency lists. In other words, each key in the hash/object will have a value that is a set. Each set will be a list of adjacent nodes. Let's take another look at our friends image and then see what it might look like as a graph: + +![friends network](./friends.jpg) + +``` +{ + Anne: [Bob, Elisa, Diana], + Bob: [Anne, Diana, Carl], + Elisa: [Anne, Carl], + Carl: [Bob, Elisa, Diana], + Diana: [Anne, Bob, Carl] +} +``` + +## Implement a Graph Class + +Let's see if we can gain a better understanding of graphs by building a Graph class. + +Before we dive in, let's set up some rules: + +- Every vertex in the graph has a corresponding key in the Hash/object being used to store the graph +- All of a vertex's adjacent vertices are listed in an adjacency list stored as the value for that key: `{ a: {"b", "c", "d"} }` +- All values in an adjacency list are unique - there can be no repeats +- The graph consists only of vertices that are connected to other vertices, e.g. there cannot be a key with an empty adjacency list unless there is only one vertex in the entire graph + - This means we are making a connected graph: one in which there is a path from any vertex to another vertex. Be aware that it is possible to make a disconnected graph. + - OK: `{ a: {} }` or `{ a: { "b" }, b: { "a" } }` + - Not OK: `{ a: {}, b: {}, c: {} }` or `{ a: { "c" }, b: {}, c: { "a" } }` + +### 1. Initialize a New Object: `initialize(paths)` / `constructor(paths)` + +A user should provide an array of paths when instantiating a new object from the Graph class. We'll take care of the array in the next step. For now, simply accept it as an argument and set an instance variable called `graph` to an empty Hash / object. `graph` should be readable on an object instantiated from the class. + +``` +graph = new Graph(paths) +graph.graph +=> {} +``` + +### 2. Create the Graph from the Array + +When a new Graph object is instantiated, it will be initialized using an array of paths. We need to convert this list of paths to a valid graph. A valid graph contains every vertex as a key, and every adjacent vertex as a value in a set that's associated with that key. + +The list of paths is a two-dimensional array, and you may assume that it's always valid. Each path contains a list of connected vertices in the order in which they're connected, i.e. there is an edge between elements that are next to one another in a list: + +``` +[["a", "b", "c"], ["b", "d"]] +// a is adjacent to b +// b is adjacent to a, c and d +// c is adjacent to b +// d is adjacent to b +``` + +``` +paths = [["a", "b", "c"], ["b", "d"]] +graph = new Graph(paths) +graph.graph +=> { + a: { "b" }, + b: { "a", "c", "d" }, + c: { "b" }, + d: { "b" } +} +``` + +_Question: Why do you think we used a set instead of an array to store the adjacency lists?_ + +Once you've got that working, determine what the worst-case time complexity (Big O) is for converting the array of paths to a graph. + +### 3. `is_adjacent(vertex_a, vertex_b)` / `isAdjacent(vertexA, vertexB)` + +Returns `true` if two vertices are adjacent, i.e. they're connected by an edge. Otherwise it returns `false`. + +``` +paths = [["a", "b", "c"], ["b", "d"]] +graph = new Graph(paths) + +graph.is_adjacent("a", "b") +=> true + +graph.is_adjacent("a", "c") +=> false +``` + +Once you've got that working, determine the worst-case time complexity (Big O) for determining if two vertices are adjacent. + +### 4. `add_vertex(vertex, array)` / `addVertex(vertex, array)` + +Add a new vertex to the graph along with its adjacency list. This means that the adjacency lists for existing vertices will also need to be updated. + +``` +paths = [["a", "b", "c"], ["b", "d"]] +graph = new Graph(paths) + +graph.add_vertex("e", ["a", "d"]) +graph.graph +=> { + a: { "b", "e" }, + b: { "a", "c", "d" }, + c: { "b" }, + d: { "b", "e" }, + e: { "a", "d" } +} +``` + +Once you've got that working, determine the worst-case time complexity (Big O) for adding a vertex. + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/14-week-11/00-day-1--what-is-a-graph-/friends.jpg b/14-week-11/00-day-1--what-is-a-graph-/friends.jpg new file mode 100644 index 00000000..7dffbef5 Binary files /dev/null and b/14-week-11/00-day-1--what-is-a-graph-/friends.jpg differ diff --git a/14-week-11/00-day-1--what-is-a-graph-/javascript/graph.js b/14-week-11/00-day-1--what-is-a-graph-/javascript/graph.js new file mode 100644 index 00000000..80c55e77 --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/javascript/graph.js @@ -0,0 +1,52 @@ +class Graph { + constructor(paths) { + + } + + isAdjacent(vertexA, vertexB) { + + } + + // array is an adjacency list + addVertex(vertex, array) { + + } +} + +if (require.main === module) { + // add your own tests in here + let graph = new Graph([]); + + console.log("Expecting: {}"); + console.log(graph.graph); + + console.log(""); + + graph = new Graph([["a", "b", "c"], ["b", "d"]]); + + console.log('Expecting: { a: { "b" }, b: { "a", "c", "d" }, c: { "b" }, d: { "b" }}'); + console.log(graph.graph); + + console.log(""); + + console.log("Expecting: true"); + console.log(graph.isAdjacent("a", "b")); + + console.log(""); + + console.log("Expecting: false"); + console.log(graph.isAdjacent("a", "c")); + + console.log(""); + + graph.addVertex("e", ["a", "d"]); + console.log('Expecting: { a: { "b", "e" }, b: { "a", "c", "d" }, c: { "b" }, d: { "b", "e" }, e: { "a", "d" } }'); + console.log(graph.graph); + + console.log("") +} + +module.exports = Graph; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/14-week-11/00-day-1--what-is-a-graph-/javascript/package.json b/14-week-11/00-day-1--what-is-a-graph-/javascript/package.json new file mode 100644 index 00000000..9d4300a7 --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "graph", + "version": "1.0.0", + "description": "graph", + "main": "graph.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/14-week-11/00-day-1--what-is-a-graph-/javascript/tests/graph.test.js b/14-week-11/00-day-1--what-is-a-graph-/javascript/tests/graph.test.js new file mode 100644 index 00000000..cba0beb0 --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/javascript/tests/graph.test.js @@ -0,0 +1,67 @@ +const Graph = require("../graph"); + +describe("Graph", () => { + const graph = () => new Graph([["a", "b", "c"], ["b", "d"]]); + const result = () => ({ a: new Set(["b"]), b: new Set(["a", "c", "d"]), c: new Set(["b"]), d: new Set(["b"]) }); + + describe("constructor(paths)", () => { + it("can be initialized with an empty list of paths", () => { + expect(new Graph([]).graph).toEqual({}); + }); + + it("can be initialized with a single vertex in a single path", () => { + expect(new Graph([["a"]]).graph).toEqual({ a: new Set }); + }); + + it("creates a valid graph from a list of paths", () => { + expect(graph().graph).toEqual(result()); + }); + }); + + describe("isAdjacent(vertexA, vertexB)", () => { + it("returns true when two vertices are adjacent", () => { + expect(graph().isAdjacent("a", "b")).toBe(true); + }); + + it("returns false when two vertices are not adjacent", () => { + expect(graph().isAdjacent("a", "c")).toBe(false); + }); + }); + + describe("addVertex(vertex, array)", () => { + it("adds the new vertex to the graph with its adjacency list as a set as its value", () => { + const myGraph = graph(); + myGraph.addVertex("e", ["a", "d"]); + + expect(myGraph.graph["e"]).toEqual(new Set(["a", "d"])); + }); + + it("updates the existing vertices adjacency lists with the new vertex when adjacent", () => { + const myGraph = graph(); + myGraph.addVertex("e", ["a", "d"]); + + const myResult = result(); + myResult["a"].add("e"); + myResult["d"].add("e"); + myResult["e"] = new Set(["a", "d"]); + + expect(myGraph.graph).toEqual(myResult); + }); + + it("adds new vertices to the graph when they're present in the adjacency list", () => { + const adjacency = ["a", "d", "f", "g"]; + const myGraph = graph(); + + myGraph.addVertex("e", adjacency); + + const myResult = result(); + myResult["a"].add("e"); + myResult["d"].add("e"); + myResult["f"] = new Set(["e"]); + myResult["g"] = new Set(["e"]); + myResult["e"] = new Set(adjacency); + + expect(myGraph.graph).toEqual(myResult); + }); + }); +}); diff --git a/14-week-11/00-day-1--what-is-a-graph-/ruby/.rspec b/14-week-11/00-day-1--what-is-a-graph-/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/14-week-11/00-day-1--what-is-a-graph-/ruby/Gemfile b/14-week-11/00-day-1--what-is-a-graph-/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/14-week-11/00-day-1--what-is-a-graph-/ruby/graph.rb b/14-week-11/00-day-1--what-is-a-graph-/ruby/graph.rb new file mode 100644 index 00000000..4d7d4679 --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/ruby/graph.rb @@ -0,0 +1,48 @@ +require 'set' + +class Graph + def initialize(paths) + end + + def is_adjacent(vertex_a, vertex_b) + end + + # array is an adjacency list + def add_vertex(vertex, array) + end +end + +if __FILE__ == $PROGRAM_NAME + graph = Graph.new([]) + + puts "Expecting: {}" + puts graph.graph + + puts + + graph = Graph.new([["a", "b", "c"], ["b", "d"]]) + + puts 'Expecting: { a: { "b" }, b: { "a", "c", "d" }, c: { "b" }, d: { "b" }}' + puts graph.graph + + puts + + # Don't forget to add your own! + + puts "Expecting: true" + puts graph.is_adjacent("a", "b") + + puts + + puts "Expecting: false" + puts graph.is_adjacent("a", "c") + + puts + + graph.add_vertex("e", ["a", "d"]) + puts 'Expecting: { a: { "b", "e" }, b: { "a", "c", "d" }, c: { "b" }, d: { "b", "e" }, e: { "a", "d" } }' + puts graph.graph +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/14-week-11/00-day-1--what-is-a-graph-/ruby/spec/graph_spec.rb b/14-week-11/00-day-1--what-is-a-graph-/ruby/spec/graph_spec.rb new file mode 100644 index 00000000..707a7dbc --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/ruby/spec/graph_spec.rb @@ -0,0 +1,60 @@ +require "./graph" + +RSpec.describe "Graph" do + let(:graph) { Graph.new([["a", "b", "c"], ["b", "d"]]) } + let(:result) { { "a" => Set.new(["b"]), "b" => Set.new(["a", "c", "d"]), "c" => Set.new(["b"]), "d" => Set.new(["b"]) } } + + describe "#initialize(paths)" do + it "can be initialized with an empty list of paths" do + expect(Graph.new([]).graph).to eq({}) + end + + it "can be initialized with a single vertex in a single path" do + expect(Graph.new([["a"]]).graph).to eq({ "a" => Set.new }) + end + + it "creates a valid graph from a list of paths" do + expect(graph.graph).to eq(result) + end + end + + describe "is_adjacent(vertex_a, vertex_b)" do + it "returns true when two vertices are adjacent" do + expect(graph.is_adjacent("a", "b")).to be true + end + + it "returns false when two vertices are not adjacent" do + expect(graph.is_adjacent("a", "c")).to be false + end + end + + describe "add_vertex(vertex, array)" do + it "adds the new vertex to the graph with its adjacency list as a set as its value" do + graph.add_vertex("e", ["a", "d"]) + + expect(graph.graph["e"]).to eq(Set.new(["a", "d"])) + end + + it "updates the existing vertices adjacency lists with the new vertex when adjacent" do + graph.add_vertex("e", ["a", "d"]) + + result["a"].add("e") + result["d"].add("e") + result["e"] = Set.new(["a", "d"]) + + expect(graph.graph).to eq(result) + end + + it "adds new vertices to the graph when they're present in the adjacency list" do + adjacency = ["a", "d", "f", "g"] + graph.add_vertex("e", adjacency) + result["a"].add("e") + result["d"].add("e") + result["f"] = Set.new(["e"]) + result["g"] = Set.new(["e"]) + result["e"] = Set.new(adjacency) + + expect(graph.graph).to eq(result) + end + end +end \ No newline at end of file diff --git a/14-week-11/00-day-1--what-is-a-graph-/ruby/spec/spec_helper.rb b/14-week-11/00-day-1--what-is-a-graph-/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/14-week-11/00-day-1--what-is-a-graph-/solutions/graph.js b/14-week-11/00-day-1--what-is-a-graph-/solutions/graph.js new file mode 100644 index 00000000..45d7d61a --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/solutions/graph.js @@ -0,0 +1,136 @@ +class Graph { + constructor(paths) { + this.graph = paths.reduce((graph, path) => this.constructor.populate(graph, path), {}); + } + + isAdjacent(vertexA, vertexB) { + return this.graph[vertexA].has(vertexB); + } + + addVertex(vertex, array) { + this.graph[vertex] = new Set(array); + + array.forEach((adjacency) => { + if (this.graph[adjacency] === undefined) { + this.graph[adjacency] = new Set(); + } + + this.graph[adjacency].add(vertex); + }); + } + + // helper function for building graph from a path + static populate(graph, path) { + return path.reduce((graph, vertex, idx) => { + graph[vertex] = graph[vertex] || new Set(); + const nextVertex = path[idx + 1]; + + if (nextVertex !== undefined) { + graph[vertex].add(nextVertex); + graph[nextVertex] = graph[nextVertex] || new Set(); + graph[nextVertex].add(vertex); + } + + return graph; + }, graph); + } +} + +if (require.main === module) { + // add your own tests in here + let graph = new Graph([]); + + console.log("Expecting: {}"); + console.log(graph.graph); + + console.log(""); + + graph = new Graph([["a", "b", "c"], ["b", "d"]]); + + console.log('Expecting: { a: { "b" }, b: { "a", "c", "d" }, c: { "b" }, d: { "b" }}'); + console.log(graph.graph); + + console.log(""); + + console.log("Expecting: true"); + console.log(graph.isAdjacent("a", "b")); + + console.log(""); + + console.log("Expecting: false"); + console.log(graph.isAdjacent("a", "c")); + + console.log(""); + + graph.addVertex("e", ["a", "d"]); + console.log('Expecting: { a: { "b", "e" }, b: { "a", "c", "d" }, c: { "b" }, d: { "b", "e" }, e: { "a", "d" } }'); + console.log(graph.graph); + + console.log("") + + graph = new Graph([["a"]]); + + console.log("Expecting: { a: {} }"); + console.log(graph.graph); + + console.log(""); + + graph.addVertex("d", ["a", "b", "c"]); + console.log("Expecting: { a: { 'd' }, b: { 'd' }, c: { 'd' }, d: { 'a', 'b', 'c' } }"); + console.log(graph.graph); +} + +module.exports = Graph; + +// Please add your pseudocode to this file +/******************************************************************************************* + * // this method converts a single path into a graph + * function populate(graph, path): + * iterate over the path with its index and element included: + * if element is not a key in graph: + * add element as key and initialize value to empty set + * + * if next element exists: + * add the next element to the element's set + * + * if next element isn't a key in graph: + * add next element as key in graph and initialize value to empty set + * + * add element to next element's set + * + * return graph + * + * + * function isAdjacent(vertex_a, vertex_b): + * get set from graph associated with key vertex_a + * return true if vertex_b is in set, else false + * + * function add_vertex(vertex, array): + * add vertex as key in graph with value initialized to a set made from array + * + * iterate over each element in array: + * if element is not in graph: + * add element as key with value initialized to an empty set + * add vertex to each element's set in graph + * ******************************************************************************************/ + + // And a written explanation of your solution +/******************************************************************************************** + * I can create the graph from an array of paths by creating a key in the graph object for every + * element in a path. If the element, or vertex, is not in the graph, I initialize it to an empty + * set. Since the next vertex in the path is adjacent to the current vertex, I can have them both + * point to each other, or rather put each vertex in the other's adjacency list. + * + * To check if two vertices are adjacent, I just need to grab one of their sets (values in the graph), + * and check if the other vertex is in its adjacency list. If it is return true, otherwise false. + * + * To add a vertex, I add it as a key to the graph and set its value to a new set made from the array + * (adjacency list) that's passed into the method. Next, I iterate over the array to ensure each vertex + * is added to the graph as a key if it's not already in there. I also need to ensure that each vertex + * in the adjacency list points back to the added vertex. + * *********************************************************************************************/ + + // Time complexity for: + // creating a new graph: O(n) since we must iterate over the entire paths array + // checking if adjacent: O(1) accessing an element in a set is constant time as is accessing a key in a hash + // adding a vertex: O(n) since we must iterate over the entire adjacency list \ No newline at end of file diff --git a/14-week-11/00-day-1--what-is-a-graph-/solutions/graph.rb b/14-week-11/00-day-1--what-is-a-graph-/solutions/graph.rb new file mode 100644 index 00000000..1833bde7 --- /dev/null +++ b/14-week-11/00-day-1--what-is-a-graph-/solutions/graph.rb @@ -0,0 +1,136 @@ +require 'set' + +class Graph + attr_reader :graph + + def initialize(paths) + @graph = paths.reduce({}) { |graph, path| self.class.populate(graph, path) } + end + + def is_adjacent(vertex_a, vertex_b) + @graph[vertex_a].include?(vertex_b) + end + + def add_vertex(vertex, array) + @graph[vertex] = Set.new(array) + + array.each do |adjacency| + @graph[adjacency] = Set.new if @graph[adjacency].nil? + @graph[adjacency].add(vertex) + end + end + + def self.populate(graph, path) + path.each_with_index.reduce(graph) do |graph, (vertex, idx)| + graph[vertex] = Set.new if graph[vertex].nil? + next_vertex = path[idx + 1] + + return graph if next_vertex.nil? + + graph[vertex].add(next_vertex) + graph[next_vertex] = Set.new if graph[next_vertex].nil? + graph[next_vertex].add(vertex) + graph + end + end +end + +if __FILE__ == $PROGRAM_NAME + graph = Graph.new([]) + + puts "Expecting: {}" + puts graph.graph + + puts + + graph = Graph.new([["a", "b", "c"], ["b", "d"]]) + + puts 'Expecting: { a: { "b" }, b: { "a", "c", "d" }, c: { "b" }, d: { "b" }}' + puts graph.graph + + puts + + # Don't forget to add your own! + + puts "Expecting: true" + puts graph.is_adjacent("a", "b") + + puts + + puts "Expecting: false" + puts graph.is_adjacent("a", "c") + + puts + + graph.add_vertex("e", ["a", "d"]) + puts 'Expecting: { a: { "b", "e" }, b: { "a", "c", "d" }, c: { "b" }, d: { "b", "e" }, e: { "a", "d" } }' + puts graph.graph + + puts + + graph = Graph.new([["a"]]) + + puts "Expecting: { a: {} }" + puts graph.graph + + puts + + puts "" + + graph.add_vertex("d", ["a", "b", "c"]) + puts "Expecting: { a: { 'd' }, b: { 'd' }, c: { 'd' }, d: { 'a', 'b', 'c' } }" + puts graph.graph +end + +# Please add your pseudocode to this file +########################################################################################################### + # # this method converts a single path into a graph + # function populate(graph, path): + # iterate over the path with its index and element included: + # if element is not a key in graph: + # add element as key and initialize value to empty set + # + # if next element exists: + # add the next element to the element's set + # + # if next element isn't a key in graph: + # add next element as key in graph and initialize value to empty set + # + # add element to next element's set + # + # return graph + # + # + # function isAdjacent(vertex_a, vertex_b): + # get set from graph associated with key vertex_a + # return true if vertex_b is in set, else false + # + # function add_vertex(vertex, array): + # add vertex as key in graph with value initialized to a set made from array + # + # iterate over each element in array: + # if element is not in graph: + # add element as key with value initialized to an empty set + # add vertex to each element's set in graph +########################################################################################################### + + # And a written explanation of your solution +########################################################################################################### + # I can create the graph from an array of paths by creating a key in the graph object for every + # element in a path. If the element, or vertex, is not in the graph, I initialize it to an empty + # set. Since the next vertex in the path is adjacent to the current vertex, I can have them both + # point to each other, or rather put each vertex in the other's adjacency list. + # + # To check if two vertices are adjacent, I just need to grab one of their sets (values in the graph), + # and check if the other vertex is in its adjacency list. If it is return true, otherwise false. + # + # To add a vertex, I add it as a key to the graph and set its value to a new set made from the array + # (adjacency list) that's passed into the method. Next, I iterate over the array to ensure each vertex + # is added to the graph as a key if it's not already in there. I also need to ensure that each vertex + # in the adjacency list points back to the added vertex. +########################################################################################################### + + # Time complexity for: + # creating a new graph: O(n) since we must iterate over the entire paths array + # checking if adjacent: O(1) accessing an element in a set is constant time as is accessing a key in a hash + # adding a vertex: O(n) since we must iterate over the entire adjacency list diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/.gitignore b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/README.md b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/README.md new file mode 100644 index 00000000..f0017b5f --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/README.md @@ -0,0 +1,87 @@ +# Days 2 to 3: Depth-First Graph Traversal + +![fork in the road](./fork_road.jpg) + +For this challenge we'll be determining whether there is a path from vertex A to vertex B in a graph. We'll do this using depth-first traversal, which is typically a recursive process. During a depth-first traversal, we explore a path completely before going down another path. It's like exploring a fork in a road on foot: you walk all the way down one side of the fork, then walk back up to where you started, and then go down the other side before walking back up again. + +## Our Graph + +For this challenge, we'll be using a directed disconnected graph stored in a hash/object where each key maps to an adjacency list. Let's break down that word jumble: + +- Directed graph: Starting at any vertex, we can travel in only one direction, e.g. we can go from vertex A to vertex B but not from B to A. This is similar to a Linked List. The difference, however, is that we can connect any two vertices we like, e.g. A -> B -> C -> A, and a vertex may be connected to many other vertices. +- Disconnected: It is possible that certain vertices might be unreachable from another vertex. E.g. we can go from A to B, but there is no path from A to C. +- Adjacency list: A list of vertices that share an edge with a given vertex, i.e. there is a direct connection from the given vertex to every vertex in the adjacency list. + +Example: + +![friends graph](./graph.jpg) + +``` +graph = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby"], + dave: [] +} +``` + +In this graph, jan can travel to carl via john, but jambaby can't visit anyone and neither can dave. Similarly, no one can visit dave, who's just floating in space all by their lonesome. Sorry dave. + +## Implement isPath(graph, vertexA, vertexB) / is_path(graph, vertex_a, vertex_b) + +Your method will be given a graph stored in a hash/object. Each key represents a vertex in the graph, and each value will be a list of adjacent vertices stored in an array. The method will also be passed two vertices. Your job is to determine whether there is a path between them. If there is, return `true`, otherwise `false`. The vertices given will always be in the graph, and the graph will always be valid and contain data. + +``` +graph = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby"], + dave: [] +} + +is_path(graph, "jan", "carl") +=> true + +is_path(graph, "jan", "dave") +=> false +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/fork_road.jpg b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/fork_road.jpg new file mode 100644 index 00000000..1860ebeb Binary files /dev/null and b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/fork_road.jpg differ diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/graph.jpg b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/graph.jpg new file mode 100644 index 00000000..62a62beb Binary files /dev/null and b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/graph.jpg differ diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/javascript/graph_dfs.js b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/javascript/graph_dfs.js new file mode 100644 index 00000000..b3b7294e --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/javascript/graph_dfs.js @@ -0,0 +1,27 @@ +function isPath(graph, vertexA, vertexB) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + let graph = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby"], + dave: [] + }; + + console.log("Expecting: true"); + console.log(isPath(graph, "jan", "carl")); + + console.log(""); + + console.log("Expecting: false"); + console.log(isPath(graph, "jan", "dave")); +} + +module.exports = isPath; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/javascript/package.json b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/javascript/package.json new file mode 100644 index 00000000..5b7abd3a --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "graph_dfs", + "version": "1.0.0", + "description": "depth-first graph traversal", + "main": "graph_dfs.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/javascript/tests/graph_dfs.test.js b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/javascript/tests/graph_dfs.test.js new file mode 100644 index 00000000..1fd03046 --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/javascript/tests/graph_dfs.test.js @@ -0,0 +1,61 @@ +const isPath = require("../graph_dfs"); + +const simpleGraph = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby"], + dave: [] +}; + +const lessSimpleGraph = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby", "dave"], + dave: ["jan"], + mittens: [] +}; + +const complexGraph = { + jan: ["john", "jambaby", "malala"], + john: ["carl"], + jambaby: [], + carl: ["jambaby", "dave", "martin"], + dave: ["jan"], + mittens: [], + martin: ["mittens"], + malala: ["dave", "carl", "martin", "pirate"], + pirate: ["shiba", "inu"], + shiba: [], + inu: [] +}; + +describe("isPath", () => { + it("returns true when there is a path between two vertices", () => { + expect(isPath(simpleGraph, "jan", "carl")).toBe(true); + expect(isPath(simpleGraph, "jan", "jambaby")).toBe(true); + }); + + it("returns false when there is NO path between two vertices", () => { + expect(isPath(simpleGraph, "jan", "dave")).toBe(false); + expect(isPath(simpleGraph, "dave", "jambaby")).toBe(false); + expect(isPath(simpleGraph, "jan", "jan")).toBe(false); + }); + + it("returns true when there is a path from A to B and A and B are the same vertex", () => { + expect(isPath(lessSimpleGraph, "jan", "jan")).toBe(true); + }); + + it("returns false when there is NO path from A to B and A and B are the same vertex", () => { + expect(isPath(simpleGraph, "jan", "jan")).toBe(false); + }); + + it("can handle coming across looping paths", () => { + expect(isPath(lessSimpleGraph, "jan", "mittens")).toBe(false); + }); + + it("can handle slightly complex paths", () => { + expect(isPath(complexGraph, "carl", "inu")).toBe(true); + }); +}); diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/.rspec b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/Gemfile b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/graph_dfs.rb b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/graph_dfs.rb new file mode 100644 index 00000000..978ae94b --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/graph_dfs.rb @@ -0,0 +1,26 @@ +def is_path(graph, vertex_a, vertex_b) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + graph = { + jan: [:john, :jambaby], + john: [:carl], + jambaby: [], + carl: [:jambaby], + dave: [] + } + + puts "Expecting: true" + puts is_path(graph, :jan, :carl) + + puts + + puts "Expecting: false" + puts is_path(graph, :jan, :dave) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/spec/graph_dfs_spec.rb b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/spec/graph_dfs_spec.rb new file mode 100644 index 00000000..6002385a --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/spec/graph_dfs_spec.rb @@ -0,0 +1,61 @@ +require "./graph_dfs" + +RSpec.describe "is_path" do + simple_graph = { + jan: [:john, :jambaby], + john: [:carl], + jambaby: [], + carl: [:jambaby], + dave: [] + } + + less_simple_graph = { + jan: [:john, :jambaby], + john: [:carl], + jambaby: [], + carl: [:jambaby, :dave], + dave: [:jan], + mittens: [] + } + + complex_graph = { + jan: [:john, :jambaby, :malala], + john: [:carl], + jambaby: [], + carl: [:jambaby, :dave, :martin], + dave: [:jan], + mittens: [], + martin: [:mittens], + malala: [:dave, :carl, :martin, :pirate], + pirate: [:shiba, :inu], + shiba: [], + inu: [] + } + + it "returns true when there is a path between two vertices" do + expect(is_path(simple_graph, :jan, :carl)).to be true + expect(is_path(simple_graph, :jan, :jambaby)).to be true + end + + it "returns false when there is NO path between two vertices" do + expect(is_path(simple_graph, :jan, :dave)).to be false + expect(is_path(simple_graph, :dave, :jambaby)).to be false + expect(is_path(simple_graph, :jan, :jan)).to be false + end + + it "returns true when there is a path from A to B and A and B are the same vertex" do + expect(is_path(less_simple_graph, :jan, :jan)).to be true + end + + it "returns false when there is NO path from A to B and A and B are the same vertex" do + expect(is_path(simple_graph, :jan, :jan)).to be false + end + + it "can handle coming across looping paths" do + expect(is_path(less_simple_graph, :jan, :mittens)).to be false + end + + it "can handle slightly complex paths" do + expect(is_path(complex_graph, :carl, :inu)).to be true + end +end \ No newline at end of file diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/spec/spec_helper.rb b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/solutions/graph_dfs.js b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/solutions/graph_dfs.js new file mode 100644 index 00000000..86849471 --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/solutions/graph_dfs.js @@ -0,0 +1,136 @@ +function isPath(graph, vertexA, vertexB, visited = new Set()) { + visited.add(vertexA); + + for (const vertex of graph[vertexA]) { + if (vertex === vertexB) { + return true; + } + + if (!visited.has(vertex)) { + if (isPath(graph, vertex, vertexB, visited)) { + return true; + } + } + } + + return false; +} + +if (require.main === module) { + // add your own tests in here + let graph = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby"], + dave: [] + }; + + console.log("Expecting: true"); + console.log(isPath(graph, "jan", "carl")); + + console.log(""); + + console.log("Expecting: false"); + console.log(isPath(graph, "jan", "dave")); + + console.log(""); + + console.log("Expecting: false"); + console.log(isPath(graph, "dave", "jambaby")); + + console.log(""); + + console.log("Expecting: false"); + console.log(isPath(graph, "jan", "jan")); + + graph = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby", "dave"], + dave: ["jan"], + mittens: [] + }; + + console.log(""); + + console.log("Expecting: true"); + console.log(isPath(graph, "jan", "jan")); + + console.log(""); + + console.log("Expecting: false"); + console.log(isPath(graph, "jan", "mittens")); + + graph = { + jan: ["john", "jambaby", "malala"], + john: ["carl"], + jambaby: [], + carl: ["jambaby", "dave", "martin"], + dave: ["jan"], + mittens: [], + martin: ["mittens"], + malala: ["dave", "carl", "martin", "pirate"], + pirate: ["shiba", "inu"], + shiba: [], + inu: [] + }; + + console.log(""); + + console.log("Expecting: true"); + console.log(isPath(graph, "carl", "inu")); +} + +module.exports = isPath; + +// Please add your pseudocode to this file +/****************************************************************************************** + * function is_path(graph, vertex_a, vertex_b, visited): + * add vertex_a to visited + * + * iterate over each vertex in vertex_a adjacency list: + * return true if vertex == vertex_b + * + * if vertex is not in visited: + * initialize variable result to value returned from is_path(graph, vertex, vertex_b, visited) + * return true if result is true + * + * return false + * *****************************************************************************************/ + +// And a written explanation of your solution +/****************************************************************************************** + * I started by thinking of my base case, which is that the algorithm should return true if + * vertex B is in vertex A's adjacency list. I'm not checking for inclusion in the other direction + * from B to A because this is a directed graph. Since it is possible for there to be loops in + * the graph, I need to also store a list of vertices that have been visited. This prevents me + * from creating a stack overflow / infinite loop. + * + * I chose a Set instead of an Array to track the visited vertices because checking if a vertex + * has been visited already would then have an O(1) lookup time, whereas an Array would be O(n). + * The trade-off is that the Set takes up more space in memory. + * + * I chose to iterate over the adjacency list using a loop that can be broken out of, as opposed + * to one that offers no breaking mechanism. I did this because I want iteration to stop as soon + * as a path has been found. At that time I can simply return true. + * + * I decided the best place for the base case was inside of the loop that iterates over the + * adjacency list. That way if I find a match for vertex B, I can return true and not recurse. + * This also ensures that if I'm looking for a path from vertex A back to vertex A it will only + * return true if there is actually a path. If I put the base case above the loop, it'd return true + * in all cases when searching for such a loop. + * + * On each iteration, I also check if the vertex in the adjacency list has not been visited. If it hasn't + * I recurse with the current vertex that's being iterated over, vertex B, and the + * visited Set. If the recursive call returns true, that value will travel up the stack and + * continue returning true until it reaches the top and the whole method returns true. + * + * If all reachable vertices are visited and vertex B is not found, the algorithm will return + * false once it exits the iteration in each frame. + * + * The time complexity for the worst case occurs when we must visit every vertex and every + * adjacency list. I think this can be boiled down to O(n) essentially, but in reality it's + * a combination of all the vertices and all of the edges, since they'd all be visited, O(V + E). + * *****************************************************************************************/ diff --git a/14-week-11/01-days-2-to-3--depth-first-graph-traversal/solutions/graph_dfs.rb b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/solutions/graph_dfs.rb new file mode 100644 index 00000000..049576cf --- /dev/null +++ b/14-week-11/01-days-2-to-3--depth-first-graph-traversal/solutions/graph_dfs.rb @@ -0,0 +1,131 @@ +require "set" + +def is_path(graph, vertex_a, vertex_b, visited = Set.new) + visited.add(vertex_a) + idx = 0 + + while idx < graph[vertex_a].length + vertex = graph[vertex_a][idx] + idx += 1 + return true if vertex == vertex_b + next if visited.include?(vertex) + return true if is_path(graph, vertex, vertex_b, visited) + end + + false +end + +if __FILE__ == $PROGRAM_NAME + graph = { + jan: [:john, :jambaby], + john: [:carl], + jambaby: [], + carl: [:jambaby], + dave: [] + } + + puts "Expecting: true" + puts is_path(graph, :jan, :carl) + + puts + + puts "Expecting: false" + puts is_path(graph, :jan, :dave) + + # Don't forget to add your own! + puts "Expecting: false" + puts is_path(graph, :dave, :jambaby) + + puts "" + + puts "Expecting: false" + puts is_path(graph, :jan, :jan) + + graph = { + jan: [:john, :jambaby], + john: [:carl], + jambaby: [], + carl: [:jambaby, :dave], + dave: [:jan], + mittens: [] + } + + puts "" + + puts "Expecting: true" + puts is_path(graph, :jan, :jan) + + puts "" + + puts "Expecting: false" + puts is_path(graph, :jan, :mittens) + + graph = { + jan: [:john, :jambaby, :malala], + john: [:carl], + jambaby: [], + carl: [:jambaby, :dave, :martin], + dave: [:jan], + mittens: [], + martin: [:mittens], + malala: [:dave, :carl, :martin, :pirate], + pirate: [:shiba, :inu], + shiba: [], + inu: [] + } + + puts "" + + puts "Expecting: true" + puts is_path(graph, :carl, :inu) +end + +# Please add your pseudocode to this file +############################################################################################################# +# function is_path(graph, vertex_a, vertex_b, visited): +# add vertex_a to visited +# +# iterate over each vertex in vertex_a adjacency list: +# return true if vertex == vertex_b +# +# if vertex is not in visited: +# initialize variable result to value returned from is_path(graph, vertex, vertex_b, visited) +# return true if result is true +# +# return false +############################################################################################################# + +# And a written explanation of your solution +############################################################################################################# +# I started by thinking of my base case, which is that the algorithm should return true if +# vertex B is in vertex A's adjacency list. I'm not checking for inclusion in the other direction +# from B to A because this is a directed graph. Since it is possible for there to be loops in +# the graph, I need to also store a list of vertices that have been visited. This prevents me +# from creating a stack overflow / infinite loop. +# +# I chose a Set instead of an Array to track the visited vertices because checking if a vertex +# has been visited already would then have an O(1) lookup time, whereas an Array would be O(n). +# The trade-off is that the Set takes up more space in memory. +# +# I chose to iterate over the adjacency list using a loop that can be broken out of, as opposed +# to one that offers no breaking mechanism. I did this because I want iteration to stop as soon +# as a path has been found. At that time I can simply return true. +# +# I decided the best place for the base case was inside of the loop that iterates over the +# adjacency list. That way if I find a match for vertex B, I can return true and not recurse. +# This also ensures that if I'm looking for a path from vertex A back to vertex A it will only +# return true if there is actually a path. If I put the base case above the loop, it'd return true +# in all cases when searching for such a loop. +# +# On each iteration, I also check if the vertex in the adjacency list has not been visited. If it hasn't +# I recurse with the current vertex that's being iterated over, vertex B, and the +# visited Set. If the recursive call returns true, that value will travel up the stack and +# continue returning true until it reaches the top and the whole method returns true. +# +# If all reachable vertices are visited and vertex B is not found, the algorithm will return +# false once it exits the iteration in each frame. +# +# The time complexity for the worst case occurs when we must visit every vertex and every +# adjacency list. I think this can be boiled down to O(n) essentially, but in reality it's +# a combination of all the vertices and all of the edges, since they'd all be visited, O(V + E). +############################################################################################################# diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/.gitignore b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/README.md b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/README.md new file mode 100644 index 00000000..80140400 --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/README.md @@ -0,0 +1,75 @@ +# Days 4 to 5: Breadth-first Graph Traversal + +![Friends graph](./graph.jpg) + +For this challenge we are going to find the length of the shortest distance between two friends (or vertices, rather, which represent friends). To achieve this we'll need to perform a breadth-first search (BFS). Unlike depth-first search (DFS), which travels all the way down a path, a BFS considers all adjacent vertices on each iteration. If we were to do a BFS on the friends graph to find the shortest distance from jan to carl, it might check the friends like so: + +1. Is john == carl? +2. Is jambaby == carl? +3. Is carl == carl? + +In other words, the algorithm checks the adjacent vertices first (the closest ones to the starting vertex), and then checks the vertices adjacent to those. Since it always checks the closest vertices first, and the furthest last, it's perfect for finding the shortest distance between two vertices! + +You should know that this type of search can also help us find the shortest path between two points! + +## find_distance(graph, vertex_a, vertex_b) / findDistance(graph, vertexA, vertexB) + +Use BFS to return the shortest distance from vertex A to B. If there is no path, return -1. The graph and vertices will always be valid. The graph will always contain vertices and vertices A and B will always be in the graph. We will be using a directed disconnected graph. + +If you are asked to find the distance from a vertex back to itself, i.e. a cycle, return the distance of the cycle, rather than 0. Be aware that there might not be a cycle. + +``` +graph = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby"], + dave: [] +} + +find_distance(graph, "jan", "carl") +=> 2 + +find_distance(graph, "dave", "carl") +=> -1 +``` + +_Hint: A BFS is normally implemented as an iterative algorithm that uses a queue to track which vertices to visit next. Be careful of cycles!_ + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/graph.jpg b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/graph.jpg new file mode 100644 index 00000000..62a62beb Binary files /dev/null and b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/graph.jpg differ diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/javascript/find_distance.js b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/javascript/find_distance.js new file mode 100644 index 00000000..eb99ac7b --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/javascript/find_distance.js @@ -0,0 +1,27 @@ +function findDistance(graph, vertexA, vertexB) { + // type your code here +} + +if (require.main === module) { + // add your own tests in here + const graph = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby"], + dave: [] + }; + + console.log("Expecting: 2"); + console.log(findDistance(graph, "jan", "carl")); + + console.log(""); + + console.log("Expecting: -1"); + console.log(findDistance(graph, "dave", "carl")); +} + +module.exports = findDistance; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/javascript/package.json b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/javascript/package.json new file mode 100644 index 00000000..cad6013b --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "find_distance", + "version": "1.0.0", + "description": "find shortest distance between vertices", + "main": "find_distance.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/javascript/tests/find_distance.test.js b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/javascript/tests/find_distance.test.js new file mode 100644 index 00000000..572d982a --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/javascript/tests/find_distance.test.js @@ -0,0 +1,38 @@ +const findDistance = require("../find_distance"); + +describe("findDistance", () => { + const graphSmall = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby"], + dave: [] + }; + + const graphMed = { + jan: ["cranberry", "jamboree"], + john: ["jambaby"], + jambaby: ["jan", "cranberry"], + carl: [], + dave: ["john", "carl"], + cranberry: [], + hamtaro: ["jambaby", "dave"], + jamboree: ["carl", "john"] + }; + + it("returns the shortest distance between two vertices when there's a path between them", () => { + expect(findDistance(graphSmall, "jan", "carl")).toEqual(2); + expect(findDistance(graphMed, "dave", "carl")).toEqual(1); + }); + + it("returns -1 when there is no path between vertices", () => { + expect(findDistance(graphSmall, "dave", "carl")).toEqual(-1); + expect(findDistance(graphSmall, "jambaby", "carl")).toEqual(-1); + expect(findDistance(graphMed, "jamboree", "hamtaro")).toEqual(-1); + }); + + it("returns the correct distance when detecting cycles", () => { + expect(findDistance(graphSmall, "jan", "jan")).toEqual(-1); + expect(findDistance(graphMed, "jan", "jan")).toEqual(4); + }); +}); diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/.rspec b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/.rspec new file mode 100644 index 00000000..c99d2e73 --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/Gemfile b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/find_distance.rb b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/find_distance.rb new file mode 100644 index 00000000..99b3aa6e --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/find_distance.rb @@ -0,0 +1,26 @@ +def find_distance(graph, vertex_a, vertex_b) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + graph = { + jan: [:john, :jambaby], + john: [:carl], + jambaby: [], + carl: [:jambaby], + dave: [] + } + + puts "Expecting: 2" + puts find_distance(graph, :jan, :carl) + + puts + + puts "Expecting: -1" + puts find_distance(graph, :dave, :carl) + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/spec/find_distance_spec.rb b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/spec/find_distance_spec.rb new file mode 100644 index 00000000..d6a60499 --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/spec/find_distance_spec.rb @@ -0,0 +1,38 @@ +require "./find_distance" + +RSpec.describe "find_distance" do + graph_small = { + jan: [:john, :jambaby], + john: [:carl], + jambaby: [], + carl: [:jambaby], + dave: [] + } + + graph_med = { + jan: [:cranberry, :jamboree], + john: [:jambaby], + jambaby: [:jan, :cranberry], + carl: [], + dave: [:john, :carl], + cranberry: [], + hamtaro: [:jambaby, :dave], + jamboree: [:carl, :john] + } + + it "returns the shortest distance between two vertices when there's a path between them" do + expect(find_distance(graph_small, :jan, :carl)).to eq 2 + expect(find_distance(graph_med, :dave, :carl)).to eq 1 + end + + it "returns -1 when there is no path between vertices" do + expect(find_distance(graph_small, :dave, :carl)).to eq -1 + expect(find_distance(graph_small, :jambaby, :carl)).to eq -1 + expect(find_distance(graph_med, :jamboree, :hamtaro)).to eq -1 + end + + it "returns the correct distance when detecting cycles" do + expect(find_distance(graph_small, :jan, :jan)).to eq -1 + expect(find_distance(graph_med, :jan, :jan)).to eq 4 + end +end \ No newline at end of file diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/spec/spec_helper.rb b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/solutions/find_distance.js b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/solutions/find_distance.js new file mode 100644 index 00000000..03d3b33c --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/solutions/find_distance.js @@ -0,0 +1,124 @@ +function findDistance(graph, vertexA, vertexB) { + const queue = [[0, vertexA]]; + const visited = new Set(); + + while (queue.length > 0) { + const [distance, vertex] = queue.shift(); + + if (vertex === vertexB && distance > 0) { + return distance; + } + + if (visited.has(vertex)) { + continue; + } + + graph[vertex].forEach((adj) => { + queue.push([distance + 1, adj]); + }); + + visited.add(vertex); + } + + return -1; +} + +if (require.main === module) { + // add your own tests in here + let graph = { + jan: ["john", "jambaby"], + john: ["carl"], + jambaby: [], + carl: ["jambaby"], + dave: [] + }; + + console.log("Expecting: 2"); + console.log(findDistance(graph, "jan", "carl")); + + console.log(""); + + console.log("Expecting: -1"); + console.log(findDistance(graph, "dave", "carl")); + + console.log(""); + + console.log("Expecting: -1"); + console.log(findDistance(graph, "jan", "jan")); + + console.log(""); + + console.log("Expecting: -1"); + console.log(findDistance(graph, "jambaby", "carl")); + + graph = { + jan: ["cranberry", "jamboree"], + john: ["jambaby"], + jambaby: ["jan", "cranberry"], + carl: [], + dave: ["john", "carl"], + cranberry: [], + hamtaro: ["jambaby", "dave"], + jamboree: ["carl", "john"] + }; + + console.log(""); + + console.log("Expecting: 4"); + console.log(findDistance(graph, "jan", "jan")); + + console.log(""); + + console.log("Expecting: 1"); + console.log(findDistance(graph, "dave", "carl")); + + console.log(""); + + console.log("Expecting: -1"); + console.log(findDistance(graph, "jamboree", "hamtaro")); +} + +module.exports = findDistance; + +// Please add your pseudocode to this file +/************************************************************************************ + * function find_distance(graph, vertex_a, vertex_b): + * initialize an array queue to store the starting vertex with a distance of 0 + * initialize an empty set to store the visited vertices + * + * while the queue has vertices: + * remove the first vertex from the queue + * + * if the vertex equals the target vertex_b and the distance is more than 0: + * return distance + * + * if the vertex has not been visited: + * iterate over its adjacency list: + * add each vertex and distance + 1 to the queue + * + * add the vertex to visited + * + * return -1 +*************************************************************************************/ + + // And a written explanation of your solution +/************************************************************************************ + * To find the distance from one vertex to another we need to always keep track of + * which vertices require visitation, which have been visited so we can avoid infinite + * loops, and the distance of each vertex from the start at any given time. This is why + * our queue is a 2-dimensional array: it needs to store the vertex and its distance. + * + * At the beginning, we add the starting vertex with a distance of 0 to the queue. Next + * we enter the loop and remove the first vertex from the queue. We can then check if + * this vertex is the target vertex and if we've actually traveled the graph by checking + * that the distance is more than 0. If so, we can return the distance. + * + * Next, we check if we've already visited this vertex. If we have, it means we've already + * added its adjacent vertices to the queue at some point, and we should not do that + * again so as to avoid infinite loops. If it has been visited, we can just continue + * on to the next iteration. If it hasn't been visited, we add each adjacent vertex + * to the queue with the correct distance, which is distance + 1. Lastly, we add the + * vertex to visited. + * + * If we visit every reachable vertex without finding the target, we return -1. +*************************************************************************************/ \ No newline at end of file diff --git a/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/solutions/find_distance.rb b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/solutions/find_distance.rb new file mode 100644 index 00000000..ac923a48 --- /dev/null +++ b/14-week-11/02-days-4-to-5--breadth-first-graph-traversal/solutions/find_distance.rb @@ -0,0 +1,120 @@ +require "set" + +def find_distance(graph, vertex_a, vertex_b) + queue = [[0, vertex_a]] + visited = Set.new + + until queue.empty? + distance, vertex = queue.shift + + return distance if vertex == vertex_b && distance > 0 + next if visited.include? vertex + + graph[vertex].each do |adj| + queue << [distance + 1, adj] + end + + visited.add(vertex) + end + + -1 +end + +if __FILE__ == $PROGRAM_NAME + graph = { + jan: [:john, :jambaby], + john: [:carl], + jambaby: [], + carl: [:jambaby], + dave: [] + } + + puts "Expecting: 2" + puts find_distance(graph, :jan, :carl) + + puts + + puts "Expecting: -1" + puts find_distance(graph, :dave, :carl) + + puts + + puts "Expecting: -1" + puts find_distance(graph, :jan, :jan) + + puts + + puts "Expecting: -1" + puts find_distance(graph, :jambaby, :carl) + + # Don't forget to add your own! + + graph = { + jan: [:cranberry, :jamboree], + john: [:jambaby], + jambaby: [:jan, :cranberry], + carl: [], + dave: [:john, :carl], + cranberry: [], + hamtaro: [:jambaby, :dave], + jamboree: [:carl, :john] + } + + puts + + puts "Expecting: 4" + puts find_distance(graph, :jan, :jan) + + puts + + puts "Expecting: 1" + puts find_distance(graph, :dave, :carl) + + puts + + puts "Expecting: -1" + puts find_distance(graph, :jamboree, :hamtaro) +end + +# Please add your pseudocode to this file +########################################################################################### + # function find_distance(graph, vertex_a, vertex_b): + # initialize an array queue to store the starting vertex with a distance of 0 + # initialize an empty set to store the visited vertices + # + # while the queue has vertices: + # remove the first vertex from the queue + # + # if the vertex equals the target vertex_b and the distance is more than 0: + # return distance + # + # if the vertex has not been visited: + # iterate over its adjacency list: + # add each vertex and distance + 1 to the queue + # + # add the vertex to visited + # + # return -1 +########################################################################################### + + # And a written explanation of your solution +########################################################################################### + # To find the distance from one vertex to another we need to always keep track of + # which vertices require visitation, which have been visited so we can avoid infinite + # loops, and the distance of each vertex from the start at any given time. This is why + # our queue is a 2-dimensional array: it needs to store the vertex and its distance. + # + # At the beginning, we add the starting vertex with a distance of 0 to the queue. Next + # we enter the loop and remove the first vertex from the queue. We can then check if + # this vertex is the target vertex and if we've actually traveled the graph by checking + # that the distance is more than 0. If so, we can return the distance. + # + # Next, we check if we've already visited this vertex. If we have, it means we've already + # added its adjacent vertices to the queue at some point, and we should not do that + # again so as to avoid infinite loops. If it has been visited, we can just continue + # on to the next iteration. If it hasn't been visited, we add each adjacent vertex + # to the queue with the correct distance, which is distance + 1. Lastly, we add the + # vertex to visited. + # + # If we visit every reachable vertex without finding the target, we return -1. +########################################################################################### diff --git a/15-week-12/00-days-1-to-2--convert-html-to-a-graph/.gitignore b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/15-week-12/00-days-1-to-2--convert-html-to-a-graph/README.md b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/README.md new file mode 100644 index 00000000..1b0b3d09 --- /dev/null +++ b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/README.md @@ -0,0 +1,70 @@ +# Days 1 to 2: Convert an HTML Table to a Graph + +For this challenge, you'll be provided with a string of HTML representing a table of friends. You'll need to convert the table to an undirected graph. The final graph may be disconnected, i.e. there might not be a path from each person to every other person. The string will always contain a valid HTML table with entries in it, and the table will always have the same format: a header row followed by rows which contain the friends data, with two cells per row. + +Note that the returned graph is a Hash/Object and the adjacency lists are arrays. Also, although the table stored in the string in the example below is spaced with carriage returns, the string provided to your function will not be. Instead, it'll always be a continuous string like so: `"..."`. Spacing is shown below to help you understand how the data is represented in the table. + +``` +friends = "
+ + + + + + + + + + + + +
PersonFriends
FredJane, Carol, Anesh, Xi
CarolFred, Anesh, Janelle
" + +table_to_graph(friends) +=> { + "Fred": ["Jane", "Carol", "Anesh", "Xi"], + "Jane": ["Fred"], + "Carol": ["Fred", "Anesh", "Janelle"], + "Anesh": ["Fred", "Carol"], + "Xi": ["Fred"], + "Janelle": ["Carol"] +} +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/15-week-12/00-days-1-to-2--convert-html-to-a-graph/javascript/package.json b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/javascript/package.json new file mode 100644 index 00000000..a2ca36ad --- /dev/null +++ b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "table-to-graph", + "version": "1.0.0", + "description": "convert html table to a graph", + "main": "table_to_graph.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/15-week-12/00-days-1-to-2--convert-html-to-a-graph/javascript/table_to_graph.js b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/javascript/table_to_graph.js new file mode 100644 index 00000000..96935661 --- /dev/null +++ b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/javascript/table_to_graph.js @@ -0,0 +1,35 @@ +function tableToGraph(friends) { + // type your code here +} + +if (require.main === module) { + function printResults(obj) { + for (const key in obj) { + console.log(`${key}: ${obj[key]}`); + } + } + + // add your own tests in here + const friends = "
PersonFriends
FredJane, Carol, Anesh, Xi
CarolFred, Anesh, Janelle
"; + const result = { + Fred: ["Jane", "Carol", "Anesh", "Xi"], + Jane: ["Fred"], + Carol: ["Fred", "Anesh", "Janelle"], + Anesh: ["Fred", "Carol"], + Xi: ["Fred"], + Janelle: ["Carol"] + }; + + console.log("Expecting: "); + console.log(printResults(result)); + console.log(""); + console.log("Got: "); + console.log(printResults(tableToGraph(friends))); + + console.log(""); +} + +module.exports = tableToGraph; + +// Please add your pseudocode to this file +// And a written explanation of your solution diff --git a/15-week-12/00-days-1-to-2--convert-html-to-a-graph/javascript/tests/table_to_graph.test.js b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/javascript/tests/table_to_graph.test.js new file mode 100644 index 00000000..161d8c3c --- /dev/null +++ b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/javascript/tests/table_to_graph.test.js @@ -0,0 +1,88 @@ +const tableToGraph = require("../table_to_graph"); + +describe("tableToGraph()", () => { + test("converts an HTML table to a friends graph which is a Hash with array adjacency lists", () => { + let friends = "
PersonFriends
FredJane, Carol, Anesh, Xi
CarolFred, Anesh, Janelle
"; + let result = { + "Fred": ["Jane", "Carol", "Anesh", "Xi"], + "Jane": ["Fred"], + "Carol": ["Fred", "Anesh", "Janelle"], + "Anesh": ["Fred", "Carol"], + "Xi": ["Fred"], + "Janelle": ["Carol"] + }; + let graph = tableToGraph(friends); + + expect(Object.keys(graph).sort()).toEqual(Object.keys(result).sort()); + + for (const key in result) { + expect(graph[key].sort()).toEqual(result[key].sort()); + } + + friends = "
PersonFriends
GremlinJambaby
"; + result = { + "Gremlin": ["Jambaby"], + "Jambaby": ["Gremlin"] + }; + graph = tableToGraph(friends); + + expect(Object.keys(graph).sort()).toEqual(Object.keys(result).sort()); + + for (const key in result) { + expect(graph[key].sort()).toEqual(result[key].sort()); + } + }); + + test("returns the correct result when the resulting graph is disconnected", () => { + let friends = "
PersonFriends
GremlinJambaby, Carbonara, Hamtaro, Crain
BatsCustard, Colonel
MalteserJambaby, Hamtaro, Bartelby, Viper
ViperMalteser, Munchkin, Baconini, Bartelby
"; + let result = { + "Gremlin": ["Jambaby", "Carbonara", "Hamtaro", "Crain"], + "Jambaby": ["Gremlin", "Malteser"], + "Carbonara": ["Gremlin"], + "Hamtaro": ["Gremlin", "Malteser"], + "Crain": ["Gremlin"], + "Bats": ["Custard", "Colonel"], + "Custard": ["Bats"], + "Colonel": ["Bats"], + "Malteser": ["Jambaby", "Hamtaro", "Bartelby", "Viper"], + "Bartelby": ["Malteser", "Viper"], + "Viper": ["Malteser", "Munchkin", "Baconini", "Bartelby"], + "Munchkin": ["Viper"], + "Baconini": ["Viper"] + }; + graph = tableToGraph(friends); + + expect(Object.keys(graph).sort()).toEqual(Object.keys(result).sort()); + + for (const key in result) { + expect(graph[key].sort()).toEqual(result[key].sort()); + } + }); + + test("returns the correct result when a person has no friends :(", () => { + let friends = "
PersonFriends
Gremlin
"; + let result = { + "Gremlin": [] + }; + let graph = tableToGraph(friends); + + expect(Object.keys(graph).sort()).toEqual(Object.keys(result).sort()); + + for (const key in result) { + expect(graph[key].sort()).toEqual(result[key].sort()); + } + + friends = "
PersonFriends
Gremlin
Baconini
"; + result = { + "Gremlin": [], + "Baconini": [] + }; + graph = tableToGraph(friends); + + expect(Object.keys(graph).sort()).toEqual(Object.keys(result).sort()); + + for (const key in result) { + expect(graph[key].sort()).toEqual(result[key].sort()); + } + }); +}); \ No newline at end of file diff --git a/15-week-12/00-days-1-to-2--convert-html-to-a-graph/ruby/Gemfile b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/ruby/Gemfile new file mode 100644 index 00000000..3b693468 --- /dev/null +++ b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' diff --git a/15-week-12/00-days-1-to-2--convert-html-to-a-graph/ruby/spec/table_to_graph_spec.rb b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/ruby/spec/table_to_graph_spec.rb new file mode 100644 index 00000000..96f05664 --- /dev/null +++ b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/ruby/spec/table_to_graph_spec.rb @@ -0,0 +1,89 @@ +require "./table_to_graph" + +RSpec.describe "table_to_graph" do + it "converts an HTML table to a friends graph which is a Hash with array adjacency lists" do + friends = "
PersonFriends
FredJane, Carol, Anesh, Xi
CarolFred, Anesh, Janelle
" + result = { + "Fred" => ["Jane", "Carol", "Anesh", "Xi"], + "Jane" => ["Fred"], + "Carol" => ["Fred", "Anesh", "Janelle"], + "Anesh" => ["Fred", "Carol"], + "Xi" => ["Fred"], + "Janelle" => ["Carol"] + } + graph = table_to_graph(friends) + + expect(graph.keys).to match_array(result.keys) + + result.each do |k, v| + expect(graph[k]).to match_array(result[k]) + end + + friends = "
PersonFriends
GremlinJambaby
" + result = { + "Gremlin" => ["Jambaby"], + "Jambaby" => ["Gremlin"] + } + + graph = table_to_graph(friends) + + expect(graph.keys).to match_array(result.keys) + + result.each do |k, v| + expect(graph[k]).to match_array(result[k]) + end + end + + it "returns the correct result when the resulting graph is disconnected" do + friends = "
PersonFriends
GremlinJambaby, Carbonara, Hamtaro, Crain
BatsCustard, Colonel
MalteserJambaby, Hamtaro, Bartelby, Viper
ViperMalteser, Munchkin, Baconini, Bartelby
" + result = { + "Gremlin" => ["Jambaby", "Carbonara", "Hamtaro", "Crain"], + "Jambaby" => ["Gremlin", "Malteser"], + "Carbonara" => ["Gremlin"], + "Hamtaro" => ["Gremlin", "Malteser"], + "Crain" => ["Gremlin"], + "Bats" => ["Custard", "Colonel"], + "Custard" => ["Bats"], + "Colonel" => ["Bats"], + "Malteser" => ["Jambaby", "Hamtaro", "Bartelby", "Viper"], + "Bartelby" => ["Malteser", "Viper"], + "Viper" => ["Malteser", "Munchkin", "Baconini", "Bartelby"], + "Munchkin" => ["Viper"], + "Baconini" => ["Viper"] + } + graph = table_to_graph(friends) + + expect(graph.keys).to match_array(result.keys) + + result.each do |k, v| + expect(graph[k]).to match_array(result[k]) + end + end + + it "returns the correct result when a person has no friends :(" do + friends = "
PersonFriends
Gremlin
" + result = { + "Gremlin" => [] + } + graph = table_to_graph(friends) + + expect(graph.keys).to match_array(result.keys) + + result.each do |k, v| + expect(graph[k]).to match_array(result[k]) + end + + friends = "
PersonFriends
Gremlin
Baconini
" + result = { + "Gremlin" => [], + "Baconini" => [] + } + graph = table_to_graph(friends) + + expect(graph.keys).to match_array(result.keys) + + result.each do |k, v| + expect(graph[k]).to match_array(result[k]) + end + end +end diff --git a/15-week-12/00-days-1-to-2--convert-html-to-a-graph/ruby/table_to_graph.rb b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/ruby/table_to_graph.rb new file mode 100644 index 00000000..93436420 --- /dev/null +++ b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/ruby/table_to_graph.rb @@ -0,0 +1,32 @@ +def table_to_graph(friends) + # type your code in here +end + +if __FILE__ == $PROGRAM_NAME + def print_results(hash) + hash.each { |key, val| puts "#{key}: #{val}" } + end + + friends = "
PersonFriends
FredJane, Carol, Anesh, Xi
CarolFred, Anesh, Janelle
" + result = { + "Fred" => ["Jane", "Carol", "Anesh", "Xi"], + "Jane" => ["Fred"], + "Carol" => ["Fred", "Anesh", "Janelle"], + "Anesh" => ["Fred", "Carol"], + "Xi" => ["Fred"], + "Janelle" => ["Carol"] + } + + puts "Expecting: " + print_results(result) + puts + puts "Got: " + print_results(table_to_graph(friends)) + + puts + + # Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution diff --git a/15-week-12/00-days-1-to-2--convert-html-to-a-graph/solutions/table_to_graph.js b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/solutions/table_to_graph.js new file mode 100644 index 00000000..47b19a91 --- /dev/null +++ b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/solutions/table_to_graph.js @@ -0,0 +1,156 @@ +function tableToGraph(friends) { + const dataString = friends.slice(55, -18).replace(/<\/tr>/g, ""); + const data = dataString.split(""); + const graph = {}; + + for (let idx = 0; idx < data.length; idx += 2) { + const vertex = data[idx]; + const friendsList = data[idx + 1] !== "" ? data[idx + 1].split(", ") : []; + + graph[vertex] = friendsList; + + friendsList.forEach(friend => { + if (!graph[friend]) { + graph[friend] = []; + } + + if(!graph[friend].includes(vertex)) { + graph[friend].push(vertex); + } + }); + } + + return graph; +} + +if (require.main === module) { + function printResults(obj) { + for (const key in obj) { + console.log(`${key}: ${obj[key]}`); + } + } + // add your own tests in here + let friends = "
PersonFriends
FredJane, Carol, Anesh, Xi
CarolFred, Anesh, Janelle
"; + let result = { + Fred: ["Jane", "Carol", "Anesh", "Xi"], + Jane: ["Fred"], + Carol: ["Fred", "Anesh", "Janelle"], + Anesh: ["Fred", "Carol"], + Xi: ["Fred"], + Janelle: ["Carol"] + }; + + console.log("Expecting: "); + console.log(printResults(result)); + console.log(""); + console.log("Got: "); + console.log(printResults(tableToGraph(friends))); + + console.log(""); + + friends = "
PersonFriends
GremlinJambaby, Carbonara, Hamtaro, Crain
BatsCustard, Colonel
MalteserJambaby, Hamtaro, Bartelby, Viper
ViperMalteser, Munchkin, Baconini, Bartelby
" + result = { + Gremlin: ["Jambaby", "Carbonara", "Hamtaro", "Crain"], + Jambaby: ["Gremlin", "Malteser"], + Carbonara: ["Gremlin"], + Hamtaro: ["Gremlin", "Malteser"], + Crain: ["Gremlin"], + Bats: ["Custard", "Colonel"], + Custard: ["Bats"], + Colonel: ["Bats"], + Malteser: ["Jambaby", "Hamtaro", "Bartelby", "Viper"], + Bartelby: ["Malteser", "Viper"], + Viper: ["Malteser", "Munchkin", "Baconini", "Bartelby"], + Munchkin: ["Viper"], + Baconini: ["Viper"] + }; + + console.log("Expecting: "); + console.log(printResults(result)); + console.log(""); + console.log("Got: "); + console.log(printResults(tableToGraph(friends))); + + console.log(""); + + friends = "
PersonFriends
GremlinJambaby
"; + result = { + Gremlin: ["Jambaby"], + Jambaby: ["Gremlin"] + }; + + console.log("Expecting: "); + console.log(printResults(result)); + console.log(""); + console.log("Got: "); + console.log(printResults(tableToGraph(friends))); + + console.log(""); + + friends = "
PersonFriends
Gremlin
"; + result = { + Gremlin: [] + }; + + console.log("Expecting: "); + console.log(printResults(result)); + console.log(""); + console.log("Got: "); + console.log(printResults(tableToGraph(friends))); + + console.log(""); + + friends = "
PersonFriends
Gremlin
Baconini
"; + result = { + Gremlin: [], + Baconini: [] + }; + + console.log("Expecting: "); + console.log(printResults(result)); + console.log(""); + console.log("Got: "); + console.log(printResults(tableToGraph(friends))); + + console.log(""); +} + +module.exports = tableToGraph; + +// Please add your pseudocode to this file +///////////////////////////////////////////////////////////////////////////////////////////// +// initialize a variable, data_string, and store the parts of the string between the TDs +// initialize data and split data_string's cells into an array, so that the left cell always +// comes just before the right cell +// initialize a Hash/Object called graph +// +// iterate over every other element of data: +// store the friends in an array (friends are the next element in data) +// add the current friend as a key in the graph +// set the value to the friends array +// set each friend as a key in the graph and add the current friend to their adjacency lists +// skipping duplicates +// +// return graph +///////////////////////////////////////////////////////////////////////////////////////////// + +// And a written explanation of your solution +///////////////////////////////////////////////////////////////////////////////////////////// +// From the prompt I know that there will always be at least one friend in the table. However, +// there is no guarantee that that friend will have any friends :( I also know that the +// table will always have the same HTML markup, just with a different number of rows. +// +// I figured that I should first split the table into an array, so that each element of the +// array would hold the friend data in a specific order. For example, for a table with only +// two cells, my array would look like this: ["Jam", "Gran, Minnie, Tabitha"] or [left cell, right cell] +// Now I can iterate over the array and know that every other element is an adjacency list +// for the friend in the element just before it. +// +// When I iterate over the array, I increase the index by 2 each time because of how my +// array is ordered. I set the currently iterated over element as the key in the graph, +// and then split the next element into an array of strings, which then becomes the value +// of the key in the graph (the adjacency list). I then need all of those friends in the +// adjacency list to point back to the original friend/key, but I have to ensure that if +// that friend is already a key in the graph that I don't duplicate any data in their adjacency +// list. +///////////////////////////////////////////////////////////////////////////////////////////// \ No newline at end of file diff --git a/15-week-12/00-days-1-to-2--convert-html-to-a-graph/solutions/table_to_graph.rb b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/solutions/table_to_graph.rb new file mode 100644 index 00000000..41d949cd --- /dev/null +++ b/15-week-12/00-days-1-to-2--convert-html-to-a-graph/solutions/table_to_graph.rb @@ -0,0 +1,148 @@ +def table_to_graph(friends) + data_string = friends[55..-19].gsub(/<\/tr>/, "") + data = data_string.split("") + graph = Hash.new { |h, k| h[k] = [] } + idx = 0 + + while idx < data.length + friends_list = (data[idx + 1] || "").split(", ") + vertex = data[idx] + graph[vertex] = friends_list + + friends_list.each do |friend| + graph[friend] << vertex unless graph[friend].include?(vertex) + end + + idx += 2 + end + + graph +end + +if __FILE__ == $PROGRAM_NAME + def print_results(hash) + hash.each { |key, val| puts "#{key}: #{val}" } + end + + friends = "
PersonFriends
FredJane, Carol, Anesh, Xi
CarolFred, Anesh, Janelle
" + result = { + "Fred" => ["Jane", "Carol", "Anesh", "Xi"], + "Jane" => ["Fred"], + "Carol" => ["Fred", "Anesh", "Janelle"], + "Anesh" => ["Fred", "Carol"], + "Xi" => ["Fred"], + "Janelle" => ["Carol"] + } + + puts "Expecting: " + print_results(result) + puts + puts "Got: " + print_results(table_to_graph(friends)) + + puts + + friends = "
PersonFriends
GremlinJambaby, Carbonara, Hamtaro, Crain
BatsCustard, Colonel
MalteserJambaby, Hamtaro, Bartelby, Viper
ViperMalteser, Munchkin, Baconini, Bartelby
" + result = { + "Gremlin" => ["Jambaby", "Carbonara", "Hamtaro", "Crain"], + "Jambaby" => ["Gremlin", "Malteser"], + "Carbonara" => ["Gremlin"], + "Hamtaro" => ["Gremlin", "Malteser"], + "Crain" => ["Gremlin"], + "Bats" => ["Custard", "Colonel"], + "Custard" => ["Bats"], + "Colonel" => ["Bats"], + "Malteser" => ["Jambaby", "Hamtaro", "Bartelby", "Viper"], + "Bartelby" => ["Malteser", "Viper"], + "Viper" => ["Malteser", "Munchkin", "Baconini", "Bartelby"], + "Munchkin" => ["Viper"], + "Baconini" => ["Viper"] + } + + puts "Expecting: " + print_results(result) + puts + puts "Got: " + print_results(table_to_graph(friends)) + + puts + + friends = "
PersonFriends
GremlinJambaby
" + result = { + "Gremlin" => ["Jambaby"], + "Jambaby" => ["Gremlin"] + } + + puts "Expecting: " + print_results(result) + puts + puts "Got: " + print_results(table_to_graph(friends)) + + puts + + friends = "
PersonFriends
Gremlin
" + result = { + "Gremlin" => [] + } + + puts "Expecting: " + print_results(result) + puts + puts "Got: " + print_results(table_to_graph(friends)) + + puts + + friends = "
PersonFriends
Gremlin
Baconini
" + result = { + "Gremlin" => [], + "Baconini" => [] + } + + puts "Expecting: " + print_results(result) + puts + puts "Got: " + print_results(table_to_graph(friends)) + + puts +end + +# Please add your pseudocode to this file +######################################################################################### +# initialize a variable, data_string, and store the parts of the string between the TDs +# initialize data and split data_string's cells into an array, so that the left cell always +# comes just before the right cell +# initialize a Hash/Object called graph +# +# iterate over every other element of data: +# store the friends in an array (friends are the next element in data) +# add the current friend as a key in the graph +# set the value to the friends array +# set each friend as a key in the graph and add the current friend to their adjacency lists +# skipping duplicates +# +# return graph +######################################################################################### + +# And a written explanation of your solution +######################################################################################### +# From the prompt I know that there will always be at least one friend in the table. However, +# there is no guarantee that that friend will have any friends :( I also know that the +# table will always have the same HTML markup, just with a different number of rows. +# +# I figured that I should first split the table into an array, so that each element of the +# array would hold the friend data in a specific order. For example, for a table with only +# two cells, my array would look like this: ["Jam", "Gran, Minnie, Tabitha"] or [left cell, right cell] +# Now I can iterate over the array and know that every other element is an adjacency list +# for the friend in the element just before it. +# +# When I iterate over the array, I increase the index by 2 each time because of how my +# array is ordered. I set the currently iterated over element as the key in the graph, +# and then split the next element into an array of strings, which then becomes the value +# of the key in the graph (the adjacency list). I then need all of those friends in the +# adjacency list to point back to the original friend/key, but I have to ensure that if +# that friend is already a key in the graph that I don't duplicate any data in their adjacency +# list. +######################################################################################### diff --git a/16-pairing-exercise-4/00-whiteboard-and-calculate-big-o/README.md b/16-pairing-exercise-4/00-whiteboard-and-calculate-big-o/README.md new file mode 100644 index 00000000..e1ee55cc --- /dev/null +++ b/16-pairing-exercise-4/00-whiteboard-and-calculate-big-o/README.md @@ -0,0 +1,95 @@ +# Whiteboard and Calculate Big O + +## Introduction + +For this activity, you and your partner will each choose a problem and solve it +in front of one another, and you will also calculate the time complexity for the +solution. You may select a problem you have already solved or a problem you have +not yet solved. You may complete this activity using an actual whiteboard, if +available, or in the IDE of your choosing, such as VS Code or an online REPL. + +Keep in mind that these challenges tend to make people nervous, so remember to +always be kind, patient, and encouraging. Also be aware that nerves can cause +people to come up with some pretty weird solutions to problems, so remember to +bring your empathy with you! + +Plan to spend 15 minutes in each role. This means you and/or your partner might +not have enough time to finish the solution, and that's OK. If you can +reasonably spend more time on this, you can, but do put a time limit on it. Be +sure to calculate Big O for time complexity even if your solution isn't +complete. If you can, try to reason about and discuss what Big O would be if you +had completed it based on your pseudocode. + +## Instructions for Interviewer + +As the interviewer your job will be to first present the problem. Explain the +challenge to your partner and provide some example test cases. You are not +expected to provide every possible test case or edge case. Instead, provide just +enough detail for the interviewee to understand the problem and ask clarifying +questions. Example: "For this challenge, your function will accept a single +string as input and return it in reverse. So if it were to receive 'cat', it +would return 'tac'." + +You will also need to answer questions. Your partner might ask you to confirm +their understanding of the problem or whether or not they should handle certain +edge cases. If you don't know the answer to a question, it's OK to say "I don't +know" or "I'll let you decide." Sometimes the interviewer doesn't have the +answers. + +Notice when your partner gets stuck and needs a nudge in the right direction. +Provide helpful tips or hints, but don't give away the answer. Ideally, your +partner will ask questions when they get stuck, but if you notice that they're +struggling with something for a little too long, don't be afraid to give a +little nudge. You can also ask in advance if your partner would like a hint +before providing advice. + +When time is up, provide a constructive review of your partner's performance. +Some areas to talk about include: + +- Problem explanation: did your partner explain the problem back to you in their + own words and confirm their understanding before coding? Did they ask + clarifying questions when necessary? +- Testing: did your partner check their understanding against the given test + case/s? Did they write their own? +- Pseudocoding: did your partner explain what they were going to do out loud + before coding and with pseudocode? Did they check their pseudocode against the + test cases? +- Solution: did your partner solve the problem? Was their syntax correct? Did + they handle all of the test cases? Were variables and functions named + appropriately? How readable was the code? +- Openness to feedback: Did they ask for feedback when necessary? Were they + receptive to your feedback? + +Ultimately, it is more important for the interviewer to evaluate the +interviewee's communication during this exercise, so if time is an issue, focus +your review there rather than on the solution itself. When providing feedback, +be specific, so your partner has an opportunity to improve. Also structure your +feedback into two sections. For example, you might talk about what went well and +then what can be improved, rather than mixing them together. + +## Instructions for Interviewee + +As the interviewee, your job will be to solve the problem posed by your partner. +The Interviewer instructions above explain which areas you should focus on in +order to ace your interview. The content in the Welcome section of this course +will also help, so make sure you read it before attempting your first whiteboard +challenge. + +Here are some general tips: + +- Communication is key: explain the problem and your approach to the solution +- Assume you haven't been given all of the information, such as all of the + inputs you need to account for +- Ask for help when you need it and be receptive to feedback +- Don't be afraid to admit when you know there's a better way to do something, + e.g. "I know I'm brute forcing this solution and there's a more efficient way, + but I want to solve it first and then optimize once I know I can solve it." + +When time is up, provide a constructive review for your interviewer. Some areas +to talk about include: + +- Did the interviewer communicate clearly? Could you understand the challenge + and did they answer your questions in a manner that you could understand? +- Did they give you time to solve the problem or explore a possible solution + before providing feedback? In other words, were they patient? +- Was their feedback helpful? diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/.gitignore b/17-week-13/00-day-1-to-2--dynamic-programming/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/README.md b/17-week-13/00-day-1-to-2--dynamic-programming/README.md new file mode 100644 index 00000000..d0cb878e --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/README.md @@ -0,0 +1,60 @@ +# Day 1-2: Dynamic Programming + +## Dynamic Programming + +Dynamic Programming (DP) is an algorithmic technique for solving an optimization problem by breaking it down into simpler subproblems and utilizing the fact that the optimal solution to the overall problem depends upon the optimal solution to its subproblems. + +## Task + +Revisit the Fibonacci series. Go back to your old solutions (iterative and recursive). If the iterative solution keeps a whole array of values, modify it to only keep track of the data it needs. Next, modify the recursive solution to count the number of stack frames. Copy this solution and modify it to use cached values to avoid repeating recursive calls with the same value. Benchmark each version and compare. + +Find the nth element in the Fibonacci series. The Fibonacci sequence starts with a 0 followed by a 1. After that, every value is the sum of the two values preceding it. Here are the first seven values as an example: 0, 1, 1, 2, 3, 5, 8. + +``` +Input: 0 +Output: 0 + +Input: 2 +Output: 1 + +Input: 10 +Output: 55 +``` + +Use the language of your choosing. We've included starter files for some languages where you can pseudocode, explain your solution and code. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/javascript/fibonacci.js b/17-week-13/00-day-1-to-2--dynamic-programming/javascript/fibonacci.js new file mode 100644 index 00000000..438f703d --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/javascript/fibonacci.js @@ -0,0 +1,24 @@ +function fibonacci(num) { +// type your code here +} + +if (require.main === module) { +// add your own tests in here +console.log("Expecting: 0"); +console.log("=>", fibonacci(0)); + +console.log(""); + +console.log("Expecting: 1"); +console.log("=>", fibonacci(2)); + +console.log(""); + +console.log("Expecting: 55"); +console.log("=>", fibonacci(10)); +} + +module.exports = fibonacci; + +// Please add your pseudocode to this file +// And a written explanation of your solution \ No newline at end of file diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/javascript/package.json b/17-week-13/00-day-1-to-2--dynamic-programming/javascript/package.json new file mode 100644 index 00000000..fed2cb53 --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "fibonacci", + "version": "1.0.0", + "description": "find nth value in fibo sequence with DP", + "main": "fibonacci.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/javascript/tests/fibonacci.test.js b/17-week-13/00-day-1-to-2--dynamic-programming/javascript/tests/fibonacci.test.js new file mode 100644 index 00000000..6d35f519 --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/javascript/tests/fibonacci.test.js @@ -0,0 +1,13 @@ +const fibonacci = require('../fibonacci'); + +const fibo = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811]; + +for (let i = 0; i < 10; ++i) { + test(`outputs the correct number in the sequence at index ${i}`, () => { + expect(fibonacci(i)).toBe(fibo[i]); + }); +} + +test('outputs the correct number in the sequence at index 28', () => { + expect(fibonacci(28)).toBe(fibo[28]); +}); \ No newline at end of file diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/ruby/.rspec b/17-week-13/00-day-1-to-2--dynamic-programming/ruby/.rspec new file mode 100644 index 00000000..82b8369c --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper \ No newline at end of file diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/ruby/Gemfile b/17-week-13/00-day-1-to-2--dynamic-programming/ruby/Gemfile new file mode 100644 index 00000000..5ab5cb4e --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' \ No newline at end of file diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/ruby/fibonacci.rb b/17-week-13/00-day-1-to-2--dynamic-programming/ruby/fibonacci.rb new file mode 100644 index 00000000..2021663b --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/ruby/fibonacci.rb @@ -0,0 +1,24 @@ + +def fibonacci(num) + # type your code here +end + +if __FILE__ == $PROGRAM_NAME +puts "Expecting: 0" +puts "=>", fibonacci(0) + +puts + +puts "Expecting: 1" +puts "=>", fibonacci(2) + +puts + +puts "Expecting: 55" +puts "=>", fibonacci(10) + +# Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution \ No newline at end of file diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/ruby/spec/fibonacci_spec.rb b/17-week-13/00-day-1-to-2--dynamic-programming/ruby/spec/fibonacci_spec.rb new file mode 100644 index 00000000..93dd94b0 --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/ruby/spec/fibonacci_spec.rb @@ -0,0 +1,15 @@ +require './fibonacci' + +RSpec.describe '#fibonacci' do + fibo = [0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, 233, 377, 610, 987, 1597, 2584, 4181, 6765, 10946, 17711, 28657, 46368, 75025, 121393, 196418, 317811] + + 10.times do |n| + it "outputs the correct number in the sequence at index #{n}" do + expect(fibonacci(n)).to eq(fibo[n]) + end + end + + it "outputs the correct number at index 28" do + expect(fibonacci(28)).to eq(fibo[28]) + end +end \ No newline at end of file diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/ruby/spec/spec_helper.rb b/17-week-13/00-day-1-to-2--dynamic-programming/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/solutions/fibonacci.js b/17-week-13/00-day-1-to-2--dynamic-programming/solutions/fibonacci.js new file mode 100644 index 00000000..1947520b --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/solutions/fibonacci.js @@ -0,0 +1,43 @@ +let fibs = []; + +function fibonacci(num) { + if (num === 0 || num === 1) return num + if (fibs[num]) return fibs[num] + fibs[num] = fibonacci(num - 1) + fibonacci(num - 2) + return fibs[num] +} + +if (require.main === module) { +// add your own tests in here +console.log("Expecting: 0"); +console.log("=>", fibonacci(0)); + +console.log(""); + +console.log("Expecting: 1"); +console.log("=>", fibonacci(2)); + +console.log(""); + +console.log("Expecting: 55"); +console.log("=>", fibonacci(10)); +} + +module.exports = fibonacci; + +// Please add your pseudocode to this file + +// fib(4) = fib(3) + fib(2) +// fib(2), fib(3) were already saved into mem, so will fib(4) + +// fib(5) = fib(4) + fib(3) +// The previously saved fib(3) and fib(4) will be used to avoid duplicated calculation and call stacks + +// And a written explanation of your solution + +// In a nutshell, DP is a efficient way in which we can use memoziation to cache visited data to faster retrieval later on. + +// This implementation makes use of mem as an array (or hash) to store value of an already computed num. This will greatly reduce the number of call stack and duplicated computation in the call stack. + +// Time complexity O(N), Space O(N) + diff --git a/17-week-13/00-day-1-to-2--dynamic-programming/solutions/fibonacci.rb b/17-week-13/00-day-1-to-2--dynamic-programming/solutions/fibonacci.rb new file mode 100644 index 00000000..4dc746f8 --- /dev/null +++ b/17-week-13/00-day-1-to-2--dynamic-programming/solutions/fibonacci.rb @@ -0,0 +1,42 @@ +Fibs = {} + +def fibonacci(num) + return num if num == 0 || num == 1 + return Fibs[num] if Fibs.keys.include?(num) + + Fibs[num] = fibonacci(num -1) + fibonacci(num - 2) + return Fibs[num] +end + +if __FILE__ == $PROGRAM_NAME +puts "Expecting: 0" +puts "=>", fibonacci(0) + +puts + +puts "Expecting: 1" +puts "=>", fibonacci(2) + +puts + +puts "Expecting: 55" +puts "=>", fibonacci(10) + +# Don't forget to add your own! +end + +# // Please add your pseudocode to this file + +# // fib(4) = fib(3) + fib(2) +# // fib(2), fib(3) were already saved into mem, so will fib(4) + +# // fib(5) = fib(4) + fib(3) +# // The previously saved fib(3) and fib(4) will be used to avoid duplicated calculation and call stacks + +# // And a written explanation of your solution + +# // In a nutshell, DP is a efficient way in which we can use memoziation to cache visited data to faster retrieval later on. + +# // This implementation makes use of mem as an array (or hash) to store value of an already computed num. This will greatly reduce the number of call stack and duplicated computation in the call stack. + +# // Time complexity O(N), Space O(N) \ No newline at end of file diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/.gitignore b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/.gitignore new file mode 100644 index 00000000..2731f409 --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/.gitignore @@ -0,0 +1,4 @@ +Gemfile.lock + +node_modules/ +package-lock.json \ No newline at end of file diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/README.md b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/README.md new file mode 100644 index 00000000..376b6b0f --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/README.md @@ -0,0 +1,64 @@ +# Day 3-5: Add Two Numbers - List Nodes + +You are given two non-empty linked lists representing two non-negative integers. The digits are stored in reverse order, and each of their nodes contains a single digit. Add the two numbers and return the sum as a linked list. + +You may assume the two numbers do not contain any leading zero, except the number 0 itself. + +![example](example.png) + +### Example 1: + +Input: l1 = [2,4,3], l2 = [5,6,4] +Output: [7,0,8] +Explanation: 342 + 465 = 807 + +### Example 2: + +Input: l1 = [0], l2 = [0] +Output: [0] + +### Example 3: + +Input: l1 = [9,9,9,9,9,9,9], l2 = [9,9,9,9] +Output: [8,9,9,9,0,0,0,1] + +### Constraints: + +- The number of nodes in each linked list is in the range [1, 100]- 0 <= Node.val <= 9 +- It is guaranteed that the list represents a number that does not have leading zeros + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/example.png b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/example.png new file mode 100644 index 00000000..489f8291 Binary files /dev/null and b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/example.png differ diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/javascript/addTwoList.js b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/javascript/addTwoList.js new file mode 100644 index 00000000..d86e976e --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/javascript/addTwoList.js @@ -0,0 +1,25 @@ +class Node { + constructor(val = null, next = null) { + this.val = val; + this.next = next; + } +} + +function addTwoList(l1, l2) { +// type your code here +} + +if (require.main === module) { +// add your own tests in here +console.log("Expecting: { val: 0, next: null }"); +console.log("=>", addTwoNumbers({ val: 0, next: null }, { val: 0, next: null })); + +} + +module.exports = { + Node, + addTwoList +}; + +// Please add your pseudocode to this file +// And a written explanation of your solution \ No newline at end of file diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/javascript/package.json b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/javascript/package.json new file mode 100644 index 00000000..f0b10ba8 --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "addtwolist", + "version": "1.0.0", + "description": "Add Two Numbers (List Nodes)", + "main": "addTwoList.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/javascript/tests/addTwoList.test.js b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/javascript/tests/addTwoList.test.js new file mode 100644 index 00000000..2817a90a --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/javascript/tests/addTwoList.test.js @@ -0,0 +1,27 @@ +const { Node , addTwoList } = require('../addTwoList'); + +const n1 = new Node(3) +const n2 = new Node(4, n1) +const l1 = new Node(2, n2) + +const n3 = new Node(4) +const n4 = new Node(6, n3) +const l2 = new Node(5, n4) + +test('Input: l1 = [2,4,3] & l2 = [5,6,4], Output: [7,0,8]', () => { + expect(addTwoList(l1,l2)).toEqual({"next": {"next": {"next": null, "val": 8}, "val": 0}, "val": 7}); +}); + +const l3 = new Node(0) +const l4 = new Node(0) + +test('Input: l3 = [0] & l4 = [0], Output: [0]', () => { + expect(addTwoList(l3,l4)).toEqual({"next": null, "val": 0}); +}); + +const l5 = {"val": 9, "next":{"val": 9, "next":{"val": 9, "next":{"val": 9, "next":{"val": 9, "next":{"val": 9, "next":{"val": 9, "next": null}}}}}}} +const l6 = {"val": 9, "next":{"val": 9, "next":{"val": 9, "next":{"val": 9, "next": null}}}} + +test('Input: l5 = [9,9,9,9,9,9,9] & l6 = [9,9,9,9], Output: [8,9,9,9,0,0,0,1]', () => { + expect(addTwoList(l5,l6)).toEqual({"val": 8, "next":{"val": 9, "next":{"val": 9, "next":{"val": 9, "next":{"val": 0, "next":{"val": 0, "next":{"val": 0, "next": {"val": 1, "next": null}}}}}}}}); +}); \ No newline at end of file diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/.rspec b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/.rspec new file mode 100644 index 00000000..82b8369c --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper \ No newline at end of file diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/Gemfile b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/Gemfile new file mode 100644 index 00000000..5ab5cb4e --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' \ No newline at end of file diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/addTwoList.rb b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/addTwoList.rb new file mode 100644 index 00000000..71b4d1ff --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/addTwoList.rb @@ -0,0 +1,24 @@ +class ListNode + attr_accessor :val, :next + def initialize(val = 0, _next = nil) + @val = val + @next = _next + end +end + +def add_two_numbers(l1, l2) +# type your code here +end + + +if __FILE__ == $PROGRAM_NAME +puts "Expecting: 0" +l1 = ListNode.new(0) +l2 = ListNode.new(0) +puts "=>", add_two_numbers(l1,l2) + +# Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution \ No newline at end of file diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/spec/addTwoList_spec.rb b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/spec/addTwoList_spec.rb new file mode 100644 index 00000000..be2fc02d --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/spec/addTwoList_spec.rb @@ -0,0 +1,25 @@ +require './addTwoList' + +RSpec.describe '#addTwoList' do + +n1 = ListNode.new(3) +n2 = ListNode.new(4, n1) +l1 = ListNode.new(2, n2) + +n3 = ListNode.new(4) +n4 = ListNode.new(6, n3) +l2 = ListNode.new(5, n4) + +n5 = ListNode.new(8) +n6 = ListNode.new(0, n5) +result_1 = ListNode.new(7, n6) + + +p "results: #{add_two_numbers(l1,l2)}" + + it "Input: l1 = [2,4,3] & l2 = [5,6,4], Output: [7,0,8]" do + expect(add_two_numbers(l1,l2).val).to eql(result_1.val) + expect(add_two_numbers(l1,l2).next.val).to eql(result_1.next.val) + expect(add_two_numbers(l1,l2).next.next.val).to eql(result_1.next.next.val) + end +end \ No newline at end of file diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/spec/spec_helper.rb b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/solutions/addTwoList.js b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/solutions/addTwoList.js new file mode 100644 index 00000000..830291ff --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/solutions/addTwoList.js @@ -0,0 +1,42 @@ +class Node { + constructor(val = null, next = null) { + this.val = val; + this.next = next; + } +} + +function addTwoList(l1, l2) { + +let node = null + +const carry = arguments[2] +if (l1 || l2) { + const val1 = l1 ? l1.val : 0 + const val2 = l2 ? l2.val : 0 + const next1 = l1 ? l1.next : null + const next2 = l2 ? l2.next : null + const val = carry ? val1 + val2 + 1 : val1 + val2 + node = new Node(val % 10) + node.next = addTwoList(next1, next2, val >= 10) +} else if (carry) { + node = new Node(1) + node.next = null +} +console.log(node) +return node + +} + +if (require.main === module) { +// add your own tests in here +console.log("Expecting: { val: 0, next: null }"); +console.log("=>", addTwoNumbers({ val: 0, next: null }, { val: 0, next: null })); + +} + +module.exports = { + Node, + addTwoList +}; + +// More details here: https://leetcode.com/problems/add-two-numbers/solution/ \ No newline at end of file diff --git a/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/solutions/addTwoList.rb b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/solutions/addTwoList.rb new file mode 100644 index 00000000..85fbbe2a --- /dev/null +++ b/17-week-13/01-day-3-to-5--add-two-numbers-list-nodes/solutions/addTwoList.rb @@ -0,0 +1,42 @@ +class ListNode + attr_accessor :val, :next + def initialize(val = 0, _next = nil) + @val = val + @next = _next + end +end + +def add_two_numbers(l1, l2) + n1, n2 = l1, l2 + + current = answer = ListNode.new(nil) + carry = 0 + + while n1 || n2 || 0 < carry + a = n1 ? n1.val : 0 + b = n2 ? n2.val : 0 + x = a + b + carry + + carry = x / 10 + x -= 10 if 0 < carry + + node = ListNode.new(x) + current&.next = node + current = node + + n1, n2 = n1&.next, n2&.next + end + answer.next +end + + +if __FILE__ == $PROGRAM_NAME +puts "Expecting: 0" +l1 = ListNode.new(0) +l2 = ListNode.new(0) +puts "=>", add_two_numbers(l1,l2) + +# Don't forget to add your own! +end + +# More details here: https://leetcode.com/problems/add-two-numbers/solution/ \ No newline at end of file diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/.gitignore b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/.gitignore new file mode 100644 index 00000000..07229f1c --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/.gitignore @@ -0,0 +1,5 @@ +Gemfile.lock + +node_modules/ +package-lock.json +.DS_store \ No newline at end of file diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/README.md b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/README.md new file mode 100644 index 00000000..12ec362d --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/README.md @@ -0,0 +1,64 @@ +# Day 1-2: Longest Substring Without Repeating Characters + +Given a string `s`, find the length of the longest substring without repeating characters. + +### Example 1: + +Input: s = "abcabcbb" + +Output: 3 + +Explanation: The answer is "abc", with the length of 3. + +### Example 2: + +Input: s = "pwwkew" + +Output: 3 + +Explanation: The answer is "wke", with the length of 3. +Notice that the answer must be a substring, "pwke" is a subsequence and not a substring. + +### Example 3: + +Input: s = "" + +Output: 0 + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +**BONUS**: For students who did not achieve O(n) on longest substring, refactor solution until achieved. + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/javascript/longSubString.js b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/javascript/longSubString.js new file mode 100644 index 00000000..40a57c2e --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/javascript/longSubString.js @@ -0,0 +1,16 @@ +function lengthOfLongestSubstring(s) { + // type your code here +} + +if (require.main === module) { + +// add your own tests in here +console.log("Expecting: 3"); +console.log("=>", lengthOfLongestSubstring("abcabcbb")); + +} + +module.exports = lengthOfLongestSubstring + +// Please add your pseudocode to this file +// And a written explanation of your solution \ No newline at end of file diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/javascript/package.json b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/javascript/package.json new file mode 100644 index 00000000..89e0d0c7 --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "length_of_longest_substring", + "version": "1.0.0", + "description": "lengthOfLongestSubstring", + "main": "longSubString.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/javascript/tests/longSubString.test.js b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/javascript/tests/longSubString.test.js new file mode 100644 index 00000000..c7f0e5b6 --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/javascript/tests/longSubString.test.js @@ -0,0 +1,23 @@ +const lengthOfLongestSubstring = require('../longSubString'); + +let s1 = "abcabcbb" +let s2 = "bbbbb" +let s3 = "pwwkew" +let s4 = "" + + +test('Input: s1 = "abcabcbb", Output: 3', () => { + expect(lengthOfLongestSubstring(s1)).toEqual(3); +}); + +test('Input: s2 = "bbbbb", Output: 1', () => { + expect(lengthOfLongestSubstring(s2)).toEqual(1); +}); + +test('Input: s3 = "pwwkew", Output: 3', () => { + expect(lengthOfLongestSubstring(s3)).toEqual(3); +}); + +test('Input: s4 = "", Output: 0', () => { + expect(lengthOfLongestSubstring(s4)).toEqual(0); +}); \ No newline at end of file diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/.rspec b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/.rspec new file mode 100644 index 00000000..82b8369c --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper \ No newline at end of file diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/Gemfile b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/Gemfile new file mode 100644 index 00000000..5ab5cb4e --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' \ No newline at end of file diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/longSubString.rb b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/longSubString.rb new file mode 100644 index 00000000..4dab98ac --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/longSubString.rb @@ -0,0 +1,15 @@ +def length_of_longest_substring(s) + # type your code here +end + + +if __FILE__ == $PROGRAM_NAME + +puts "Expecting: 3" +puts "=>", length_of_longest_substring("abcabcbb") + +# Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution \ No newline at end of file diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/spec/longSubString_spec.rb b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/spec/longSubString_spec.rb new file mode 100644 index 00000000..6a574aad --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/spec/longSubString_spec.rb @@ -0,0 +1,34 @@ +require './longSubString' + +RSpec.describe '#length_of_longest_substring' do + +s1 = "abcabcbb" +s2 = "bbbbb" +s3 = "pwwkew" +s4 = "" + +p "results: #{length_of_longest_substring(s1)}" + + it "Input: s1 = abcabcbb, Output: 3" do + expect(length_of_longest_substring(s1)).to eql(3) + end + +p "results: #{length_of_longest_substring(s2)}" + + it "Input: s2 = bbbbb, Output: 1" do + expect(length_of_longest_substring(s2)).to eql(1) + end + +p "results: #{length_of_longest_substring(s3)}" + + it "Input: s3 = pwwkew, Output: 3" do + expect(length_of_longest_substring(s3)).to eql(3) + end + + +p "results: #{length_of_longest_substring(s4)}" + + it "Input: s4 = "", Output: 0" do + expect(length_of_longest_substring(s4)).to eql(0) + end +end \ No newline at end of file diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/spec/spec_helper.rb b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/solutions/longSubString.js b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/solutions/longSubString.js new file mode 100644 index 00000000..f26675e6 --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/solutions/longSubString.js @@ -0,0 +1,29 @@ +function lengthOfLongestSubstring(s) { + + let map = {} + let start = 0 + let maxLen = 0 + let arr = s.split('') + + for (i=0; i < s.length; i++) { + let current = map[arr[i]] + if (current!=null && start <= current) { + start = current + 1 + } else { + maxLen = Math.max(maxLen, i - start + 1) + } + + map[arr[i]] = i + } + + return maxLen +} + +if (require.main === module) { + +console.log("Expecting: 3"); +console.log("=>", lengthOfLongestSubstring("abcabcbb")); + +} + +module.exports = lengthOfLongestSubstring \ No newline at end of file diff --git a/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/solutions/longSubString.rb b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/solutions/longSubString.rb new file mode 100644 index 00000000..6d544bc3 --- /dev/null +++ b/18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters/solutions/longSubString.rb @@ -0,0 +1,20 @@ +def length_of_longest_substring(s) + return s.size if s.size < 2 + + queue, hash = [], {} + s.each_char.reduce(-Float::INFINITY) do |max, c| + hash.delete(char = queue.shift) until queue.empty? || char.eql?(c) if hash.key?(c) + + [max, (queue << hash[c] = c).size].max + end +end + + +if __FILE__ == $PROGRAM_NAME + +puts "Expecting: 3" +puts "=>", length_of_longest_substring("abcabcbb") + +# Don't forget to add your own! +end + \ No newline at end of file diff --git a/18-week-14/01-day-3-to-5--validate-bst/.gitignore b/18-week-14/01-day-3-to-5--validate-bst/.gitignore new file mode 100644 index 00000000..07229f1c --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/.gitignore @@ -0,0 +1,5 @@ +Gemfile.lock + +node_modules/ +package-lock.json +.DS_store \ No newline at end of file diff --git a/18-week-14/01-day-3-to-5--validate-bst/README.md b/18-week-14/01-day-3-to-5--validate-bst/README.md new file mode 100644 index 00000000..8c02eb17 --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/README.md @@ -0,0 +1,63 @@ +# Day 3-5: Validate Binary Search Tree + +Given the root of a binary tree, determine if it is a valid binary search tree (BST). + +A valid BST is defined as follows: + +- The left subtree of a node contains only nodes with keys less than the node's key. +- The right subtree of a node contains only nodes with keys greater than the node's key. +- Both the left and right subtrees must also be binary search trees. + +### Example 1: + +![example-1](example-1.png) + +Input: root = [2,1,3] + +Output: true + +### Example 2: + +![example-2](example-2.png) + +Input: root = [5,1,4,null,null,3,6] + +Output: false + +Explanation: The root node's value is 5 but its right child's value is 4. + +## Before you start coding: + +1. Rewrite the problem in your own words +2. Validate that you understand the problem +3. Write your own test cases +4. Pseudocode +5. Code! + +**_And remember, don't run our tests until you've passed your own!_** + +## How to run your own tests + +### Ruby + +1. `cd` into the ruby folder +2. `ruby .rb` + +### JavaScript + +1. `cd` into the javascript folder +2. `node .js` + +## How to run our tests + +### Ruby + +1. `cd` into the ruby folder +2. `bundle install` +3. `rspec` + +### JavaScript + +1. `cd` into the javascript folder +2. `npm i` +3. `npm test` diff --git a/18-week-14/01-day-3-to-5--validate-bst/example-1.png b/18-week-14/01-day-3-to-5--validate-bst/example-1.png new file mode 100644 index 00000000..cd8d89ba Binary files /dev/null and b/18-week-14/01-day-3-to-5--validate-bst/example-1.png differ diff --git a/18-week-14/01-day-3-to-5--validate-bst/example-2.png b/18-week-14/01-day-3-to-5--validate-bst/example-2.png new file mode 100644 index 00000000..0a2e00b6 Binary files /dev/null and b/18-week-14/01-day-3-to-5--validate-bst/example-2.png differ diff --git a/18-week-14/01-day-3-to-5--validate-bst/javascript/package.json b/18-week-14/01-day-3-to-5--validate-bst/javascript/package.json new file mode 100644 index 00000000..db49f3ff --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/javascript/package.json @@ -0,0 +1,19 @@ +{ + "name": "valid_bts", + "version": "1.0.0", + "description": "validBTS", + "main": "validBTS.js", + "dependencies": { + "jest": "^26.6.3" + }, + "devDependencies": {}, + "scripts": { + "test": "jest" + }, + "repository": { + "type": "git", + "url": "none" + }, + "author": "flatiron", + "license": "ISC" +} diff --git a/18-week-14/01-day-3-to-5--validate-bst/javascript/tests/validBTS.test.js b/18-week-14/01-day-3-to-5--validate-bst/javascript/tests/validBTS.test.js new file mode 100644 index 00000000..5c4d746d --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/javascript/tests/validBTS.test.js @@ -0,0 +1,11 @@ +const { Node , isValidBST } = require('../validBTS'); + +describe('example test cases', () => { + // Convenience helper to construct a tree. + const T = (v, l, r) => new Node(v, l, r); + + it('should validate the examples', () => { + expect(isValidBST(T(2, T(1), T(3)))).toEqual(true); + expect(isValidBST(T(5, T(1), T(4, T(3), T(6))))).toEqual(false); + }); +}); \ No newline at end of file diff --git a/18-week-14/01-day-3-to-5--validate-bst/javascript/validBTS.js b/18-week-14/01-day-3-to-5--validate-bst/javascript/validBTS.js new file mode 100644 index 00000000..32ca0681 --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/javascript/validBTS.js @@ -0,0 +1,29 @@ +class Node { + constructor(val, left = null, right = null){ + this.val = val; + this.left = left; + this.right = right; + } +} + +function isValidBST(root) { + // type your code here +} + +if (require.main === module) { + +// add your own tests in here +const T = (v, l, r) => new Node(v, l, r) + +console.log("Expecting: true"); +console.log("=>", isValidBST(T(2, T(1), T(3)))); + +} + +module.exports = { + isValidBST, + Node +} + +// Please add your pseudocode to this file +// And a written explanation of your solution \ No newline at end of file diff --git a/18-week-14/01-day-3-to-5--validate-bst/ruby/.rspec b/18-week-14/01-day-3-to-5--validate-bst/ruby/.rspec new file mode 100644 index 00000000..82b8369c --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/ruby/.rspec @@ -0,0 +1 @@ +--require spec_helper \ No newline at end of file diff --git a/18-week-14/01-day-3-to-5--validate-bst/ruby/Gemfile b/18-week-14/01-day-3-to-5--validate-bst/ruby/Gemfile new file mode 100644 index 00000000..5ab5cb4e --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/ruby/Gemfile @@ -0,0 +1,7 @@ +# frozen_string_literal: true + +source "https://rubygems.org" + +git_source(:github) {|repo_name| "https://github.com/#{repo_name}" } + +gem 'rspec' \ No newline at end of file diff --git a/18-week-14/01-day-3-to-5--validate-bst/ruby/spec/spec_helper.rb b/18-week-14/01-day-3-to-5--validate-bst/ruby/spec/spec_helper.rb new file mode 100644 index 00000000..251aa510 --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/ruby/spec/spec_helper.rb @@ -0,0 +1,100 @@ +# This file was generated by the `rspec --init` command. Conventionally, all +# specs live under a `spec` directory, which RSpec adds to the `$LOAD_PATH`. +# The generated `.rspec` file contains `--require spec_helper` which will cause +# this file to always be loaded, without a need to explicitly require it in any +# files. +# +# Given that it is always loaded, you are encouraged to keep this file as +# light-weight as possible. Requiring heavyweight dependencies from this file +# will add to the boot time of your test suite on EVERY test run, even for an +# individual file that may not need all of that loaded. Instead, consider making +# a separate helper file that requires the additional dependencies and performs +# the additional setup, and require it from the spec files that actually need +# it. +# +# See http://rubydoc.info/gems/rspec-core/RSpec/Core/Configuration +RSpec.configure do |config| + # rspec-expectations config goes here. You can use an alternate + # assertion/expectation library such as wrong or the stdlib/minitest + # assertions if you prefer. + config.expect_with :rspec do |expectations| + # This option will default to `true` in RSpec 4. It makes the `description` + # and `failure_message` of custom matchers include text for helper methods + # defined using `chain`, e.g.: + # be_bigger_than(2).and_smaller_than(4).description + # # => "be bigger than 2 and smaller than 4" + # ...rather than: + # # => "be bigger than 2" + expectations.include_chain_clauses_in_custom_matcher_descriptions = true + end + + # rspec-mocks config goes here. You can use an alternate test double + # library (such as bogus or mocha) by changing the `mock_with` option here. + config.mock_with :rspec do |mocks| + # Prevents you from mocking or stubbing a method that does not exist on + # a real object. This is generally recommended, and will default to + # `true` in RSpec 4. + mocks.verify_partial_doubles = true + end + + # This option will default to `:apply_to_host_groups` in RSpec 4 (and will + # have no way to turn it off -- the option exists only for backwards + # compatibility in RSpec 3). It causes shared context metadata to be + # inherited by the metadata hash of host groups and examples, rather than + # triggering implicit auto-inclusion in groups with matching metadata. + config.shared_context_metadata_behavior = :apply_to_host_groups + +# The settings below are suggested to provide a good initial experience +# with RSpec, but feel free to customize to your heart's content. +=begin + # This allows you to limit a spec run to individual examples or groups + # you care about by tagging them with `:focus` metadata. When nothing + # is tagged with `:focus`, all examples get run. RSpec also provides + # aliases for `it`, `describe`, and `context` that include `:focus` + # metadata: `fit`, `fdescribe` and `fcontext`, respectively. + config.filter_run_when_matching :focus + + # Allows RSpec to persist some state between runs in order to support + # the `--only-failures` and `--next-failure` CLI options. We recommend + # you configure your source control system to ignore this file. + config.example_status_persistence_file_path = "spec/examples.txt" + + # Limits the available syntax to the non-monkey patched syntax that is + # recommended. For more details, see: + # - http://rspec.info/blog/2012/06/rspecs-new-expectation-syntax/ + # - http://www.teaisaweso.me/blog/2013/05/27/rspecs-new-message-expectation-syntax/ + # - http://rspec.info/blog/2014/05/notable-changes-in-rspec-3/#zero-monkey-patching-mode + config.disable_monkey_patching! + + # This setting enables warnings. It's recommended, but in some cases may + # be too noisy due to issues in dependencies. + config.warnings = true + + # Many RSpec users commonly either run the entire suite or an individual + # file, and it's useful to allow more verbose output when running an + # individual spec file. + if config.files_to_run.one? + # Use the documentation formatter for detailed output, + # unless a formatter has already been configured + # (e.g. via a command-line flag). + config.default_formatter = "doc" + end + + # Print the 10 slowest examples and example groups at the + # end of the spec run, to help surface which specs are running + # particularly slow. + config.profile_examples = 10 + + # Run specs in random order to surface order dependencies. If you find an + # order dependency and want to debug it, you can fix the order by providing + # the seed, which is printed after each run. + # --seed 1234 + config.order = :random + + # Seed global randomization in this process using the `--seed` CLI option. + # Setting this allows you to use `--seed` to deterministically reproduce + # test failures related to randomization by passing the same `--seed` value + # as the one that triggered the failure. + Kernel.srand config.seed +=end +end diff --git a/18-week-14/01-day-3-to-5--validate-bst/ruby/spec/valdiBTS_spec.rb b/18-week-14/01-day-3-to-5--validate-bst/ruby/spec/valdiBTS_spec.rb new file mode 100644 index 00000000..6b794c43 --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/ruby/spec/valdiBTS_spec.rb @@ -0,0 +1,21 @@ +require './validBTS' + +RSpec.describe '#validBTS' do + + it "should validate the example-1" do + def T(v,l,r) + return Node.new(v, l, r) + end + + expect(is_valid_bst(T(2, T(1, nil, nil), T(3, nil, nil)))).to eql(true) + end + + it "should validate the example-2" do + def T(v,l,r) + return Node.new(v, l, r) + end + + expect(is_valid_bst(T(5, T(1, nil, nil), T(4, T(3, nil, nil), T(6, nil, nil))))).to eql(false) + end + +end \ No newline at end of file diff --git a/18-week-14/01-day-3-to-5--validate-bst/ruby/validBTS.rb b/18-week-14/01-day-3-to-5--validate-bst/ruby/validBTS.rb new file mode 100644 index 00000000..ef98ae5c --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/ruby/validBTS.rb @@ -0,0 +1,26 @@ +class Node + attr_accessor :val, :left, :right + def initialize(val = 0, left = nil, right = nil) + @val = val + @left = left + @right = right + end +end + +def is_valid_bst(root) + # type your code here +end + +if __FILE__ == $PROGRAM_NAME + + puts "Expecting: true" + def T(v,l,r) + return Node.new(v, l, r) + end + puts "=>", is_valid_bst(T(2, T(1, nil, nil), T(3, nil, nil))) + +# Don't forget to add your own! +end + +# Please add your pseudocode to this file +# And a written explanation of your solution \ No newline at end of file diff --git a/18-week-14/01-day-3-to-5--validate-bst/solutions/validBTS.js b/18-week-14/01-day-3-to-5--validate-bst/solutions/validBTS.js new file mode 100644 index 00000000..cf14b14c --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/solutions/validBTS.js @@ -0,0 +1,37 @@ +class Node { + constructor(val, left = null, right = null){ + this.val = val; + this.left = left; + this.right = right; + } +} + +const isValid = (root, low, high) => { + if(!root) { + return true; + } + if(root.val <= low || root.val >= high) { + return false; + } + return (isValid(root.left, low, root.val) && isValid(root.right, root.val, high)); + +} + +function isValidBST(root) { + return isValid(root, -Infinity, Infinity); +} + +if (require.main === module) { + +// add your own tests in here +const T = (v, l, r) => new Node(v, l, r) + +console.log("Expecting: true"); +console.log("=>", isValidBST(T(2, T(1), T(3)))); + +} + +module.exports = { + isValidBST, + Node +} \ No newline at end of file diff --git a/18-week-14/01-day-3-to-5--validate-bst/solutions/validBTS.rb b/18-week-14/01-day-3-to-5--validate-bst/solutions/validBTS.rb new file mode 100644 index 00000000..dd541aa4 --- /dev/null +++ b/18-week-14/01-day-3-to-5--validate-bst/solutions/validBTS.rb @@ -0,0 +1,30 @@ +class Node + attr_accessor :val, :left, :right + def initialize(val = 0, left = nil, right = nil) + @val = val + @left = left + @right = right + end +end + +def isValid(root, low, high) + !root || + low < root.val && + root.val < high && + isValid(root.left, low, root.val) && + isValid(root.right, root.val, high) +end + +def is_valid_bst(root) + return isValid(root, -Float::INFINITY, Float::INFINITY) +end + +if __FILE__ == $PROGRAM_NAME + + puts "Expecting: true" + def T(v,l,r) + return Node.new(v, l, r) + end + puts "=>", is_valid_bst(T(2, T(1, nil, nil), T(3, nil, nil))) + +end diff --git a/19-week-15/00-week-15-algo-practices/README.md b/19-week-15/00-week-15-algo-practices/README.md new file mode 100644 index 00000000..e3cd26ad --- /dev/null +++ b/19-week-15/00-week-15-algo-practices/README.md @@ -0,0 +1,11 @@ +# Week-15-algo-practices + +More Practice: +- [Maximum Subarray](https://leetcode.com/problems/maximum-subarray/) + - First just solve it however possible. BONUS: Use DP to solve. Calculate Big-O. +- [House Robber](https://leetcode.com/problems/house-robber/) + - First just solve it however possible. BONUS: Use DP to solve. Calculate Big-O. +- [Is Subsequence](https://leetcode.com/problems/is-subsequence/) + - Solve however possible, then aim for O(n). Calculate Big-O. +- [Unique Paths](https://leetcode.com/problems/unique-paths-iii/) +- [Egg Dropping Problem](https://leetcode.com/problems/super-egg-drop/) diff --git a/20-pairing-exercises-5/00-code-comparison/README.md b/20-pairing-exercises-5/00-code-comparison/README.md new file mode 100644 index 00000000..47bbdb77 --- /dev/null +++ b/20-pairing-exercises-5/00-code-comparison/README.md @@ -0,0 +1,23 @@ +# Code Comparison + +## Introduction + +For this activity, you and your partner will each choose a problem that you have +both completed. Either in person or over video chat via screenshare: + +- Explain your solutions to each other +- Point out the differences in how you approached solving the problem +- Discuss at least one part of your partner's solution that you liked and have + learned from + - Did your partner do something you didn't even think of? + - Is there something about the way they code that jumps out to you? For + example, is their code extremely readable without comments? + - Is their solution more efficient, e.g. better time complexity or uses less + memory? +- Discuss at least one part of your partner's solution which you think can be + improved and why + - Is the solution difficult to understand without heavy commenting? + - Does it contain redundant/unused code? + - Should helper methods have been used? + - Could it easily be made more efficient? For example, were the best data + structures used to solve the problem? diff --git a/20-pairing-exercises-5/01-whiteboard-and-calculate-big-o/README.md b/20-pairing-exercises-5/01-whiteboard-and-calculate-big-o/README.md new file mode 100644 index 00000000..444011cd --- /dev/null +++ b/20-pairing-exercises-5/01-whiteboard-and-calculate-big-o/README.md @@ -0,0 +1,44 @@ +# Whiteboard and Calculate Big O + +## Introduction + +For this activity, you and your partner will each choose a problem and solve it in front of one another, and you will also calculate the time complexity for the solution. You may select a problem you have already solved or a problem you have not yet solved. You may complete this activity using an actual whiteboard, if available, or in the IDE of your choosing, such as VS Code or an online REPL. + +Keep in mind that these challenges tend to make people nervous, so remember to always be kind, patient, and encouraging. Also be aware that nerves can cause people to come up with some pretty weird solutions to problems, so remember to bring your empathy with you! + +Plan to spend 15 minutes in each role. This means you and/or your partner might not have enough time to finish the solution, and that's OK. If you can reasonably spend more time on this, you can, but do put a time limit on it. Be sure to calculate Big O for time complexity even if your solution isn't complete. If you can, try to reason about and discuss what Big O would be if you had completed it based on your pseudocode. + +## Instructions for Interviewer + +As the interviewer your job will be to first present the problem. Explain the challenge to your partner and provide some example test cases. You are not expected to provide every possible test case or edge case. Instead, provide just enough detail for the interviewee to understand the problem and ask clarifying questions. Example: "For this challenge, your function will accept a single string as input and return it in reverse. So if it were to receive 'cat', it would return 'tac'." + +You will also need to answer questions. Your partner might ask you to confirm their understanding of the problem or whether or not they should handle certain edge cases. If you don't know the answer to a question, it's OK to say "I don't know" or "I'll let you decide." Sometimes the interviewer doesn't have the answers. + +Notice when your partner gets stuck and needs a nudge in the right direction. Provide helpful tips or hints, but don't give away the answer. Ideally, your partner will ask questions when they get stuck, but if you notice that they're struggling with something for a little too long, don't be afraid to give a little nudge. You can also ask in advance if your partner would like a hint before providing advice. + +When time is up, provide a constructive review of your partner's performance. Some areas to talk about include: + +- Problem explanation: did your partner explain the problem back to you in their own words and confirm their understanding before coding? Did they ask clarifying questions when necessary? +- Testing: did your partner check their understanding against the given test case/s? Did they write their own? +- Pseudocoding: did your partner explain what they were going to do out loud before coding and with pseudocode? Did they check their pseudocode against the test cases? +- Solution: did your partner solve the problem? Was their syntax correct? Did they handle all of the test cases? Were variables and functions named appropriately? How readable was the code? +- Openness to feedback: Did they ask for feedback when necessary? Were they receptive to your feedback? + +Ultimately, it is more important for the interviewer to evaluate the interviewee's communication during this exercise, so if time is an issue, focus your review there rather than on the solution itself. When providing feedback, be specific, so your partner has an opportunity to improve. Also structure your feedback into two sections. For example, you might talk about what went well and then what can be improved, rather than mixing them together. + +## Instructions for Interviewee + +As the interviewee, your job will be to solve the problem posed by your partner. The Interviewer instructions above explain which areas you should focus on in order to ace your interview. The content in the Welcome section of this course will also help, so make sure you read it before attempting your first whiteboard challenge. + +Here are some general tips: + +- Communication is key: explain the problem and your approach to the solution +- Assume you haven't been given all of the information, such as all of the inputs you need to account for +- Ask for help when you need it and be receptive to feedback +- Don't be afraid to admit when you know there's a better way to do something, e.g. "I know I'm brute forcing this solution and there's a more efficient way, but I want to solve it first and then optimize once I know I can solve it." + +When time is up, provide a constructive review for your interviewer. Some areas to talk about include: + +- Did the interviewer communicate clearly? Could you understand the challenge and did they answer your questions in a manner that you could understand? +- Did they give you time to solve the problem or explore a possible solution before providing feedback? In other words, were they patient? +- Was their feedback helpful? diff --git a/README.md b/README.md new file mode 100644 index 00000000..1f71213e --- /dev/null +++ b/README.md @@ -0,0 +1,193 @@ +# Data Structures and Algorithms + +Welcome to our Data Structures and Algorithms course! You may or may not have +seen this course on Canvas as well during your time in the program. This repo +is set up to give you easy access to the material from this course upon graduation +so you can continue working and prepare for technical interviews. + +Fork and clone this repository to get started! + +## How to Progress Through This Course + +This course is split up so that you can devote 30 to 60 minutes each day to a +new algorithm problem. You are welcome to move through the material faster if +you have time to do so. Like learning anything, consistency is key, so make sure +you have a plan for approaching this material and stick to it! + +We recommend approaching these problems in order by yourself since each problem +increases in difficulty. For a problem that is meant to be solved on the same +day, work on it for 30 minutes. If you can’t solve it in 30 minutes, work on +something else, and then come back to it and give yourself up to 30 more +minutes. For problems that are meant to be solved over the course of several +days, set aside 30 to 60 minutes each day to work on them. If you reach the time +limit and have not solved the problem, look at our solution or someone else's +and take time to understand why it works. + +For any type of problem, if you were able to pass most of the test cases but +just can’t quite solve a couple of the edge cases, consider looking for a +solution to compare yours to. If we provide a solution, you can compare it to +ours, but you can also ask other students and search the Internet. Take the time +to understand why the solutions work and consider working on your solution until +it works given what you’ve learned. + +## Some Things to Keep in Mind + +You may struggle with some or many of these problems. Be patient with yourself +and trust that with time and practice, you’ll improve. You’ll be amazed at how +much you progress with each week or month! + +If you look at other solutions, be aware that shorter doesn’t mean better. Aim +for a solution that makes sense to you, which you can also explain to others. +Three lines of easy-to-understand code is better than one line of confusing +code. + +If you use an online platform that provides information on how fast your code +runs or how well it utilizes memory, take that information with a giant grain of +salt. Many of those platforms do not tell you how they arrive at those results, +and you may find that the results vary greatly even when running the same code. +You might also find that someone with a similar solution to yours has wildly +different performance results - an indication that the platform isn’t +calculating results correctly. + +Lastly, try not to compare yourself to others at this point. Everyone progresses +at a different pace, and that’s OK. The key is to keep trying while taking care +of yourself mentally and physically. + +## Course Roadmap + +Use the checklist below to track your progress. In Markdown, you can check +an item off the list like so: + +```md +- [x] done with this +- [ ] still working +``` + +> VSCode Users: You can install the +> [Markdown Checkboxes](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-checkbox) +> extension to enable checkboxes in Markdown previews + +Start with the Welcome section, then work your way through the other sections in +order. You should reach out to a classmate or other Flatiron alumni to work on +the pairing exercises together and give feedback. + +### Welcome + +- [ ] [Welcome to Technical Interview Preparation](./00-welcome/00-welcome-to-technical-interview-preparation) +- [ ] [Algorithmic Problem Solving](./00-welcome/01-algorithmic-problem-solving) +- [ ] [A Note on Testing](./00-welcome/02-a-note-on-testing) +- [ ] [Problem Solving Tips](./00-welcome/03-problem-solving-tips) + +### Week 1: Starter Algorithms + +- [ ] [Day 1: Reverse a String](./01-week-1--starter-algorithms/00-day-1--reverse-a-string) +- [ ] [Day 2: Find First Duplicate](./01-week-1--starter-algorithms/01-day-2--find-first-duplicate) +- [ ] [Day 3: Fibonacci Series](./01-week-1--starter-algorithms/02-day-3--fibonacci-series) +- [ ] [Day 4: Selection Sort](./01-week-1--starter-algorithms/03-day-4--selection-sort) +- [ ] [Day 5: Find Shortest String](./01-week-1--starter-algorithms/04-day-5--find-shortest-string) + +### Week 2: Recursion + +- [ ] [Introduction to Recursion](./02-week-2--recursion/00-introduction-to-recursion) +- [ ] [Day 1: Recursive Counting](./02-week-2--recursion/01-day-1--recursive-counting) +- [ ] [Day 2: Recursive Search](./02-week-2--recursion/02-day-2--recursive-search) +- [ ] [Day 3: Recursive Fibonacci Series](./02-week-2--recursion/03-day-3--recursive-fibonacci-series) +- [ ] [Day 4: Recursive Find Shortest String](./02-week-2--recursion/04-day-4--recursive-find-shortest-string) +- [ ] [Day 5: Recursive Selection Sort](./02-week-2--recursion/05-day-5--recursive-selection-sort) + +### Week 3: Additional Practice + +- [ ] [Bonus 1: Balancing Parentheses](./03-week-3--additional-practice/00-bonus-1--balancing-parenetheses) +- [ ] [Bonus 2: Roman Numeral to Integer](./03-week-3--additional-practice/01-bonus-2--roman-numeral-to-integer) +- [ ] [Bonus 3: Rotate Array Clockwise](./03-week-3--additional-practice/02-bonus-3--rotate-array-clockwise) +- [ ] [Bonus 4: Distinct Pair Sum](./03-week-3--additional-practice/03-bonus-4--distinct-pair-sum) +- [ ] [Bonus 5: Consecutive Substrings](./03-week-3--additional-practice/04-bonus-5--consecutive-substrings) + +### Pairing Exercises 1 + +- [ ] [Code Review](./04-pairing-exercises-1/00-code-review) +- [ ] [Whiteboard](./04-pairing-exercises-1/01-whiteboard) + +### Week 4: Big O Notation + +- [ ] [Day 1: Introduction to Big O Notation](./05-week-4--big-o-notation/00-day-1--introduction-to-big-o-notation) +- [ ] [Day 2: Introduction to Space Complexity](./05-week-4--big-o-notation/01-day-2--introduction-to-space-complexity) + +### Week 5: Big O Continued + +- [ ] [Days 1 to 2: Implement a Stack Class](./06-week-5--big-o-continued/00-days-1-to-2--implement-a-stack-class) +- [ ] [Days 3 to 4: Implement a Queue Class](./06-week-5--big-o-continued/01-days-3-to-4--implement-a-queue-class) +- [ ] [Day 5: Implement a Set](./06-week-5--big-o-continued/02-day-5--implement-a-set) + +### Week 6: Foundational Data Structures + +- [ ] [Days 1 to 2: Implement a Linked List](./07-week-6--foundational-data-structures/00-days-1-to-2--implement-a-linked-list) +- [ ] [Day 3: Underneath Arrays](./07-week-6--foundational-data-structures/01-day-3--underneath-arrays) +- [ ] [Day 4: Underneath Hashes](./07-week-6--foundational-data-structures/02-day-4--underneath-hashes) +- [ ] [Bonus Algorithm: Recursive String Reverse](./07-week-6--foundational-data-structures/03-bonus-algorithm--recursive-string-reverse) +- [ ] [Bonus: Modify Linked List to Track Tail and Size](./07-week-6--foundational-data-structures/04-bonus--modify-linked-list-to-track-tail-and-size) +- [ ] [Bonus: Build a Doubly Linked List](./07-week-6--foundational-data-structures/05-bonus--build-a-doubly-linked-list) + +### Pairing Exercise 2 + +- [ ] [Whiteboard Big O](./08-pairing-exercise-2/00-whiteboard-big-o) + +### Week 7: Sorting Algorithms + +- [ ] [Days 1 to 2: Bubble Sort](./09-week-7--sorting-algorithms/00-days-1-to-2--bubble-sort) +- [ ] [Days 3 to 5: Merge Sort](./09-week-7--sorting-algorithms/01-days-3-to-5--merge-sort) + +### Week 8: Searching + +- [ ] [Days 1 to 3: Binary Search](./10-week-8--searching/00-days-1-to-3--binary-search) +- [ ] [Day 4: Manual Binary Tree](./10-week-8--searching/01-day-4--manual-binary-tree) +- [ ] [Day 5: Build a Binary Tree + Balancing](./10-week-8--searching/02-day-5--build-a-binary-tree---balancing) + +### Week 9: Searching and Sorting Continued + +- [ ] [Days 1 to 2: Binary Tree Traversal: Level-Order / Breadth-First](./11-week-9--searching-and-sorting-continued/00-days-1-to-2--binary-tree-traversal--level-order---breadth-first) +- [ ] [Day 3 to 4: Tree Traversal In Order](./11-week-9--searching-and-sorting-continued/01-day-3-to-4--tree-traversal-in-order) +- [ ] [Day 5: Find Value Binary Tree](./11-week-9--searching-and-sorting-continued/02-day-5--find-value-binary-tree) +- [ ] [Bonus: Quicksort](./11-week-9--searching-and-sorting-continued/03-bonus--quicksort) + +### Pairing Exercise 3 + +- [ ] [Pair Programming](./12-pairing-exercise-3/00-pair-programming) + +### Week 10 + +- [ ] [Days 1 to 2: Create a Queue Class Using Nodes](./13-week-10/00-days-1-to-2--create-a-queue-class-using-nodes) +- [ ] [Days 3 to 5: Build an LRU Cache](./13-week-10/01-days-3-to-5--build-an-lru-cache) + +### Week 11 + +- [ ] [Day 1: What Is a Graph?](./14-week-11/00-day-1--what-is-a-graph-) +- [ ] [Days 2 to 3: Depth-first Graph Traversal](./14-week-11/01-days-2-to-3--depth-first-graph-traversal) +- [ ] [Days 4 to 5: Breadth-first Graph Traversal](./14-week-11/02-days-4-to-5--breadth-first-graph-traversal) + +### Week 12 + +- [ ] [Days 1 to 2: Convert HTML to a Graph](./15-week-12/00-days-1-to-2--convert-html-to-a-graph) + +### Pairing Exercise 4 + +- [ ] [Whiteboard and Calculate Big O](./16-pairing-exercise-4/00-whiteboard-and-calculate-big-o) + +### Week 13 + +- [ ] [Day 1 to 2: Dynamic Programming](./17-week-13/00-day-1-to-2--dynamic-programming) +- [ ] [Day 3 to 5: Add Two Numbers List Nodes](./17-week-13/01-day-3-to-5--add-two-numbers-list-nodes) + +### Week 14 + +- [ ] [Day 1 to 2: Longest Substring Without Repeating Characters](./18-week-14/00-day-1-to-2--longest-substring-without-repeating-characters) +- [ ] [Day 3 to 5: Validate BST](./18-week-14/01-day-3-to-5--validate-bst) + +### Week 15 + +- [ ] [Week 15 Algorithm Practices](./19-week-15/00-week-15-algo-practices) + +### Pairing Exercises 5 + +- [ ] [Code Comparison](./20-pairing-exercises-5/00-code-comparison) +- [ ] [Whiteboard and Calculate Big O](./20-pairing-exercises-5/01-whiteboard-and-calculate-big-o)