Ruby Koan 151 raising exceptions - ruby

I'm going through the ruby koans, I'm on 151 and I just hit a brick wall.
Here is the koan:
# You need to write the triangle method in the file 'triangle.rb'
require 'triangle.rb'
class AboutTriangleProject2 < EdgeCase::Koan
# The first assignment did not talk about how to handle errors.
# Let's handle that part now.
def test_illegal_triangles_throw_exceptions
assert_raise(TriangleError) do triangle(0, 0, 0) end
assert_raise(TriangleError) do triangle(3, 4, -5) end
assert_raise(TriangleError) do triangle(1, 1, 3) end
assert_raise(TriangleError) do triangle(2, 4, 2) end
end
end
Then in triangle.rb we have:
def triangle(a, b, c)
# WRITE THIS CODE
if a==b && a==c
return :equilateral
end
if (a==b && a!=c) || (a==c && a!=b) || (b==c && b!=a)
return :isosceles
end
if a!=b && a!=c && b!=c
return :scalene
end
if a==0 && b==0 && c==0
raise new.TriangleError
end
end
# Error class used in part 2. No need to change this code.
class TriangleError < StandardError
end
I am beyond confused - any help at all would be much appreciated!
EDIT: To complete this koan, I need to put something in the TriangleError class - but I have no idea what
UPDATE: Here is what the koan karma thing is saying:
<TriangleError> exception expected but none was thrown.

A triangle should not have any sides of length 0. If it does, it's either a line segment or a point, depending on how many sides are 0.
Negative length doesn't make sense.
Any two sides of a triangle should add up to more than the third side.
See 3, and focus on the "more".
You shouldn't need to change the TriangleError code, AFAICS. Looks like your syntax is just a little wacky. Try changing
raise new.TriangleError
to
raise TriangleError, "why the exception happened"
Also, you should be testing the values (and throwing exceptions) before you do anything with them. Move the exception stuff to the beginning of the function.

You forgot the case when a,b, or c are negative:
def triangle(a, b, c)
raise TriangleError if [a,b,c].min <= 0
x, y, z = [a,b,c].sort
raise TriangleError if x + y <= z
[:equilateral,:isosceles,:scalene].fetch([a,b,c].uniq.size - 1)
end

Ended up doing this:
def triangle(a, b, c)
a, b, c = [a, b, c].sort
raise TriangleError if a <= 0 || a + b <= c
[nil, :equilateral, :isosceles, :scalene][[a, b, c].uniq.size]
end
Thanks to commenters here :)

def triangle(a, b, c)
[a, b, c].permutation do |sides|
raise TriangleError unless sides[0] + sides[1] > sides[2]
end
case [a,b,c].uniq.size
when 3; :scalene
when 2; :isosceles
when 1; :equilateral
end
end

I like Cory's answer. But I wonder if there's any reason or anything to gain by having four tests, when you could have two:
raise TriangleError, "Sides must by numbers greater than zero" if (a <= 0) || (b <= 0) || (c <= 0)
raise TriangleError, "No two sides can add to be less than or equal to the other side" if (a+b <= c) || (a+c <= b) || (b+c <= a)

You don't need to modify the Exception. Something like this should work;
def triangle(*args)
args.sort!
raise TriangleError if args[0] + args[1] <= args[2] || args[0] <= 0
[nil, :equilateral, :isosceles, :scalene][args.uniq.length]
end

I wanted a method that parsed all arguments effectively instead of relying on the order given in the test assertions.
def triangle(a, b, c)
# WRITE THIS CODE
[a,b,c].permutation { |p|
if p[0] + p[1] <= p[2]
raise TriangleError, "Two sides of a triangle must be greater than the remaining side."
elsif p.count { |x| x <= 0} > 0
raise TriangleError, "A triangle cannot have sides of zero or less length."
end
}
if [a,b,c].uniq.count == 1
return :equilateral
elsif [a,b,c].uniq.count == 2
return :isosceles
elsif [a,b,c].uniq.count == 3
return :scalene
end
end
Hopefully this helps other realize there is more than one way to skin a cat.

