why is this not working - ruby koans - ruby

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

Related

Is there a known algorithm for simplifying a boolean expression with number comparisons?

For example, if I have the expression (A > 5) && (A == 6),
that expression can be simplified to just (A == 6), and still have the same behavior for A ∈ ℤ.
I also need it to work with multiple variables, so for instance ((B > 2) && (C == 2)) || ((B > 2) && (C < 2)) should simplify to (B > 2) && (C < 3).
I won't need to compare two unknowns, only unknowns and numbers, and I only need it to work with the operators <, >, and == for numbers, and && and || for expressions (&& being AND and || being OR, of course). All unknowns are integers.
Is there any algorithm that takes such an expression and returns an expression with equal behavior and a minimal amount of operators?
(in my specific case, || operators are preferred over &&)
Here's a slow dynamic programming algorithm along the lines that you were thinking of.
from collections import defaultdict, namedtuple
from heapq import heappop, heappush
from itertools import product
from math import inf
# Constructors for Boolean expressions. False and True are also accepted.
Lt = namedtuple("Lt", ["lhs", "rhs"])
Eq = namedtuple("Eq", ["lhs", "rhs"])
Gt = namedtuple("Gt", ["lhs", "rhs"])
And = namedtuple("And", ["lhs", "rhs"])
Or = namedtuple("Or", ["lhs", "rhs"])
# Variable names. Arbitrary strings are accepted.
A = "A"
B = "B"
C = "C"
# Example formulas.
first_example = And(Gt(A, 5), Eq(A, 6))
second_example = Or(And(Gt(B, 2), Eq(C, 2)), And(Gt(B, 2), Lt(C, 2)))
third_example = Or(And(Gt(A, 1), Gt(B, 1)), And(Gt(A, 0), Gt(B, 2)))
fourth_example = Or(Lt(A, 6), Gt(A, 5))
fifth_example = Or(And(Eq(A, 2), Gt(C, 2)), And(Eq(B, 2), Lt(C, 2)))
# Returns a map from each variable to the set of values such that the formula
# might evaluate differently for variable = value-1 versus variable = value.
def get_critical_value_sets(formula, result=None):
if result is None:
result = defaultdict(set)
if isinstance(formula, bool):
pass
elif isinstance(formula, Lt):
result[formula.lhs].add(formula.rhs)
elif isinstance(formula, Eq):
result[formula.lhs].add(formula.rhs)
result[formula.lhs].add(formula.rhs + 1)
elif isinstance(formula, Gt):
result[formula.lhs].add(formula.rhs + 1)
elif isinstance(formula, (And, Or)):
get_critical_value_sets(formula.lhs, result)
get_critical_value_sets(formula.rhs, result)
else:
assert False, str(formula)
return result
# Returns a list of inputs sufficient to compare Boolean combinations of the
# primitives returned by enumerate_useful_primitives.
def enumerate_truth_table_inputs(critical_value_sets):
variables, value_sets = zip(*critical_value_sets.items())
return [
dict(zip(variables, values))
for values in product(*({-inf} | value_set for value_set in value_sets))
]
# Returns both constants and all single comparisons whose critical value set is
# a subset of the given ones.
def enumerate_useful_primitives(critical_value_sets):
yield False
yield True
for variable, value_set in critical_value_sets.items():
for value in value_set:
yield Lt(variable, value)
if value + 1 in value_set:
yield Eq(variable, value)
yield Gt(variable, value - 1)
# Evaluates the formula recursively on the given input.
def evaluate(formula, input):
if isinstance(formula, bool):
return formula
elif isinstance(formula, Lt):
return input[formula.lhs] < formula.rhs
elif isinstance(formula, Eq):
return input[formula.lhs] == formula.rhs
elif isinstance(formula, Gt):
return input[formula.lhs] > formula.rhs
elif isinstance(formula, And):
return evaluate(formula.lhs, input) and evaluate(formula.rhs, input)
elif isinstance(formula, Or):
return evaluate(formula.lhs, input) or evaluate(formula.rhs, input)
else:
assert False, str(formula)
# Evaluates the formula on the many inputs, packing the values into an integer.
def get_truth_table(formula, inputs):
truth_table = 0
for input in inputs:
truth_table = (truth_table << 1) + evaluate(formula, input)
return truth_table
# Returns (the number of operations in the formula, the number of Ands).
def get_complexity(formula):
if isinstance(formula, bool):
return (0, 0)
elif isinstance(formula, (Lt, Eq, Gt)):
return (1, 0)
elif isinstance(formula, And):
ops_lhs, ands_lhs = get_complexity(formula.lhs)
ops_rhs, ands_rhs = get_complexity(formula.rhs)
return (ops_lhs + 1 + ops_rhs, ands_lhs + 1 + ands_rhs)
elif isinstance(formula, Or):
ops_lhs, ands_lhs = get_complexity(formula.lhs)
ops_rhs, ands_rhs = get_complexity(formula.rhs)
return (ops_lhs + 1 + ops_rhs, ands_lhs + ands_rhs)
else:
assert False, str(formula)
# Formula compared by complexity.
class HeapItem:
__slots__ = ["_complexity", "formula"]
def __init__(self, formula):
self._complexity = get_complexity(formula)
self.formula = formula
def __lt__(self, other):
return self._complexity < other._complexity
def __le__(self, other):
return self._complexity <= other._complexity
def __eq__(self, other):
return self._complexity == other._complexity
def __ne__(self, other):
return self._complexity != other._complexity
def __ge__(self, other):
return self._complexity >= other._complexity
def __gt__(self, other):
return self._complexity > other._complexity
# Like heapq.merge except we can add iterables dynamically.
class Merge:
__slots__ = ["_heap", "_iterable_count"]
def __init__(self):
self._heap = []
self._iterable_count = 0
def update(self, iterable):
iterable = iter(iterable)
try:
value = next(iterable)
except StopIteration:
return
heappush(self._heap, (value, self._iterable_count, iterable))
self._iterable_count += 1
def __iter__(self):
return self
def __next__(self):
if not self._heap:
raise StopIteration
value, index, iterable = heappop(self._heap)
try:
next_value = next(iterable)
except StopIteration:
return value
heappush(self._heap, (next_value, index, iterable))
return value
class Combinations:
__slots__ = ["_op", "_formula", "_best_formulas", "_i", "_n"]
def __init__(self, op, formula, best_formulas):
self._op = op
self._formula = formula
self._best_formulas = best_formulas
self._i = 0
self._n = len(best_formulas)
def __iter__(self):
return self
def __next__(self):
if self._i >= self._n:
raise StopIteration
formula = self._op(self._formula, self._best_formulas[self._i])
self._i += 1
return HeapItem(formula)
# Returns the simplest equivalent formula, breaking ties in favor of fewer Ands.
def simplify(target_formula):
critical_value_sets = get_critical_value_sets(target_formula)
inputs = enumerate_truth_table_inputs(critical_value_sets)
target_truth_table = get_truth_table(target_formula, inputs)
best = {}
merge = Merge()
for formula in enumerate_useful_primitives(critical_value_sets):
merge.update([HeapItem(formula)])
best_formulas = []
for item in merge:
if target_truth_table in best:
return best[target_truth_table]
formula = item.formula
truth_table = get_truth_table(formula, inputs)
if truth_table in best:
continue
n = len(best_formulas)
for op in [And, Or]:
merge.update(Combinations(op, formula, best_formulas))
best[truth_table] = formula
best_formulas.append(formula)
print(simplify(first_example))
print(simplify(second_example))
print(simplify(third_example))
print(simplify(fourth_example))
print(simplify(fifth_example))
Output:
Eq(lhs='A', rhs=6)
And(lhs=Lt(lhs='C', rhs=3), rhs=Gt(lhs='B', rhs=2))
And(lhs=And(lhs=Gt(lhs='B', rhs=1), rhs=Gt(lhs='A', rhs=0)), rhs=Or(lhs=Gt(lhs='B', rhs=2), rhs=Gt(lhs='A', rhs=1)))
True
Or(lhs=And(lhs=Eq(lhs='B', rhs=2), rhs=Lt(lhs='C', rhs=2)), rhs=And(lhs=Gt(lhs='C', rhs=2), rhs=Eq(lhs='A', rhs=2)))
Maybe you can consider intervals for your variables, for example:
(A > 5) && (A == 6)
Given you have a variable A, set an initial interval for it: A: [-∞, ∞].
Each condition that you read, you can reduce your interval:
(A > 5) sets the interval for A: [6, ∞]
(A == 6) sets the interval for A: [6, 6]
For each update on the interval, check if the new condition is possible, for example:
(A > 5) sets the interval for A: [6, ∞]
(A == 5) out of the interval, impossible condition.
Just another example:
((B > 2) && (C == 2)) || ((B > 2) && (C < 2))
Initially: B: [-∞, ∞] and C: [-∞, ∞].
((B > 2) && (C == 2))
(B > 2) sets the interval for B: [3, ∞]
(C == 2) sets the interval for C: [2, 2]
The next condition is attached with ||, so you add intervals:
((B > 2) && (C < 2))
(B > 2) sets the interval for B: [3, ∞]
(C < 2) sets the interval for C: [2, 2] U [-∞, 1] = [-∞, 2]

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! :)

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|).

What is the explanation for length of a block?

I am wondering about this line:
{ |i| (num % i) == 0}.length == 0
Can someone explain what happens using length on a block? I am trying to answer a rubeque problem about prime factors:
divisors = Array.new
for d in 2..(num)
divisors << d if (num % d) == 0
end
primes = divisors.select do |num|
(2..(num-1)).select { |i| (num % i) == 0}.length == 0
end
It's calling length on the result of (2..(num-1)).select { |i| (num % i) == 0} — which is an array — not on the block.

Ruby Koan 151 raising exceptions

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)

Resources