A more elegant solution to Ruby Koans' triangle.rb - ruby

I have been working through Ruby Koans and made it to about_triangle_project.rb in which you are required to write the code for a method, triangle.
Code for these items are found here:
https://github.com/edgecase/ruby_koans/blob/master/koans/about_triangle_project.rb
https://github.com/edgecase/ruby_koans/blob/master/koans/triangle.rb
In triangle.rb, I created the following method:
def triangle(a, b, c)
if ((a == b) && (a == c) && (b == c))
return :equilateral
elsif ((a == b) || (a == c) || (b == c))
return :isosceles
else
return :scalene
end
end
I know from reading Chris Pine's "Learn to Program" there is always more than one way to do things. Although the above code works, I can't help but think there is a more elegant way of doing this. Would anyone out there be willing to offer their thoughts on how they might make such a method more efficient and compact?
Another thing I am curious about is why, for determining an equilateral triangle, I was unable to create the condition of (a == b == c). It is the proof for an equilateral triangle but Ruby hates the syntax. Is there an easy explanation as to why this is?

There is an easy explanation for why that is:
== in Ruby is an operator, which performs a specific function. Operators have rules for determining what order they're applied in — so, for example, a + 2 == 3 evaluates the addition before the equality check. But only one operator at a time is evaluated. It doesn't make sense to have two equality checks next to each other, because an equality check evaluates to true or false. Some languages allow this, but it still doesn't work right, because then you'd be evaluating true == c if a and b were equal, which is obviously not true even if a == b == c in mathematical terms.
As for a more elegant solution:
case [a,b,c].uniq.size
when 1 then :equilateral
when 2 then :isosceles
else :scalene
end
Or, even briefer (but less readable):
[:equilateral, :isosceles, :scalene].fetch([a,b,c].uniq.size - 1)

Another approach:
def triangle(a, b, c)
a, b, c = [a, b, c].sort
raise TriangleError if a <= 0 or a + b <= c
return :equilateral if a == c
return :isosceles if a == b or b == c
return :scalene
end

I borrowed Chuck's cool uniq.size technique and worked it into an oo solution. Originally I just wanted to extract the argument validation as a guard clause to maintain single responsibility principle, but since both methods were operating on the same data, I thought they belonged together in an object.
# for compatibility with the tests
def triangle(a, b, c)
t = Triangle.new(a, b, c)
return t.type
end
class Triangle
def initialize(a, b, c)
#sides = [a, b, c].sort
guard_against_invalid_lengths
end
def type
case #sides.uniq.size
when 1 then :equilateral
when 2 then :isosceles
else :scalene
end
end
private
def guard_against_invalid_lengths
if #sides.any? { |x| x <= 0 }
raise TriangleError, "Sides must be greater than 0"
end
if #sides[0] + #sides[1] <= #sides[2]
raise TriangleError, "Not valid triangle lengths"
end
end
end

def triangle(a, b, c)
if a == b && a == c # transitivity => only 2 checks are necessary
:equilateral
elsif a == b || a == c || b == c # == operator has the highest priority
:isosceles
else
:scalene # no need for return keyword
end
end

class TriangleError < StandardError
end
def triangle(a, b, c)
sides = [a,b,c].sort
raise TriangleError if sides.first <= 0 || sides[2] >= sides[1] + sides[0]
return :equilateral if sides.uniq.length == 1
return :isosceles if sides.uniq.length == 2
:scalene
end

Here is my solution:
def triangle(a, b, c)
sides = [a, b, c].sort
raise TriangleError, "Invalid side #{sides[0]}" unless sides[0] > 0
raise TriangleError, "Impossible triangle" if sides[0] + sides[1] <= sides[2]
return [:scalene, :isosceles, :equilateral][ 3 - sides.uniq.size ]
end

Hmm.. I didn't know about uniq - so coming from smalltalk (ages ago) I used:
require 'set'
def triangle(a, b, c)
case [a, b, c].to_set.count
when 1 then :equilateral
when 2 then :isosceles
else :scalene
end
end

Coming from the matlab world, I am used to array functions 'any' and 'all', and was happy enough to find them in Ruby. So:
def triangle(a, b, c)
eqs = [a==b, a==c, b==c]
eqs.all?? :equilateral : eqs.any?? :isosceles : :scalene
end
No idea whether that's optimal though, in terms of readability, computation time... (ruby noob).

Here is my solution:
def triangle(a, b, c)
return :equilateral if a == b and b == c
return :isosceles if ( a == b or b == c or a == c )
return :scalene
end

Related

Conditional to compare three values

I am trying to determine if there is a way for me to write an if statement that will result in true if three values are equal to each other.
EX:
if a == b == c
puts "true"
end
Instead of having to write:
if a == b && a == c && b == c
You don't need all 3, if a equal b and c equal b it implies a equal c.
If you need it more scalable 3+ arguments, you can do:
Set[a, b, c].size == 1
Original answer was:
Set[a, b, c].one?
But it one? does not count nil or false elements. So Set[nil, nil].one? will be false.
Thanks to Sagar Pandya for pointing it out in the comments.
What about this?
[a, b, c].uniq.size == 1
As #Bodacious mentioned a == b && b == c would work due to the transitive property.
But if you have many items, you could do something like:
values = [a, b, c]
if values.all? { |value| value == value[0] }
puts "true"
end
The all? method returns true if a block returns true for every item. So in that block, check to see if every item is equal to the first item.