After try to understand what I must to do with koan 151, I got it with the first posts, and get lot fun to check everyone solution :) ... here is the mine:
def triangle(a, b, c)
array = [a, b, c].sort
raise TriangleError if array.min <= 0 || array[0]+array[1] <= array[2]
array.uniq!
array.length == 1 ? :equilateral: array.length == 2 ? :isosceles : :scalene
end
Koan is a very interesting way to learn Ruby

You definately do not update the TriangleError class - I am stuck on 152 myself. I think I need to use the pythag theorem here.
def triangle(a, b, c)
# WRITE THIS CODE
if a == 0 || b == 0 || c == 0
raise TriangleError
end
# The sum of two sides should be less than the other side
if((a+b < c) || (a+c < b) || (b+c < a))
raise TriangleError
end
if a==b && b==c
return :equilateral
end
if (a==b && a!=c) || (a==c && a!=b) || (b==c && b!=a)
return :isosceles
end
if(a!=b && a!=c && b!=c)
return :scalene
end
end
# Error class used in part 2. No need to change this code.
class TriangleError < StandardError
end

In fact in the following code the condition a <= 0 is redundant. a + b will always be less than c if a < 0 and we know that b < c
raise TriangleError if a <= 0 || a + b <= c

I don't think I see this one here, yet.
I believe all the illegal triangle conditions imply that the longest side can't be more than half the total. i.e:
def triangle(a, b, c)
fail TriangleError, "Illegal triangle: [#{a}, #{b}, #{c}]" if
[a, b, c].max >= (a + b + c) / 2.0
return :equilateral if a == b and b == c
return :isosceles if a == b or b == c or a == c
return :scalene
end

This one did take some brain time. But here's my solution
def triangle(a, b, c)
# WRITE THIS CODE
raise TriangleError, "All sides must be positive number" if a <= 0 || b <= 0 || c <= 0
raise TriangleError, "Impossible triangle" if ( a + b + c - ( 2 * [a,b,c].max ) <= 0 )
if(a == b && a == c)
:equilateral
elsif (a == b || b == c || a == c)
:isosceles
else
:scalene
end
end

I ended up with this code:
def triangle(a, b, c)
raise TriangleError, "impossible triangle" if [a,b,c].min <= 0
x, y, z = [a,b,c].sort
raise TriangleError, "no two sides can be < than the third" if x + y <= z
if a == b && b == c # && a == c # XXX: last check implied by previous 2
:equilateral
elsif a == b || b == c || c == a
:isosceles
else
:scalene
end
end
I don't like the second condition/raise, but I'm unsure how to improve it further.

You could also try to instance the exception with:
raise TriangleError.new("All sides must be greater than 0") if a * b * c <= 0

Here is what I wrote and it all worked fine.
def triangle(a, b, c)
# WRITE THIS CODE
raise TriangleError, "Sides have to be greater than zero" if (a == 0) | (b == 0) | (c == 0)
raise TriangleError, "Sides have to be a postive number" if (a < 0) | (b < 0) | (c < 0)
raise TriangleError, "Two sides can never be less than the sum of one side" if ((a + b) < c) | ((a + c) < b) | ((b + c) < a)
raise TriangleError, "Two sides can never be equal one side" if ((a + b) == c) | ((a + c) == b) | ((b + c) == a)
return :equilateral if (a == b) & (a == c) & (b == c)
return :isosceles if (a == b) | (a == c) | (b == c)
return :scalene
end
# Error class used in part 2. No need to change this code.
class TriangleError < StandardError
end

You have to check that the new created triangle don't break the "Triangle inequality". You can ensure this by this little formula.
if !((a-b).abs < c && c < a + b)
raise TriangleError
end
When you get the Error:
<TriangleError> exception expected but none was thrown.
Your code is probably throwing an exception while creating a regular triangle in this file. about_triangle_project.rb

For the Koan about_triangle_project_2.rb there's no need to change TriangleError class. Insert this code before your triangle algorithm to pass all tests:
if ((a<=0 || b<=0 || c<=0))
raise TriangleError
end
if ((a+b<=c) || (b+c<=a) || (a+c<=b))
raise TriangleError
end

Here is my version... :-)
def triangle(a, b, c)
if a <= 0 || b <= 0 || c <= 0
raise TriangleError
end
if a + b <= c || a + c <= b || b + c <= a
raise TriangleError
end
return :equilateral if a == b && b == c
return :isosceles if a == b || a == c || b == c
return :scalene if a != b && a != c && b != c
end

