This week I took a break from building things to go back to the basics a bit. I've been having trouble getting a good handle on the different parts of Ruby (ie- blocks, hashes, etc.) and also struggled at the code retreat last weekend when I had to write code for tests in order to make them pass. Recognizing this as we significant weakness, I decided to spend the week tackling Project Euler problems.
Project Euler problems are math problems that you then solve using code. First, I found that they help me take an issue and think about different approaches. Second, they are great for practicing test-driven development. Third, they create additional challenges. For example, you can solve the problem and then go back and refactor to make your code even better OR I've had a few amazing friends/mentors this week look at my solutions and give me additional constraints or challenges to make my code even cleaner and better or explore a different method to solve the problem. There isn't one solution to these problems and if you google "euler ruby" you'll find dozens of responses.
I'll walk through the setup and first problem.
For me, the setup was actually pretty confusing, although really simply once I received some direction. It may seem basic, but I couldn't find these instructions anywhere so I'm writing them to hopefully help others get set up.
I wanted one euler folder with each problem in it's folder. I originally put both the problem1.rb and problem1_spec.rb files in the same folder but then rspec wouldn't run! It turns out that rspec looks for a lib folder in order to run it. The simplest way around this was to create a lib folder and a spec folder in my euler folder. The lib folder contains all the problem files and the spec folder contains all the spec files. To run the tests, I typed into the command line: $rspec spec/problem1_spec.rb
and to run the actual file I had to type $ruby lib/problem1/problem1.rb
. Finally, in order to test my code, I have been running the answer through IRB. To get to IRB, you simply type irb
into the terminal and then you can write each line of code to see if you get the correct answer. Finally, at the top of the spec, we need to require the problem so that the tests finds the code file.
Euler Problem 1:If we list all the natural numbers below 10 that are multiples of 3 or 5, we get 3, 5, 6 and 9. The sum of these multiples is 23. Find the sum of all the multiples of 3 or 5 below 1000.
First, I'll start with the tests that I wrote. I use rspec:
require 'problem1/problem1'
describe 'solution' do
it "sums the multiples of 3 and 5 under 10" do
expect(Problem1.multiples(10)).to eq 23
end
it "sums the multiples of 3 and 5 under 1000" do
expect(Problem1.multiples(1000)).to eq 233168
end
end
This test format is pretty much what I have been using for all of the Euler problems and I have found it pretty useful. To start the test, you have to describe something and then describe it's characteristics. So, since we are given the answer, we are able to hard code (put in the actual numbers) the test. The test says that for the sum of the multiples of 3 and 5 under to, we should expect that problem1(module)'s multiples(method) of 10(parameters/argument) to equal 23 (because that's what the problem tells us. I did a similar format for the next test and plugged in the final number 233168 once I got the code working.
Now for the code:
module Problem1
def self.multiples(stop_counting)
(1...stop_counting).find_all { |i| i%3 == 0 || i%5 == 0 }.reduce(:+)
end
end
So, first we defined what the module was. Then, we were trying to find the multiples of two numbers (3 and 5) up to a certain number. I put
self
on the method definition because we are calling the method on the module. I put stop_counting
as the argument which helps the tests pass, because in the tests we put at what number I stop counting (10 in the first example and 1000 in the second example). By defining the argument as stop_counting
, we have more flexibility on the tests to plug the numbers into the tests and not the code (I think of this like an excel document where you have you assumptions worksheet. When you have an assumptions worksheet, you can change the number there as your assumptions change and since the rest of the spreadsheet is built on those assumptions, the numbers used in the formulas on other worksheets will automatically change as opposed to having to go through each worksheet and redo all the math manually). So, the code says to look at all the numbers from 1 until stop_counting
and then find all the numbers (I think you can also use .select
here) that have a remainder of 0 when using the modulus (or modulo) which is the operation used to find the remainder of dividing one number by another, in this case either 3 or 5. Then the reduce method takes all the remaining numbers and using the :+ operator, adds those numbers together to get the sum.
Also, while going through this process, I paused in the middle to complete Ruby Monk. While initially I was like, "OMG, I CANNOT do another tutorial", Ruby Monk was really helpful and I wish it had been recommended to me two months ago. As opposed to the traditional ruby tutorial that builds a site of some sort, Ruby Monk really explains the terms and parts that make up Ruby and I feel much more confident in the definitions and grammar of it all now that I've completed it.
I'll be posting more problems in the next few days.