Evaluation of multiple conditions

I have a quick question :
In ruby, If I write
def test
foo && a == b && c == "bar"
end
if foo is null or false, will it keep evaluating the rest of the expression ?
Does it change anything if I do this instead
def test
a == b && c == "bar" if foo
end
thanks a lot
Theory
&& is a lazy operator, just like ||.
It means that in a && b, if a is false or nil, Ruby won't bother to check b because a && b will be false/nil anyway.
This behaviour is actually desired, because it saves time and could avoid NoMethodErrors.
if a && method_which_requires_a_non_nil_parameter(a)
# ...
end
method_which_requires_a_non_nil_parameter wouldn't be called at all if a is nil.
or :
x = x || long_method_to_calculate_x
is often used for caching, more often written as :
#x ||= long_method_to_calculate_x
Answer
def test
foo && a == b && c == "bar"
end
The rest of the expression won't be evaluated if foo is nil or false :
a, b, c could even be undefined without raising a NameError.
def test
a == b & c == "bar" if foo
end
If foo is truthy, the results will be exactly the same.
If foo is nil or false, the 2 equalities won't be evaluated, just like with the first example. But :
If foo is nil, both test will return nil.
If foo is false, first example will return false, second example will return nil.
"If foo is null or false, will it keep evaluating the rest of the expression?"
No, it will not
This table should help you in such questions:
The following table is ordered according to descending precedence (highest precedence at the top)
N A M Operator(s) Description
- - - ----------- -----------
1 R Y ! ~ + boolean NOT, bitwise complement, unary plus
(unary plus may be redefined from Ruby 1.9 with +#)
2 R Y ** exponentiation
1 R Y - unary minus (redefine with -#)
2 L Y * / % multiplication, division, modulo (remainder)
2 L Y + - addition (or concatenation), subtraction
2 L Y << >> bitwise shift-left (or append), bitwise shift-right
2 L Y & bitwise AND
2 L Y | ^ bitwise OR, bitwise XOR (exclusive OR)
2 L Y < <= >= > ordering
2 N Y == === != =~ !~ <=> equality, pattern matching, comparison
(!= and !~ may not be redefined prior to Ruby 1.9)
2 L N && boolean AND
2 L N || boolean OR
2 N N .. ... range creation (inclusive and exclusive)
and boolean flip-flops
3 R N ? : ternary if-then-else (conditional)
2 L N rescue exception-handling modifier
2 R N = assignment
2 R N **= *= /= %= += -= assignment
2 R N <<= >>= assignment
2 R N &&= &= ||= |= ^= assignment
1 N N defined? test variable definition and type
1 R N not boolean NOT (low precedence)
2 L N and or boolean AND, boolean OR (low precedence)
2 N N if unless while until conditional and loop modifiers

How do I create arithmetic statements using variables?

I'm trying to create a method to check if three variables a, b and c are a pythagorean triplet. I set it up with a known triplet: 3, 4, 5. This program won't run though and I can't figure out why.
a = 3
b = 4
c = 5
def triplet?
if a**2 + b ** 2 == c ** 2
puts 'pythagorean triplet'
else puts 'not a pythagorean triplet'
end
end
triplet?
It returns the error message:
undefined local variable or method `a' for main:Object (NameError)
Any help will be much appreciated.
a, b, and c are local to the scope they're defined in, and thus aren't visible to separate scopes (such as other methods). See the doc on Object#def:
Starts a new local scope; local variables in existence when the def block is entered are not in scope in the block, and local variables created in the block do not survive beyond the block.
What you want to do is pass numbers as parameters:
def triplet?(a, b, c)
if a**2 + b ** 2 == c ** 2
puts 'pythagorean triplet'
else puts 'not a pythagorean triplet'
end
end
triplet?(3, 4, 5)
This will define those three variables in the scope of the triplet? method, then you populate their values by passing them when you invoke the method.
A small point of note, by convention, predicate methods (that is, methods ending in ?) in Ruby conventionally return a boolean. To write this method idiomatically, you might say:
def triplet?(a, b, c)
a**2 + b ** 2 == c ** 2
end
if triplet?(3, 4, 5)
puts 'pythagorean triplet'
else
puts 'not a pythagorean triplet'
end
That way, triplet? will always return a boolean true or false, then you can use it in your code to write English-y sentences.
Within the definition block, which is the scope for local variables, a is not defined, hence the error message.
a = 3
b = 4
c = 5
def triplet?(a, b, c)
if a**2 + b ** 2 == c ** 2
puts 'pythagorean triplet'
else
puts 'not a pythagorean triplet'
end
end
triplet?(a, b, c)
def creates a function. Inside the function block, you have a scope. a, b, and c are not in that scope. Tell the function to take parameters a, b, c and pass it the parameters.
There is no relation between the name you give the function parameters and the function parameters you pass.
The following will also work:
x = 3
y = 4
z = 5
def triplet?(a, b, c)
if a**2 + b ** 2 == c ** 2
puts 'pythagorean triplet'
else
puts 'not a pythagorean triplet'
end
end
triplet?(x, y, z)

Write an algorithm that tells me if two and only two numbers in three are the same

Deceptively simple algorithm question I came across. I'm trying to pull it off in 3 or less operations, and I'm reasonably convinced it can be solved with math, but I can't figure it out (and the source for the question didn't have an answer).
EDIT:
(a[0] == a[1] + a[0] == a[2] + a[1] == a[2]) == 1
is what I originally thought, but I'd like to see if it can be done in less operations (1 comparison being an operation).
Assuming the 3 numbers are a, b and c,
(b == c) ? (a != c) : (a == b || a == c)
If (a, b, c) = (1, 1, 1), then we will call b == c (true) and then a != c (false) and done.
If (a, b, c) = (1, 1, 2), then we will call b == c (false) and then a == b (true) and done.
If (a, b, c) = (1, 2, 1), then we will call b == c (false) and then a == b (false) and a == c (true) and done.
If (a, b, c) = (2, 1, 1), then we will call b == c (true) and then a != c (true) and done.
If (a, b, c) = (1, 2, 3), then we will call b == c (false) and then a == b (false) and a == c (false) and done.
So at most 3 comparison are performed.
There are also 2 conditional branching points at ?: and || but OP does not count it.
Depending on what you consider to be an "operation"...
The following uses only 3 comparisons out of the array. There is a forth comparison though, the == 1 to ensure that there is exactly one match. I believe you could use a ton of branching to conditionally eliminate some of the comparisons, but if this is an optimization, the branching would probably make it perform worse.
There are exactly 3 outcomes:
none of the values will be the same (sum is zero)
two will be the same (sum is one)
all three are the same (sum is three)
if (((array[0] == array[1]) +
(array[1] == array[2]) +
(array[0] == array[2])) == 1)
{
// stuff
}
This trades comparisons with branching to achieve a maximum of 3 comparisons and a routes that only requires 2:
if (array[0] == array[1]) // if these are equal
return !(array[1] == array[2]); // and these are not equal, return true
else
return (array[0] == array[2]) || (array[1] == array[2]); // otherwise, if these are equal, we already know the others are not equal because we already tested them so return true
You can write the expression:
((b == a) | (b == c)) ^ (a == c)
which has constant cost, always performs three comparisons and two logic operations. Having no branches, it ought to go easy on the processor.
Depending on the architecture,
((b == a) || (b == c)) ^ (a == c)
might be faster (this one performs two or three comparisons, one logic operation and one branch).
My try...
return (ab ? (!ac) : (ac ? true : bc));
Where:
ab = (a==b)
ac = (a==c)
bc = (b==c)
This uses 2 or 3 comparisons, at the expense of conditional jumps sometimes. Let us check the number of operations on each case:
a == c == b: (a==b) + jump + (a==c) + negation [returns a!=c] 4 operations
a == b != c: the same as above, 4 operations
a != b == c: (a==b) + jump + (a==c) + jump + (b==c) [returns this value] 5 operations
a == c != b: (a==b) + jump + (a==c) + jump [returns true] 4 operations
a != c != b: the same as above, 4 operations
Of course, this depends on your concept of operation... If jumps are not considered...

Is == a special method in Ruby?

I understand that x == y in Ruby interpreted as a.==(y). I tried to check if I can achieve the same with custom method, foo, like this:
class Object
def foo(n)
self == n
end
end
class A
attr_accessor :x
end
a = A.new
a.x = 4
puts a.x.==(4) # => true
puts a.x.foo(4) # => true
puts a.x == 4 # => true
puts a.x foo 4 # => in `x': wrong number of arguments (1 for 0) (ArgumentError)
Unfortunately, this doesn't work. What am I missing ? Is == a special method in Ruby ?
No, == is not a special method in Ruby. It's a method like any other. What you are seeing is simply a parsing issue:
a.x foo 4
is the same as
a.x(foo(4))
IOW, you are passing foo(4) as an argument to x, but x doesn't take any arguments.
There is, however, special operator syntax, which allows you to write
a == b
instead of
a.== b
for a limited list of operators:
==
!=
<
>
<=
>=
<=>
===
&
|
*
/
+
-
%
**
>>
<<
!==
=~
!~
Also, there is special syntax that allows you to write
!a
and
~a
instead of
a.!
and
a.~
As well as
+a
and
-a
instead of
a.+#
and
a.-#
Then, there is
a[b]
and
a[b] = c
instead of
a.[] b
and
a.[]= b, c
and last but not least
a.(b)
instead of
a.call b
Methods that are operators are treated specially in Ruby, at least syntax-wise. The language is not as flexible as, say, in Haskell where you can turn any function into an infix operator by enclosing its name in backticks: the list on infix operators are pre-determined.
One of the problems that would arise from custom infixes is the handling of operator precedence and associativity: for eaxmple, how would the parser handle a statement like:
a foo b == c # should this be (a foo b) == c or a foo (b == c)

Resources