This is what I ended up with. It is sort of a combination of a few of the above examples with my own unique take on the triangle inequality exception (it considers the degenerate case as well). Seems to work.
def triangle(a, b, c)
raise TriangleError if [a,b,c].min <= 0
raise TriangleError if [a,b,c].sort.reverse.reduce(:-) >= 0
return :equilateral if a == b && b == c
return :isosceles if a == b || a == c || b == c
return :scalene
end

Here is my elegant answer, with a lot of help from the comments above
def triangle(a, b, c)
test_tri = [a,b,c]
if test_tri.min <=0
raise TriangleError
end
test_tri.sort!
if test_tri[0]+ test_tri[1] <= test_tri[2]
raise TriangleError
end
if a == b and b == c
:equilateral
elsif a != b and b != c and a != c
:scalene
else
:isosceles
end
end

#(1)Any zero or -ve values
if [a,b,c].any? { |side_length| side_length <= 0 }
raise TriangleError
end
#(2)Any side of a triangle must be less than the sum of the other two sides
# a < b+c, b < a+c and c < a+b a valid triangle
# a >= b+c, b >= a+c and c >= a+b an invalid triangle
total_of_side_lengths = [a,b,c].inject {|total,x| total += x}
if [a,b,c].any? { |side_length| side_length >= (total_of_side_lengths - side_length)}
raise TriangleError
end

Not that this question needed another answer; however, I think this is the simplest and most readable solution. Thanks to all those before me.
def triangle(a, b, c)
a, b, c = [a, b, c].sort
raise TriangleError, "all sides must > 0" unless [a, b, c].min > 0
raise TriangleError, "2 smaller sides together must the > 3rd side" unless a + b > c
return :equilateral if a == b && a == c
return :isosceles if a == b || a == c || b == c
return :scalene
end
# Error class used in part 2. No need to change this code.
class TriangleError < StandardError
end

def triangle(a, b, c)
sides = a, b, c # Assigns variable signs (array) to all arguments.
begin
raise TriangleError if sides.inject(:+) <= 0 # Raise an error if all sides added together are less than or equal to 0. (the triangle would be invalid).
raise TriangleError if sides.any?(&:negative?) #Raise an error if there are any negative sides.
sides.each {|side| (side < (sides.inject(:+) - side) ? nil : (raise TriangleError))} # For the final check, Raise an error if any single side is greater than the other two sides added together. It can be broken down like this if side is less than (remaining sides - side we're comparing) raise an error, else, nil.
return :equilateral if sides.uniq.length == 1
return :isosceles if sides.uniq.length == 2
return :scalene if sides.uniq.length == 3
resuce TriangleError
end
end

your previous triangle method should appear here
class TriangleError < StandardError
end
def triangle(x,y,z)
if(x>=y+z||y>=x+z||z>=x+y)
raise TriangleError,"impossible triangle"
elsif(x==0&&y==0&&z==0)||(x<0||y<0||z<0)
raise TriangleError,"length cannot be zero or negative"
elsif(x==y&&x==z)
:equilateral
elsif(x==y||y==z||x==z)
:isosceles
else
:scalene
end
end

My solution, I think it's one of the more readable ones:
def triangle(a, b, c)
a, b, c = [a, b, c].sort
if a <= 0 or c >= a + b
raise TriangleError
end
case [a, b, c].uniq.length
when 1
:equilateral
when 2
:isosceles
when 3
:scalene
end
end

Leon wins on fancy elegance, Benji for his knowledge of the Array API. Here's my brute elegant answer:
def triangle(a, b, c)
[a, b, c].each { | side | raise TriangleError, "Sides must be positive" unless side > 0 }
raise TriangleError, "Two sides can never be less than or equal to third side" if ((a + b) <= c) | ((a + c) <= b) | ((b + c) <= a)
return :equilateral if (a == b) && (b == c)
return :isosceles if (a == b) || (b == c) || (a == c)
return :scalene
end

