Return from a loop from a different method - ruby

My understanding is you can exit from a program with a return. How do you return from a loop? When I run return_method as in the following, I want to exit the loop with "RETURNING" returned.
def return_method
return "RETURNING"
end
loop do
puts "Enter:"
answer = gets.chomp
if answer == 'run'
return_method
end
break if answer == 'y'
end
break doesn't work within my method.

A typical way to escape from nested loops of from nested method calls is to use catch ... throw.
RETURNING = "RETURNING"
def return_method
throw RETURNING
end
catch(RETURNING) do
loop do
puts "Enter:"
answer = gets.chomp
if answer == 'run'
return_method
end
break if answer == 'y'
end
end

It's generally not the case that a method call forces the calling method to do something as abrupt as return. That's what exceptions are for, they will bubble up if not caught, but that's seriously heavy-handed for this sort of thing. Instead make your method return a truthy value if you want to break the loop:
def return_method
puts "RETURNING"
true
end
loop do
puts "Enter:"
answer = gets.chomp
case (answer)
when 'run'
break if return_method
when 'y'
break
end
end

Related

In Ruby, what is the return value in a loop?

Using the following code:
def get_action
action = nil
until Guide::Config.actions.include?(action)
puts "Actions: " + Guide::Config.actions.join(", ")
print "> "
user_response = gets.chomp
action = user_response.downcase.strip
end
return action
end
The following code takes the user response, and eventually returns its actions to another method.
I know that a loop repeats itself until its ultimately broken, but was curious about the return value, so I could better structure the loop for next time. In the until loop, I am curious to know whether what value does the until loop return, if there is a return value at all?
the return of a loop (loop, while, until, etc) can be anything you send to break
def get_action
loop do
action = gets.chomp
break action if Guide::Config.actions.include?(action)
end
end
or
def get_action
while action = gets.chomp
break action if Guide::Config.actions.include?(action)
end
end
or you can use begin .. while
def get_action
begin
action = gets.chomp
end while Guide::Config.actions.include?(action)
action
end
or even shorter
def get_action
action = gets.chomp while Guide::Config.actions.include?(action)
action
end
PS: loops themselves return nil as a result (implicit break which is break nil) unless you use explicit break "something". If you want to assign result of the loop you should use break for this: x = loop do break 1; end
loop can also return a value without using a break
def loop_return
#a = [1, 2, 3 ].to_enum
loop do
#a.next
end
end
print loop_return #=> [1, 2, 3]

How to break from a nested loop to a parent loop that is more than one level above which requires a value provided by the nested loop

In the following situation:
xxx.delete_if do |x|
yyy.descend do |y| # This is a pathname.descend
zzz.each do |z|
if x + y == z
# Do something
# Break all nested loops returning to "xxx.delete_if do |x|" loop
# The "xxx.delete_if do |x|" must receive a "true" so that it
# can delete the array item
end
end
end
end
What is the best way to achieve this multiple nested break while making sure I can pass the true value so that the array item is deleted?
Maybe I should use multiple break statements that return true or use a throw/catch with a variable, but I don't know if those are the best answer.
This question is different from How to break from nested loops in Ruby? because it requires that the parent loop receives a value from the nested loop.
throw/catch (NOT raise/rescue) is the way I typically see this done.
xxx.delete_if do |x|
catch(:done) do
yyy.each do |y|
zzz.each do |z|
if x + y == z
# Do something
throw(:done, true)
end
end
end
false
end
end
In fact, the Pickaxe explicitly recommends it:
While the exception mechanism of raise and rescue is great for abandoning execution when things go wrong, it's sometimes nice to be able to jump out of some deeply nested construct during normal processing. This is where catch and throw come in handy. When Ruby encounters a throw, it zips back up the call stack looking for a catch block with a matching symbol. When it finds it, Ruby unwinds the stack to that point and terminates the block. If the throw is called with the optional second parameter, that value is returned as the value of the catch.
That said, max's #any? suggestion is a better fit for this problem.
You can use multiple break statements.
For example:
xxx.delete_if do |x|
result = yyy.each do |y|
result2 = zzz.each do |z|
if x + y == z
break true
end
end
break true if result2 == true
end
result == true
end
However I would definitely avoid this in your particular situation.
You shouldn't be assigning variables to the result of each. Use map, reduce, select, reject, any?, all?, etc. instead
It makes more sense to use any? to accomplish the same purpose:
xxx.delete_if do |x|
yyy.any? do |y|
zzz.any? do |z|
x + y == z
end
end
end

How to stop outer block from inner block

