Why parentheses change returning value? - ruby

I'm learning ruby and I got stuck with probable simple problem. There is the code:
str = 'abc'
a = 1
b = 2
a = str.reverse if str.size > 3
b = (str.reverse if str.size > 3)
p a
p b
Output:
1
nil
Can somebody tell me what these parentheses change in return value ? Is it kind of "block" of code ?

They are two different statements.
The first one is a conditional assignment:
a = str.reverse if str.size > 3
The if applies to the whole line. If str.size is not greater than 3, then absolutely nothing happens; a is not touched at all. You could also write it this way:
if str.size > 3 then
a = str.reverse
end
Being able to stick the if on the end just lets you do it in one line instead of a block.
The second one is an assignment of a conditional value.
b = (str.reverse if str.size > 3)
The value of b will always be changed in this case, no matter what; the value of str.size just determines what it is changed to. You could also use the block-form of if here:
b = if str.size > 3 then
str.reverse
end
The important difference is that the assignment to b happens outside the if, so it's not conditional; it always happens.
Parentheses don't create blocks, but they do determine precedence. Whenever you have a statement that could be interpreted multiple ways depending on the order in which things happen, what actually happens is determined by precedence rules. For instance, standard arithmetic rules tell us that this is true:
3 + 2 × 4 = 11
The answer isn't 20 because multiplication has precedence over addition. Parentheses let you change that; the above is equivalent to
3 + (2 × 4) = 11
and if you want the answer to be 20, then you could write this instead:
(3 + 2) × 4 = 20
Same thing goes for Ruby. Without parentheses, the first line is equivalent to this parenthesized version:
(a = str.reverse) if str.size > 3
which makes it clear that the assignment is what is guarded by the condition, not just the value being assigned.

Parentheses will change priority during operation.
Code between parenthesis will be evaluated as boolean due to its status as conditional expression.
Like #Mark Reed said, there are differents statements. And it is up to your intention to use the correct one. Hope it helped. ;)

Related

Dynamic constant assignment main.rb:6: Ruby [duplicate]