No Need to change the TriangleError code for either challenge. You just need to check for invalid triangles and raise the error if the triangle isn't.
def triangle(a, b, c)
if a==0 && b==0 && c==0
raise TriangleError, "This isn't a triangle"
end
if a <0 or b < 0 or c <0
raise TriangleError, "Negative length - thats not right"
end
if a + b <= c or a + c <= b or b + c <= a
raise TriangleError, "One length can't be more (or the same as) than the other two added together. If it was the same, the whole thing would be a line. If more, it wouldn't reach. "
end
# WRITE THIS CODE
if a == b and b == c
return :equilateral
end
if (a==b or b == c or a == c)
return :isosceles
end
:scalene
end

There are some absolutely brilliant people on StackOverflow...I'm reminded of that every time I visit :D
Just to contribute to the conversation, here's the solution I came up with:
def triangle(a, b, c)
raise TriangleError if [a,b,c].min <= 0
x,y,z = [a,b,c].sort
raise TriangleError if x + y <= z
equal_sides = 0
equal_sides +=1 if a == b
equal_sides +=1 if a == c
equal_sides +=1 if b == c
# Note that equal_sides will never be 2. If it hits 2
# of the conditions, it will have to hit all 3 by the law
# of associativity
return [:scalene, :isosceles, nil, :equilateral][equal_sides]
end

Here's my solution... honestly I can't think of a more concise and readable one!
def triangle(a, b, c)
raise TriangleError unless a > 0 && b > 0 && c > 0
raise TriangleError if a == b && a + b <= c
raise TriangleError if a == c && a + c <= b
return :equilateral if a == b && b == c
return :isosceles if a == b || b == c || c == a
:scalene
end

Rules:
size must be > 0
Total of any 2 sides, must be bigger that the 3rd
Code:
raise TriangleError if ( [a,b,c].any? {|x| (x <= 0)} ) or ( ((a+b)<=c) or ((b+c)<=a) or ((a+c)<=b))
[:equilateral, :isosceles, :scalene].fetch([a,b,c].uniq.size - 1)

Related

Finding the sum of all the numbers between a and b: Ruby

Been working on this Kata for quite some time now and still can't figure out what I'm missing. The question is given two integers a and b, which can be positive or negative, find the sum of all the numbers between including them too and return it. If the two numbers are equal return a or b.
So far this is what my solution looks like:
def get_sum(a,b)
sum = [a+=b].sum
if sum == a or b
return a
end
end
and this is the output result:
Test Passed: Value == 1
Test Passed: Value == 3
Expected: 14, instead got: 4
Expected: 127759, instead got: 509
Expected: 44178, instead got: 444
I believe the keyword is all the numbers between but I'm not sure how to write that syntactically.
I've included some examples below for further clarification.
get_sum(1, 0) == 1 # 1 + 0 = 1
get_sum(1, 2) == 3 # 1 + 2 = 3
get_sum(0, 1) == 1 # 0 + 1 = 1
get_sum(1, 1) == 1 # 1 Since both are same
get_sum(-1, 0) == -1 # -1 + 0 = -1
get_sum(-1, 2) == 2 # -1 + 0 + 1 + 2 = 2
https://www.codewars.com/kata/55f2b110f61eb01779000053/train/ruby
You can use formula for Arithmetic progression:
def get_sum(a, b)
a, b = b, a if a > b
(b - a + 1) * (a + b) / 2
end
Active Support(Rails) extension for Range class OR modern(>= 2.4) Ruby do the same.
So, you can use #MBo answer if your Kata site uses either Rails or modern Ruby. Usually such sites specify the environment and the interpreter version.
def get_sum(a, b)
a, b = b, a if a > b
(a..b).sum
end
Your code does not return result except for a=b case. Also - what [a+=b] generates? Array with a single element a+b, so it's sum is just a+b
Make a range and get it's sum.
Added: parameter ordering
def get_sum(a,b)
a, b = b, a if a > b
return (a..b).sum
end
print get_sum(1,3)
print get_sum(2,2)
print get_sum(-1,2)
print get_sum(3,-1)
>> 6 2 2 5
def get_sum(a,b)
sum = [a+=b].sum
if sum == a or b
return a
end
end
Other answers explain what you could write instead, so let's check your code:
a+=b is called first. It's basically a = a + b, so it calculates the sum of both inputs, saves it in a, and returns a.
sum = [a].sum creates an array with one element, and calculates its sum (which is just this one element). So sum = a
a or b is just a when a is truthy (that is, neither false nor nil).
So here's what your code actually does:
def get_sum(a,b)
a = a + b
sum = a
if sum == a
return a
end
end
Which is just:
def get_sum(a,b)
a = a + b
return a
end
Or :
def get_sum(a,b)
a = a + b
end
or :
def get_sum(a,b)
a + b
end
Please, try with below and ref enter link description here:
def get_sum(a,b)
return a if a == b
return (a..b).sum if b > a
return (b..a).sum if a > b
end
Test:
describe "Example Tests" do
Test.assert_equals(get_sum(1,1),1)
Test.assert_equals(get_sum(0,1),1)
Test.assert_equals(get_sum(0,-1),-1)
Test.assert_equals(get_sum(1,2),3)
Test.assert_equals(get_sum(5,-1),14)
end
Outout:
Test Results:
Example Tests
Test Passed: Value == 1
Test Passed: Value == -1
Test Passed: Value == 3
Test Passed: Value == 14
You have passed all of the tests! :)