I try to implement search function which looks for occurrence for particular keyword, but if --max options is provided it will print only some particular number of lines.
def search_in_file(path_to_file, keyword)
seen = false
File::open(path_to_file) do |f|
f.each_with_index do |line, i|
if line.include? keyword
# print path to file before only if there occurence of keyword in a file
unless seen
puts path_to_file.to_s.blue
seen = true
end
# print colored line
puts "#{i+1}:".bold.gray + "#{line}".sub(keyword, keyword.bg_red)
break if i == #opt[:max] # PROBLEM WITH THIS!!!
end
end
end
puts "" if seen
end
I try to use break statement, but when it's within if ... end block I can't break out from outer each_with_index block.
If I move break outside if ... end it works, but it's not what I want.
How I can deal with this?
Thanks in advance.
I'm not sure how to implement it in your code as I'm still learning Ruby, but you can try catch and throw to solve this.
def search_in_file(path_to_file, keyword)
seen = false
catch :limit_reached do
#put your code to look in file here...
throw :limit_reached if i == #opt[:max] #this will break and take you to the end of catch block
Something like this already exist here

Ruby skips items from list tasks

I am trying to make an app which if give the option to type, it types false then it skips the certain element from the list and it jumps to the next executing the same task.
That is the basic idea of the following code:
string["items"].each do |item|
p continue.to_s + "<- item"
begin
Anemone.crawl("http://" + item["displayLink"] + "/") do |anemone|
anemone.on_every_page do |page|
if continue.chomp.to_bool == false
raise "no more please"
end
request = Typhoeus::Request.new(page.url, followlocation: true)
response = request.run
email = /[-0-9a-zA-Z.+_]+#[-0-9a-zA-Z.+_]+\.[a-zA-Z]{2,4}/.match(response.body)
if email.nil?
else
p email
begin
continue = Timeout::timeout(2) do
p "insert now false/nothing"
gets
end
rescue Timeout::Error
continue = "true"
end
end
end
end
rescue
continue = true
next
end
p "---------------------------------------------------------"
end
As the code shows, if the user types false when prompted the app should skip the item and go to the next one. However what it does is: when the user types false the app skips the current item and then doesn't execute any of the code that should be executed for all of the other items except the printing ( the second line of code );
Here is how the output looks like:
$ruby main.rb
"1"
"true<- item"
#<MatchData "support#keycreative.com">
"insert now false/nothing"
false
"true<- item"
"true<- item"
"true<- item"
As I'm doing my best to show after false is entered the code does skip the certain item from the list but it also never ever executes code for the other items as it should since it is an each loop
First I thought that maybe the continue is false however as you can see from the output the continue is true which makes me wonder why does ruby skip my code?
UPDATE
Here is where the to_bool method comes from:
class String
def to_bool()
return true if self == "true"
return false if self == "false"
return nil
end
end
In your last rescue statement add:
rescue => e
puts e.message
continue = true
next
end
and inspect the output. Most likely your code is throwing an exception other than "no more please" (I expect undefined method to_bool for true:TrueClass). Note that using exception for skipping the loop element is a terrible idea. Why can't you just get rid of this rescue and do:
if continue.chomp.to_bool == false
continue = true
next
end
There are a lot of things in this code which makes it very un-ruby-like. If you want to improve it please paste it to StackExchange CodeReview page. (link in the comment).
UPDATE:
My bad, you are in nested loop, so the if statement won't work. You might look at sth similar to raise/rescue bit, namely throw/catch, see example here: How to break from nested loops in Ruby?. I still think you should post it to codereview though for refactoring advises.
As to your actual code (without refactoring). You are calling to_bool method on continue, and in your rescue block you assign true instead of 'true'. Hence your to_bool method raises exception which is then rescued same way as 'no more please' exception.

if method_one returns value return value, else try method_two in ruby

How do I say if method_one returns a value, then break, else try method_two?
def ai_second_move(board)
p "2nd move called"
# TODO - how to say if method_one gives me a value, break, else method_two
method_one(board)
method_two(board)
end
Most Ruby way of writing this would be:
method_one(board) || method_two(board)
Ruby executes the right-hand side of || only if the left hand side evaluated to false (meaning it returns nil or false) and then the result of this expression would be that of the method_two
using if -
method_two(board) if method_one(board).nil?
using unless -
method_two(board) unless !method_one(board).nil?
using ternary -
# This evaluates if (method_one(board) returns nil) condition. If its true then next statement is method_two(board) else return is executed next.
method_one(board).nil? ? method_two(board) : return
This would work as well:
method_one(board) and return
The return statement is executed only if method_one(board) returns a truthy value.
You need to use return. break is for loops.
def ai_second_move(board)
p "2nd move called"
return if !!method_one(board)
method_two(board)
end
An other fun way would be
def ai_second_move(board)
p "2nd move called"
!!method_one(board) || method_two(board)
end

Resources