Idiomatic ruby for temporary variables within a method - ruby

Within a method, I am using i and j as temporary variables while calculating other variables. What is an idiomatic way of getting rid of i and j once they are no longer needed? Should I use blocks for this purpose?
i = positions.first
while nucleotide_at_position(i-1) == nucleotide_at_position(i)
raise "Assumption violated" if i == 1
i -= 1
end
first_nucleotide_position = i
j = positions.last
while nucleotide_at_position(j+1) == nucleotide_at_position(j)
raise "Assumption violated" if j == sequence.length
j += 1
end
last_nucleotide_position = j
Background: I'd like to get rid of i and j once they are no longer needed so that they aren't used by any other code in the method. Gives my code less opportunity to be wrong. I don't know the name of the concept - is it "encapsulation"? The closest concepts I can think of are (warning: links to TV Tropes - do not visit while working) Chekhov'sGun or YouHaveOutlivedYourUsefulness.
Another alternative would be to put the code into their own methods, but that may detract from readability.

What makes you think splitting the code up into multiple methods will hurt the readability? In my experience, splitting even small or medium sized pieces of code into multiple methods can greatly improve readability.

Ruby (like JS) doesn't create a new scope for each block by default (as C++, etc. do). However, in Ruby 1.9, you can try:
last_nucleotide_position = nil
proc { |;i, j|
i = positions.first
while nucleotide_at_position(i-1) == nucleotide_at_position(i)
raise "Assumption violated" if i == 1
i -= 1
end
first_nucleotide_position = i
j = positions.last
while nucleotide_at_position(j+1) == nucleotide_at_position(j)
raise "Assumption violated" if j == sequence.length
j += 1
end
last_nucleotide_position = j
}.call()
See How to make block local variables the default in ruby 1.9?. Any variables that you want to be used outside the block should be defined before-hand (like last_nucleotide_position).
FM is right that a separate method may be more readable.

I think the term you are looking for is variable scope -- in other words, you are looking for ways to confine the scope of i and j. But you don't need to worry about that. The problem at hand calls for creating separate methods -- regardless of scope considerations.
This will improve readability, because it will allow the reader to grok the code starting at the high level and then boring in deeper only as needed. It will also improve testability because your small methods will do exactly one thing.
def calc_first_nucleotide_position(po)
i = po.first
while nucleotide_at_position(i-1) == nucleotide_at_position(i)
raise "Assumption violated" if i == 1
i -= 1
end
i
end
# etc...
first_nucleotide_position = calc_first_nucleotide_position(positions)
last_nucleotide_position = calc_last_nucleotide_position(positions)
# etc...

You are looking for the Ruby equivalent of Lisp's let special operator. Ruby does not support it out of the box but you can hack it in very easily, and the resulting syntax is like this:
x = 10
scope { |x|
x = 30
}
puts x #=> 10
see: http://banisterfiend.wordpress.com/2010/01/07/controlling-object-scope-in-ruby-1-9/

If all you want is to keep new variables from spilling out into the rest of your program, you can wrap your code in a block using 1.times. Any new variables you create inside the block will be destroyed when you close the block. Just keep in mind that any changes you make to pre-existing variables will remain once the block closes.
y = 20
1.times do
# put your code in here
i = 1
puts x = y # => 20, because y is available from outside the block
y = 'new value' # We can change the value of y but our changes will
# propagate to outside the block since y was defined before we opened
# the block.
end
defined? i # => nil, i is lost when you close the block
defined? x # => nil, x is also local to the block
puts y # => 'new value'

Related

How do I destructure a range in Ruby?

Is it possible to use destructuring in ruby to extract the end and beginning from a range?
module PriceHelper
def price_range_human( range )
"$%s to $%s" % [range.begin, range.end].map(:number_to_currency)
end
end
I know that I can use array coercion as a really bad hack:
first, *center, last = *rng
"$%s to $%s" % [first, last].map(:number_to_currency)
But is there a syntactical way to get begin and end without actually manually creating an array?
min, max = (1..10)
Would have been awesome.
You can use minmax to destructure ranges:
min, max = (1..10).minmax
min # => 1
max # => 10
If you are using Ruby before 2.7, avoid using this on large ranges.
The beginning and end? I'd use:
foo = 1..2
foo.min # => 1
foo.max # => 2
Trying to use destructuring for a range is a bad idea. Imagine the sizes of the array that could be generated then thrown away, wasting CPU time and memory. It's actually a great way to DOS your own code if your range ends with Float::INFINITY.
end is not the same as max: in 1...10, end is 10, but max is 9
That's because start_val ... end_val is equivalent to start_val .. (end_val - 1):
start_value = 1
end_value = 2
foo = start_value...end_value
foo.end # => 2
foo.max # => 1
foo = start_value..(end_value - 1)
foo.end # => 1
foo.max # => 1
max reflects the reality of the values actually used by Ruby when iterating over the range or testing for inclusion in the range.
In my opinion, end should reflect the actual maximum value that will be considered inside the range, not the value used at the end of the definition of the range, but I doubt that'll change otherwise it'd affect existing code.
... is more confusing and leads to increased maintenance problems so its use is not recommended.
No, Until I am proven incorrect by Cary Swoveland, Weekly World News or another tabloid, I'll continue believing without any evidence that the answer is "no"; but it's easy enough to make.
module RangeWithBounds
refine Range do
def bounds
[self.begin, self.end]
end
end
end
module Test
using RangeWithBounds
r = (1..10)
b, e = *r.bounds
puts "#{b}..#{e}"
end
Then again, I'd just write "#{r.begin.number_to_currency}..#{r.end.number_to_currency}" in the first place.
Amadan's answer is fine. you just need to remove the splat (*) when using it since it is not needed
eg,
> "%s to %s" % (1..3).bounds.map{|x| number_to_currency(x)}
=> "$1.00 to $3.00"