How to optimize code - it works, but I know I'm missing much learning

The exercise I'm working on asks "Write a method, coprime?(num_1, num_2), that accepts two numbers as args. The method should return true if the only common divisor between the two numbers is 1."
I've written a method to complete the task, first by finding all the factors then sorting them and looking for duplicates. But I'm looking for suggestions on areas I should consider to optimize it.
The code works, but it is just not clean.
def factors(num)
return (1..num).select { |n| num % n == 0}
end
def coprime?(num_1, num_2)
num_1_factors = factors(num_1)
num_2_factors = factors(num_2)
all_factors = num_1_factors + num_2_factors
new = all_factors.sort
dups = 0
new.each_index do |i|
dups += 1 if new[i] == new[i+1]
end
if dups > 1
false
else
true
end
end
p coprime?(25, 12) # => true
p coprime?(7, 11) # => true
p coprime?(30, 9) # => false
p coprime?(6, 24) # => false
You could use Euclid's algorithm to find the GCD, then check whether it's 1.
def gcd a, b
while a % b != 0
a, b = b, a % b
end
return b
end
def coprime? a, b
gcd(a, b) == 1
end
p coprime?(25, 12) # => true
p coprime?(7, 11) # => true
p coprime?(30, 9) # => false
p coprime?(6, 24) # => false```
You can just use Integer#gcd:
def coprime?(num_1, num_2)
num_1.gcd(num_2) == 1
end
You don't need to compare all the factors, just the prime ones. Ruby does come with a Prime class
require 'prime'
def prime_numbers(num_1, num_2)
Prime.each([num_1, num_2].max / 2).map(&:itself)
end
def factors(num, prime_numbers)
prime_numbers.select {|n| num % n == 0}
end
def coprime?(num_1, num_2)
prime_numbers = prime_numbers(num_1, num_2)
# & returns the intersection of 2 arrays (https://stackoverflow.com/a/5678143)
(factors(num_1, prime_numbers) & factors(num_2, prime_numbers)).length == 0
end

How to do a conditioned for loop in Ruby

