Summing Consecutive Integers in Ruby
Let's solve a quick math problem using Ruby.
Given a positive integer, can that number be expressed as a sum of consecutive integers? For example, the number 3 would meet our criteria because 1 + 2 = 3. On the other hand, 4 would not. No combination of the integers below 4 can combine to equal 4 (1+2, 2+3, 1+2+3). Let's solve this in Ruby.
First, as always, let's write out a quick spec. The below examples use the RSpec test framework. Note that RSpec recently changed its syntax so the expectation be_true
should now be written as be_truthy
.
describe "Sum of Consecutive Numbers" do
it "returns true when the number can be summmed consecutively" do
expect(consecutive_sum?(3)).to be_truthy
expect(consecutive_sum?(10)).to be_truthy
expect(consecutive_sum?(31)).to be_truthy
end
it "returns false when the number cannot be summmed consecutively" do
expect(consecutive_sum?(4)).to be_falsy
expect(consecutive_sum?(8)).to be_falsy
expect(consecutive_sum?(32)).to be_falsy
end
end
Our tests fail as expected and we can now move on to the implementation.
For this example we're not going to break out our logic into several classes as we want this to be as functional and simple as possible. Let's think through the problem before we implement it in code.
The first thing we should realize is that we can automatically disregard half the numbers that we'll encounter. If we start with 100, we can divide by 2 and throw out the larger half because 50 + 51 will always be greater than 100. Conversly, we can express in mathematical terms that n/2 + n/2+1 will always be larger than n.
Let's start by writing a method that will iterate through each number starting from 1 to n/2
def consecutive_sum?(num)
1.upto(num/2) do |i|
end
end
We're going to need to keep track of our sum as we continue interating. Let's make that a local variable and give it an initial value of i since once an iteration is finished it means that we won't be considering that number again
def consecutive_sum?(num)
1.upto(num/2) do |i|
sum = s
end
end
Now for our logic. We're going to start a loop that will continue as long as the sum is less than our original number. Once the sum is greater than our original number, we will break out of the loop and return false
. We're going to increment our sum variable by i += 1
and will explicitly return true
if we've our sum is exactly equal to our original number. If we iterate through each number from 1 to n/2 and our sum has never matched our original number, we will return false
.
def consecutive_sum?(num)
1.upto(num/2) do |i|
sum = i
while sum < num
i += 1
sum += i
return true if sum == num
end
end
false
end
When we rerun our tests they pass! One improvement that we might want to make is wrapping our method with a quick check to ensure that we're being passed a positive integer greater than zero. Let's raise two ArgumentErrors. The first will be triggered if an a non-integer is passed to the method and the second will be triggered when a number less than zero is passed to the method. Let's spec out that behavior before we implement it.
it "raises an error when passed a non-integer" do
expect{consecutive_sum?("hello")}.to raise_error(ArgumentError, "Input must be an integer")
end
it "raises an error when passed a negative" do
expect{consecutive_sum?(-2)}.to raise_error(ArgumentError, "Input must be larger than 0")
end
Note that I'm wrapping our expect statement in a lambda so RSpec will be able to catch the error rather than exiting.
After our tests fail as expected, we can implement the logic. We're going to wrap these two checks in a seperate method and call it from our main function.
def check_input(num)
raise ArgumentError, "Input must be an integer" unless num.is_a? Integer
raise ArgumentError, "Input must be larger than 0" unless num > 0
end
And now all we have to do is call the error-checker from our original method.
def consecutive_sum?(num)
check_input(num)
1.upto(num/2) do |i|
sum = i
while sum < num
i += 1
sum += i
return true if sum == num
end
end
false
end
And we're done! We've test-driven a small application that returns true or false if a number is the sum of consecutive integers. You can find the completed code on Github
As always, I would love to hear your feedback. Drop me a note on Twitter or send me an email. Thanks for reading!