How to create a custom while block

I know that in ruby we can use a while loop, but I want to know if I can create a custom one so I can make something like this:
custom_while i < 5 do
puts i
i += 1
end
I currently have this code:
def custom_while(condition)
loop do
break if not condition
yield
end
end
i = 0
custom_while i < 5 do
puts i
i += 1
end
However, when condition is evaluated, it always get true (because it considers the first evaluation of i < 5 = true only.
Any help will be appreciated!
Note: This is for educational purposes only.
You almost had it. So, your problem is that the condition is only evaluated once? Well, what construct do we know that we can evaluate as often as we want? That's right: functions! So, let's make condition a function (or a Proc in Ruby lingo):
def custom_while(condition)
loop do
break unless condition.()
yield
end
end
i = 0
custom_while -> { i < 5 } do
puts i
i += 1
end
# 0
# 1
# 2
# 3
# 4
This is unfortunately not as nice looking as in other languages. Ruby's syntax and semantics are aggressively optimized for methods that take exactly 1 "function" as an argument. Ruby has a special syntactically and semantically light-weight construct for that, namely blocks. As soon as you have more than one, though, you're out of luck.
Compare this with languages that have proper block literals, like Smalltalk, for example. In Smalltalk, you could write a method while:do:, and call it like this:
i := 0.
while: [i < 5] do: [Transcript write: i. i := i + 1].
In fact, in Smalltalk, the syntax for blocks is so lightweight that there are no control structures at all in the language. if/then/else is simply an instance method of Boolean, for example:
i % 2 == 0 ifTrue: [Transcript write: "even"] ifFalse: [Transcript write: "odd"].
And while is actually an instance method of Block, so in reality, your example would look like this:
i := 0.
[i < 5] whileTrue: [Transcript write: i. i := i + 1]
Note: I make no guarantees for the Smalltalk code, I didn't test it.
The problem is, the condition is being evaluated before it's passed in, so it will never change.
Make the condition a function that you evaluate inside the loop, or use a macro to make it cleaner.

Rubocop rule: Never use 'do' with multi-line 'while

I have the following code
# colours a random cell with a correct colour
def colour_random!
while true do
col, row = rand(columns), rand(rows)
cell = self[row,col]
if cell.empty? then
cell.should_be_filled? ? cell.colour!(1) : cell.colour!(0)
break
end
end
end
it's not that important what's doing, although it should pretty obvious. The point is that Rubocop gives me a warning
Never use 'do' with multi-line 'while
Why should I not do that? How should I do it then?
while is a keyword,so you don't need to pass a block. Without do..end it will work fine. The below is fine
def colour_random!
while true
col, row = rand(columns), rand(rows)
cell = self[row,col]
if cell.empty? then
cell.should_be_filled? ? cell.colour!(1) : cell.colour!(0)
break
end
end
end
while is a keyword, and if you pass a block to it, like do..end, it still works as you asked it to do, by not throwing any error, rather just a warning. But it could be dangerous if you try to pass a Proc or Method object to it, and dynamically try to convert it to a block using & keyword, as we do generally. That means
# below code will work as expected just throwing an warning.
x = 2
while x < 2 do
#code
end
But if you try to do by mistake like below
while &block # booom!! error
The reason is while is a keyword, which don't support any to_proc method to satisfy your need. So it can be dangerous.
Ruby style guide also suggested that Never use while/until condition do for multi-line while/until
I think the reason is as Nobuyoshi Nakada said in the mailing list
loop is a kernel method which takes a block. A block introduces new local variable scope.
loop do
a = 1
break
end
p a #=> causes NameError
while doesn't.
while 1
a = 1
break
end
p a #=> 1
Ruby actually has a shortcut for while true: the loop statement.
def colour_random!
loop do
col, row = rand(columns), rand(rows)
cell = self[row,col]
if cell.empty? then
cell.should_be_filled? ? cell.colour!(1) : cell.colour!(0)
break
end
end
end

Plus equals with ruby send message

I'm getting familiar with ruby send method, but for some reason, I can't do something like this
a = 4
a.send(:+=, 1)
For some reason this doesn't work. Then I tried something like
a.send(:=, a.send(:+, 1))
But this doesn't work too. What is the proper way to fire plus equals through 'send'?
I think the basic option is only:
a = a.send(:+, 1)
That is because send is for messages to objects. Assignment modifies a variable, not an object.
It is possible to assign direct to variables with some meta-programming, but the code is convoluted, so far the best I can find is:
a = 1
var_name = :a
eval "#{var_name} = #{var_name}.send(:+, 1)"
puts a # 2
Or using instance variables:
#a = 2
var_name = :#a
instance_variable_set( var_name, instance_variable_get( var_name ).send(:+, 1) )
puts #a # 3
See the below :
p 4.respond_to?(:"+=") # false
p 4.respond_to?(:"=") # false
p 4.respond_to?(:"+") # true
a+=1 is syntactic sugar of a = a+1. But there is no direct method +=. = is an assignment operator,not the method as well. On the other hand Object#send takes method name as its argument. Thus your code will not work,the way you are looking for.
It is because Ruby doesn't have = method. In Ruby = don't work like in C/C++ but it rather assign new object reference to variable, not assign new value to variable.
You can't call a method on a, because a is not an object, it's a variable, and variables aren't objects in Ruby. You are calling a method on 4, but 4 is not the thing you want to modify, a is. It's just not possible.
Note: it is certainly possible to define a method named = or += and call it, but of course those methods will only exist on objects, not variables.
class Fixnum
define_method(:'+=') do |n| self + n end
end
a = 4
a.send(:'+=', 1)
# => 5
a
# => 4
This might miss the mark a bit, but I was trying to do this where a is actually a method dynamically called on an object. For example, with attributes like added_count and updated_count for Importer I wrote the following
class Importer
attr_accessor :added_count, :updated_count
def increment(method)
send("#{method}=", (send(method) + 1))
end
end
So I could use importer.increment(:added_count) or importer.increment(:updated_count)
Now this may seem silly if you only have these 2 different counters but in some cases we have a half dozen or more counters and different conditions on which attr to increment so it can be handy.

Problem comprehending C-style ruby loops

I find the .each do hard to get to stick, so I was hoping for regular use of C for loop syntax which seems to not work, so I tried a while but still get errors.
I have tried this.
i = 0
while i < SampleCount
samples[i] = amplitude
amplitude *= -1
i++
end
I get complaints about the end statement here.
There are several problems with your code. Rather than just fixing the errors, I'd suggest it's better long-term for you to learn the Ruby way - it will save you time and energy later. In this case, it's
5.times do |i|
samples[i] = amplitude # assumes samples already exists and has 5 entries.
amplitude *= -1
end
If you insist on keeping a similar style, you can do this:
samples = []
i = 0
while i < sample_count
samples << amplitude # add new item to array.
amplitude *= -1
i += 1 # you can't use ++.
end
Note that SampleCount's initial capital letter, by Ruby convention, means a constant, which I'm guessing isn't what you really mean.
I agree with Peter that there are other (more idiomatic) ways to do this in Ruby, but just to be clear: the error message you saw misdirected you. There wasn't anything wrong with your while loop per se. The problem was i++ since there is no ++ operator in Ruby.
This would work just fine:
limit = 10
i = 0
while i < limit
puts i
i += 1
end
Again, I'm not recommending it, but if you're just learning the language, it may help to know where the problem really was.
Ruby has a lot of built-in ways to iterate other than for or while (which tend to be seen less often, as far as I can tell). A few other examples:
(1..10).each do |x| # 1..10 is a range which you can iterate over with each
puts x
end
1.upto(10) { |x| puts x } # Integers have upto and downto methods that can be useful
You originally mentioned trying to use a for loop. Notwithstanding the various other comments in the answers, here's the for loop approach:
for i in 0...5
samples[i] = amplitude
amplitude *= -1
end
Nobody here has actually offered an alternate solution that actually does what Fred originally intended - and that's iterate around the value of the constant SampleCount. So could you do:
SampleCount.times do |i|
Or:
limit = SampleCount
limit.times do |i|
Or:
for i in 0..SampleCount
Would any of those be Ruby-esque enough?
The problem with the end statement is related to i++. Ruby wants to add something. There is no increment operator in Ruby. You need to use i += 1. With that change you can use your C style loop as is.

Resources