I'm amazed with Ruby's syntax, I can only describe it in one word: comfortable.
EDIT: I think I wasn't clear. I want an easy way to exit loops with conditions.
Sadly, I can't find how to do this Java code in Ruby:
Assume:
array = [1,2,3,4]
array2 = [1,2,3,4]
boolean condition = false;
for(int i = 0; i < array.length && !condition; i++)
{
for(int j = 0; j < array2.length && !condition; j++)
{
condition = (array[i] + array2[j] + 1 == 7);
}
}
if(condition)
{
System.out.println("Two elements of the arrays + 1 sum 7")
}
I love Ruby's one liners... But I can't even do this with full open loops...
I'm looking for something like this (each_while is made up):
array.each_while(condition && condition2) { SomeAction }
Which is the simplest way to do this in Ruby?
Most of the loops I work with have exiting conditions to optimize them. Everything I find on the internet is not acceptable for Ruby's beautiful syntax because it is even worse than Java, and we all know Java ain't pretty.
Some solution I found in the web:
catch "BreakOuterLoop" do
for i in 1..10
print "out #{i}\n"
for j in 1..10
print "in #{j}\n"
throw "BreakOuterLoop" if i+j > 16
end
end
end
Just awful...
require 'matrix'
Matrix[0...rows, 0...cols].each_with_index do |e, row, col|
next unless [cond1, cond2, cond3].reduce :&
# code
end
array1.each.with_index.lazy.take_while { cond1 }.each do |e1, i1|
array2.each.with_index.lazy.take_while { cond2 }.each do |e2, i2|
# do some stuff
end
end
Loop with condition
You could use break :
array1.each do |x|
break unless condition && condition2
array2.each do |y|
break unless condition3
# do_something
end
end
end
If you need the indices in your conditions :
array1.each_with_index do |x,i|
break unless condition && condition2
array2.each_with_index do |y,j|
break unless condition3
# do_something
end
end
end
Specific problem
Boolean
For your updated problem, you can use any?. It is exactly what you wanted to do. It iterates as long as a condition isn't true, and returns a value ASAP :
array = [1,2,3,4]
array2 = [1,2,3,4]
puts array.product(array2).any?{|a,b| a + b + 1 == 7 }
#=> true
Or :
puts array.any?{|a| array2.any?{ |b| a + b + 1 == 7 } }
#=> true
puts array.any?{|a| array2.any?{ |b| a + b + 1 == 12 } }
#=> false
The second example should be faster, because not every pair is created : As soon as one is found, true is returned.
Pair
If you want to know for which pair the condition is true, you can use find:
p array.product(array2).find { |a, b| a + b + 1 == 7 }
#=> [2,4]
p array.product(array2).find { |a, b| a + b + 1 == 12 }
#=> nil
Optimization for huge arrays
The above code will run slow for huge arrays.
You could convert the biggest array to a Set, and use a direct lookup :
require 'set'
array = [1, 2, 3, 4]
array2 = [1, 2, 3, 4]
set2 = array2.to_set
sum = 7 - 1
x1 = array.find { |x| set2.include?(sum - x) }
if x1
puts "#{x1} + #{sum - x1} + 1 = #{sum + 1}"
end
#=> 2 + 4 + 1 = 7
array.length.times do |i|
break unless condition_1 && condition_2
# some action
end
break will stop the loop as soon as the conditions are no longer met
Regarding the loop matrix
Of course you can nest a loop within a loops within a loop, but that is definitely not the ruby way. I'm sure there is a nicer solution to whatever problem may arise.
As stated before, there is a prettier functional solution for probably any specific use case you can think of. I will try to answer the more general question, which is how to convert this java code:
int somethingYouWillMutate = 0; // any types or values or number of things
for(int i = 0; i < array.length && condition && condition2; i++) {
for(int j = 0; j < array2.length && condition; j++) {
someAction(array[i], array2[j], somethingYouWillMutate);
}
}
To ruby:
something_you_will_mutate = 0
array.product(array2).each do |e1, e2|
break unless condition && condition2
some_action(e1, e2)
end
Note:
while c1 && c2 =:=
while true; break if !(c1 && c2) =:=
while true; break unless c1 && c2
If you want the indices as well:
array_indexed = array.each_with_index.to_a
array2_indexed = array2.each_with_index.to_a
array_indexed.product(array2_indexed).each do |(e1, i1), (e2, i2)|
break unless condition && condition2
some_action(e1, e2, i1, i2, something_you_will_mutate)
end
Note: If you want an even more generic solution (with 3 or more arrays for example):
[array, array2, array3...].
map(&:each_with_index).
map(&:to_a).
reduce(:product).
map(&:flatten).
each do |e1, i1, e2, i2, e3, i3...|
break unless condition && condition2 && condition3...
some_action(e1, e2, i1, i2, e3, i3..., something_you_will_mutate)
end

Ruby compute cumulative sum from endpoints recursively?