This question already has answers here:
Dynamic constant assignment
(7 answers)
Closed 2 years ago.
I get that error while naming variables in my loop. The point of the method is to print the index position of the only element with unique parity in the array. For example the method should print "3" for the following input array because it is the only odd number and everything else is even: [2, 4, 6, 3, 8, 10]. More specifically, it points out the error to "Odd" and "Even" variables below ("main.rb:7: dynamic constant assignment Odd = numbers[i] % 2 == 1").
def test(numbers)
i=1
countOdd = 0
countEven = 0
Odd = numbers[i] % 2 == 1
Even = numbers[i] % 2 == 0
while i < numbers.length
if Odd
countOdd += 1
else countEven +=1
end
i+=1
end
if countEven == 1
print Odd.index
else print Even.index
end
end
When you define a Capitalized variable in Ruby, that is a constant - it is a special kind of variable that is not allowed to change value (well, technically you can change it with const_set, but that's not really relevant here).
Because of this limitation, Ruby won't allow you to change constants from within functions. It assumes the function will be called many times, which would cause the constant to change value, which as I just mentioned is illegal.
So, quick fix, just replace your Odd and Even with the lowercase versions odd and even. That way they're regular variables and not constants.
the above code could be return easily using the built in array methods:
even_numbers = numbers.select(&:even?)
odd_numbers = numbers.select(&:odd?)
then the even counts will be even_numbers.count, and similarly odd_numbers.count
the first odd number index will be numbers.find_index(odd_numbers.first)
for the constants you can assign a proc to the constant :
Odd = Proc.new{ |n| n%2 == 1 } then call the constant like this: Odd.call(10) #=> false, similarly you can define a proc for even numbers.
what happening here is every time the method get called the constant will be redefined thus having a new value, but constants are meant to hold a value that would not change, and that is the cause of the error.
also note that the following condition in your method:
if Odd
then do something
....
is not actually correct. once the constant holds a value then it will have that value during execution. so after doing Odd = some_integer % 2 == 1 the Odd constant will be either true or false always. and will not re-execute some_integer % 2 == 1 in your if condition. but if you make it a proc or lamda it well re-do the calculation because it wil be as if you are calling a method.
[NOTE] however doing Odd = Proc.new{ |n| n%2 == 1 } inside your method will still give you the same error dynamic constant assignment Odd = Proc.new { |n| numbers[n] % ... since still the code is re-executed every time the method runs giving the constant different values each time. so by putting Odd = Proc.new{ |n| n%2 == 1 } definitions outside of the method then doing if Odd.call(i) it will work just fine.

How could I DRY this while loop?

I need to DRY this code but I don't know how.
I tried to dry the if condition but I don't know how to put the while in this.
def sum_with_while(min, max)
# CONSTRAINT: you should use a while..end structure
array = (min..max).to_a
sum = 0
count = 0
if min > max
return -1
else
while count < array.length
sum += array[count]
count += 1
end
end
return sum
end
Welcome to stack overflow!
Firstly, I should point out that "DRY" stands for "Don't Repeat Yourself". Since there's no repetition here, that's not really the problem with this code.
The biggest issue here is it's unrubyish. The ruby community has certain things it approves of, and certain things it avoids. That said, while loops are themselves considered bad ruby, so if you've been told to write it with a while loop, I'm guessing you're trying to get us to do your homework for you.
So I'm going to give you a couple of things to do a web search for that will help start you off:
ruby guard clauses - this will reduce your if-else-end into a simple if
ruby array pop - you can do while item = array.pop - since pop returns nil once the array is empty, you don't need a count. Again, bad ruby to do this... but maybe consider while array.any?
ruby implicit method return - generally we avoid commands we don't need
It's worth noting that using the techniques above, you can get the content of the method down to 7 reasonably readable lines. If you're allowed to use .inject or .sum instead of while, this whole method becomes 2 lines.
(as HP_hovercraft points out, the ternary operator reduces this down to 1 line. On production code, I'd be tempted to leave it as 2 lines for readability - but that's just personal preference)
You can put the whole thing in one line with a ternary:
def sum_with_while(min, max)
min > max ? -1 : [*(min..max)].inject(0){|sum,x| sum + x }
end
This is one option, cleaning up your code, see comments:
def sum_with_while(range) # pass a range
array = range.to_a
sum, count = 0, 0 # parallel assignment
while count < array.length
sum += array[count]
count += 1
end
sum # no need to return
end
sum_with_while(10..20)
#=> 165
More Rubyish:
(min..max).sum
Rule 1: Choose the right algorithm.
You wish to compute an arithmetic series.1
def sum_with_while(min, max)
max >= min ? (max-min+1)*(min+max)/2 : -1
end
sum_with_while(4, 4)
#=> 4
sum_with_while(4, 6)
#=> 15
sum_with_while(101, 9999999999999)
#=> 49999999999994999999994950
1. An arithmetic series is the sum of the elements of an arithmetic sequence. Each term of the latter is computed from the previous one by adding a fixed constant n (possibly negative). Heremax-min+1 is the number of terms in the sequence and (min+max)/2, if (min+max) is even, is the average of the values in the sequence. As (max-min+1)*(min+max) is even, this works when (min+max) is odd as well.

What's the proper way to use the <=> operator in Ruby? [duplicate]

This question already has answers here:
What is the Ruby <=> (spaceship) operator?
(6 answers)
Closed 4 years ago.
I don't quite understand how this works. I guess a large part of it is because I'm used to C and its low-level data structures, and programming at a higher level of abstraction takes some getting used to. Anyway, I was reading The Ruby Programming Language, and I came to the section about ranges and how you can use the <=> operator as sort of a shorthand for what in C you would have to implement as a sequence of if-else statements. It returns either -1, 0, or 1 depending on the results of the comparison. I decided to try it out with the following statement:
range = 1..100
r = (100 <=> range)
print( r )
The result is an empty string. So my question is, how does this operator work; what data type does it return (I assume the return value is an integer type but I can't say for sure); and finally what is the proper way to use it? Thanks for your time, and sorry if this was already answered (I didn't see it in the listing).
The <=> operator is meant to compare two values that can be compared directly, as in strings to strings or numbers to numbers. It can't magically compare two different things, Ruby doesn't convert for you like that.
As such you need to use it in the right context:
1 <=> 2
# => -1
2 <=> 1
# => 1
1 <=> 1
# => 0
When the two things can't be compared you get nil. Note that this is different from "empty string":
1 <=> '1'
# => nil
That means "invalid comparison". The <=> operator is being nice here because in other situations you get an exception:
1 < '1'
# => ArgumentError: comparison of Integer with String failed
You can also use this operator to make your own Comparable compatible class:
class Ordered
include Comparable
attr_reader :sequence
def initialize(sequence)
#sequence = sequence
end
def <=>(other)
self.sequence <=> other.sequence
end
end
Then you can do this:
a = Ordered.new(10)
b = Ordered.new(2)
[ a, b ].sort
# => [#<Ordered:0x00007ff1c6822b60 #sequence=2>, #<Ordered:0x00007ff1c6822b88 #sequence=10>]
Where those come out in order. The <=> implementation handles how these are sorted, and you can finesse that depending on how complex your sorting rules are.
Using the return values -1, 0, and 1 only as labels describing different states, you can write a condition that depends on the order between two numbers:
case a <=> b
when -1 then # a is smaller than b. Do something accordingly
when 0 then # a equals b. Do something accordingly
when 1 then # a is larger than b. Do something accordingly
end
Or, a use case where you can make use of the values -1, 0, and 1, is when you want to get the (non-negative) difference between two numbers a and b without using the abs method. The following:
(a - b) * (a <=> b)
will give the difference.
Add to the other answers this snippet: The "spaceship operator" returns -1, 0, or 1 so you can use it when comparing items in a .sort call:
events.sort {|x, y| y.event_datetime <=> x.event_datetime}
0 means the two items are the same, 1 means they are different but in the correct sort order, and -1 means they are out of order. The above example reverses x and y to sort into descending order.
In C, the function strcmp() has roughly the same behavior, to fit with qsort(), with the same semantics.

"Or" in If Blocks in Ruby

So I am writing a Ruby program for school that changes the value of a boolean to true if a certain value is either 1 or 3, and to false if it is 0 or 2. Since I come from a Java background, I thought that this code should work: if n == 1 || n == 3
But it does not. So my question here is is it possible to use an "Or" expression thingy in If blocks in Ruby? I know that my current situation could be solved easily by just something like the following:
if n == 0
t_o_f = false
elsif n == 1
t_o_f = true
Et Cetera. But I want to know if I can use an Or in If blocks for the future.
Yes, any expression can be used in an if condition, including those using the || (logical or) operator.
As with Java, Ruby's || operator short-circuits. That is, if the left side is true, the right side is not evaluated.
Idiomatic ruby uses postfix if for one-liners:
puts "Yes" if n == 4 || n == 5
Avoid postfix if the line is long, however, and break it into multiple lines:
if n == 4 || n == 5
puts "This is a big long string to be output....................."
end
That's because postfix if can get visually lost at the end of a long line.
You can have a one-liner with prefix if, but it's not commonly seen:
if n == 4 || n == 5 then puts "foo" end
or, perhaps:
if n == 4 || n == 5 ; puts "foo" ; end
This is really a multi-line if condensed into one line; the semicolons act as line breaks.
When testing for multiple conditions, it can sometimes be valuable to use Array#include?:
puts "foo" if [4, 5].include?(n)
The value of this for only two conditions is not that great. For three or more, it reads well.
The or operator works, and you can write the keyword too:
if n==1 or n==2
end
http://www.tutorialspoint.com/ruby/ruby_operators.htm
And you could also write what you want this way:
x = (n%2) ? true : false
In addition to #Wayne Conrad: if there is little or no 'logic' deciding if n is true or false, then this is also a good option:
result = case n
when 1, 3 then true
when 0, 2 then false
end

Remove value from Variable

This is probably a very trivial question, but..
Assuming we have a variable 'a' that has a value of 10
Then we have a variable 'b' that has a value of 5
Can I remove the value of 'b' from 'a' (leaving 5) and change the value of a each time?
In my head I think of it as:
a - b = 5
a - b = 0
a - b = -5
etc etc..
Thanks!
Consider this:
a = 10
b = 5
a = a - b
Now the value of a is 5. This is because Ruby evaluates what is on the right side of the assignment operator = first and then assigns that value to whatever is on the left side.
#Another way to put it
a = (a - b)
There is also a shortcut since this type of math is very common
a = a - b
a -= b
# These are both the same
If you want to do this a certain number of times, try a loop.
a = 10
b = 5
3.times do {a -= b}
# a is now -5
For further basic Ruby learning, I would suggest Try Ruby to get you started on concepts as I mentioned above. (Type "next" there to get started)

Resources