Given two numbers, say (14, 18), the problem is to find the sum of all the numbers in this range, 14, 15, 16, 17, 18 recursively. Now, I have done this using loops but I have trouble doing this recursively.
Here is my recursive solution:
def sum_cumulative_recursive(a,b)
total = 0
#base case is a == b, the stopping condition
if a - b == 0
puts "sum is: "
return total + a
end
if b - a == 0
puts "sum is: "
return total + b
end
#case 1: a > b, start from b, and increment recursively
if a > b
until b > a
puts "case 1"
total = b + sum_cumulative_recursive(a, b+1)
return total
end
end
#case 2: a < b, start from a, and increment recursively
if a < b
until a > b
puts "case 2"
total = a + sum_cumulative_recursive(a+1, b)
return total
end
end
end
Here are some sample test cases:
puts first.sum_cumulative_recursive(4, 2)
puts first.sum_cumulative_recursive(14, 18)
puts first.sum_cumulative_recursive(-2,-2)
My solution works for cases where a > b, and a < b, but it doesn't work for a == b.
How can I fix this code so that it works?
Thank you for your time.
def sum_cumulative_recursive(a,b)
return a if a == b
a, b = [a,b].sort
a + sum_cumulative_recursive(a + 1, b)
end
EDIT
Here is the most efficient solution I could see from some informal benchmarks:
def sum_cumulative_recursive(a,b)
return a if a == b
a, b = b, a if a > b
a + sum_cumulative_recursive(a + 1, b)
end
Using:
Benchmark.measure { sum_cumulative_recursive(14,139) }
Benchmark for my initial response: 0.005733
Benchmark for #Ajedi32's response: 0.000371
Benchmark for my new response: 0.000115
I was also surprised to see that in some cases, the recursive solution approaches or exceeds the efficiency of the more natural inject solution:
Benchmark.measure { 10.times { (1000..5000).inject(:+) } }
# => 0.010000 0.000000 0.010000 ( 0.027827)
Benchmark.measure { 10.times { sum_cumulative_recursive(1000,5000) } }
# => 0.010000 0.010000 0.020000 ( 0.019441)
Though you run into stack level too deep errors if you take it too far...
I'd do it like this:
def sum_cumulative_recursive(a, b)
a, b = a.to_i, b.to_i # Only works with ints
return sum_cumulative_recursive(b, a) if a > b
return a if a == b
return a + sum_cumulative_recursive(a+1, b)
end
Here's one way of doing it. I assume this is just an exercise, as the sum of the elements of a range r is of course just (r.first+r.last)*(f.last-r.first+1)/2.
def sum_range(range)
return nil if range.last < range.first
case range.size
when 1 then range.first
when 2 then range.first + range.last
else
range.first + range.last + sum_range(range.first+1..range.last-1)
end
end
sum_range(14..18) #=> 80
sum_range(14..14) #=> 14
sum_range(14..140) #=> 9779
sum_range(14..139) #=> 9639
Another solution would be to have a front-end invocation that fixes out-of-order arguments, then a private recursive back-end which does the actual work. I find this is useful to avoid repeated checks of arguments once you've established they're clean.
def sum_cumulative_recursive(a, b)
a, b = b, a if b < a
_worker_bee_(a, b)
end
private
def _worker_bee_(a, b)
a < b ? (a + _worker_bee_(a+1,b-1) + b) : a == b ? a : 0
end
This variant would cut the stack requirement in half by summing from both ends.
If you don't like that approach and/or you really want to trim the stack size:
def sum_cumulative_recursive(a, b)
if a < b
mid = (a + b) / 2
sum_cumulative_recursive(a, mid) + sum_cumulative_recursive(mid+1, b)
elsif a == b
a
else
sum_cumulative_recursive(b, a)
end
end
This should keep the stack size to O(log |b-a|).

why is this not working - ruby koans

I came up with the following solution for a koan.
# and
# about_triangle_project_2.rb
#
def triangle(a, b, c)
driehoek = Array.new[ a, b, c].sort
raise (TriangleError), "length cannnot be 0 or lesser" if (driehoek[0] <= 0)
raise (TriangleError), "impossible triangle" if (driehoek[0] + driehoek[1] < driehoek[2])
return :equilateral if ((a == b) and (b == c))
return :isosceles if (((a == b) and (b != c)) or
((a != b) and (b == c)) or
((a == c) and (a != b)))
return :scalene if ((a !=b) and (b != c))
end
# Error class used in part 2. No need to change this code.
class TriangleError < StandardError
end
But now when triangle [2,2,2] is used I see this error message :
The answers you seek...
wrong number of arguments (3 for 2)
Please meditate on the following code:
./triangle.rb:18:in `[]'
./triangle.rb:18:in `triangle'
Can anyone tell me what is wrong here ?
Roelof
The issue is with how you are creating the array. new is a method and you'll need to use parens (). Change this:
driehoek = Array.new[ a, b, c].sort
To this and it should work:
driehoek = Array.new([a, b, c]).sort

Resources