How to break double statement?
a = 1
b = 2
c = 3
if a == 1
if b == 2
c = 5
d = 6
break
end
end
puts c
puts d
Output
loop.rb:9: Invalid break
loop.rb: compile error (SyntaxError)
You can't break from inside an if, you can only break from inside loops and blocks.
If what you're asking is how to break from two nested loops, you can use catch in combination with throw—these are not the same as try and catch in other languages.
catch(:stop) do
while some_cond
while other_cond
throw :stop
end
end
end
Of course, you can always just set a flag or some such to tell the outer loop that it should break too.
Related
I wrote a simple guess the number game. But it keeps looping even when I input the correct number. Please help, thanks!
puts "Pick a number between 0 - 1000."
user_guess = gets.chomp.to_i
my_num = rand(831)
guess_count = 0
until user_guess == my_num do
if user_guess == my_num
guess_count += 1
puts "you got it!"
elsif user_guess <= 830
guess_count += 1
puts "higher"
else user_guess >= 1000
guess_count += 1
puts "lower"
end
end
puts "You guessed my number in #{guess_count} attempts. Not bad"
The part of the code that asks for a number from the user is outside the loop, so it will not repeat after the answer is checked. If you want to ask the user to guess again when their guess is wrong, that code needs to be inside the loop.
my_num = rand(831)
guess_count = 0
keep_going = true
while keep_going do
puts "Pick a number between 0 - 1000."
user_guess = gets.chomp.to_i
if user_guess == my_num
guess_count += 1
puts "you got it!"
keep_going = false
elsif user_guess <= 830
guess_count += 1
puts "higher"
else user_guess >= 1000
guess_count += 1
puts "lower"
end
end
puts "You guessed my number in #{guess_count} attempts. Not bad"
This code still has some bugs in it that stops the game from working correctly though, see if you can spot what they are.
As #Tobias has answered your question I would like to take some time to suggest how you might make your code more Ruby-like.
Firstly, while you could use a while or until loop, I suggest you rely mainly on the method Kernel#loop for most loops you will write. This simply causes looping to continue within loop's block until the keyword break is encountered1. It is much like while true or until false (commonly used in some languages) but I think it reads better. More importantly, the use of loop protects computations within its block from prying eyes. (See the section Other considerations below for an example of this point.)
You can also exit loop's block by executing return or exit, but normally you will use break.
My second main suggestion is that for this type of problem you use a case statement rather than an if/elsif/else/end construct. Let's first do that using ranges.
Use a case statement with ranges
my_num = rand(831)
guess_count = 0
loop do
print "Pick a number between 0 and 830: "
guess_count += 1
case gets.chomp.to_i
when my_num
puts "you got it!"
break
when 0..my_num-1
puts "higher"
else
puts "lower"
end
end
There are a few things to note here.
I used print rather than puts so the user will enter their response on on the same line as the prompt.
guess_count is incremented regardless of the user's response so that can be done before the case statement is executed.
there is no need to assign the user's response (gets.chomp.to_i) to a variable.
case statements compare values with the appropriate case equality method ===.
With regard to the last point, here we are comparing an integer (gets.chomp.to_i) with another integer (my_num) and with a range (0..my_num-1). In the first instance, Integer#=== is used, which is equivalent to Integer#==. For ranges the method Range#=== is used.
Suppose, for example, that my_num = 100 and gets.chomp.to_i #=> 50 The case statement then reads as follows.
case 50
when 100
puts "you got it!"
break
when 0..99
puts "higher"
else
puts "lower"
end
Here we find that 100 == 50 #=> false and (0..99) === 50 #=> true, so puts "higher" is displayed. (0..99) === 50 returns true because the integer (on the right of ===) is covered by the range (on the left). That is not the same as 50 === (0..90), which loosely reads, "(0..99) is a member of 50", so false is returned.
Here are a couple more examples of how case statements can be used to advantage because of their reliance on the triple equality method.
case obj
when Integer
obj + 10
when String
obj.upcase
when Array
obj.reverse
...
end
case str
when /\A#/
puts "A comment"
when /\blaunch missiles\b/
big_red_button.push
...
end
Use a case statement with the spaceship operator <=>
The spaceship operator is used by Ruby's Array#sort and Enumerable#sort methods, but has other uses, as in case statements. Here we can use Integer#<=> to compare two integers.
my_num = rand(831)
guess_count = 0
loop do
print "Pick a number between 0 and 830: "
case gets.chomp.to_i <=> my_num
when 0
puts "you got it!"
break
when -1
puts "higher"
else # 1
puts "lower"
end
end
In other applications the spaceship operator might be used to compare strings (String#<=>), arrays (Array#<=>), Date objects (Date#<=>) and so on.
Use a hash
Hashes can often be used as an alternative to case statements. Here we could write the following.
response = { -1=>"higher", 0=>"you got it!", 1=>"lower" }
my_num = rand(831)
guess_count = 0
loop do
print "Pick a number between 0 and 830: "
guess = gets.chomp.to_i
puts response[guess <=> my_num]
break if guess == my_num
end
Here we need the value of gets.chomp.to_i twice, so I've saved it to a variable.
Other considerations
Suppose we write the following:
i = 0
while i < 5
i += 1
j = i
end
j #=> 5
j following the loop is found to equal 5.
If we instead use loop:
i = 0
loop do
i += 1
j = i
break if i == 5
end
j #=> NameError (undefined local variable or method 'j')
Although while and loop both have access to i, but loop confines the values of local variables created in its block to the block. That's because blocks create a new scope, which is good coding practice. while and until do not use blocks. We generally don't want code following the loop to have access to local variables created within the loop, which is one reason for favouring loop over while and until.
Lastly, the keyword break can also be used with an argument whose value is returned by loop. For example:
def m
i = 0
loop do
i += 1
break 5*i if i == 10
end
end
m #=> 50
or
i = 0
n = loop do
i += 1
break 5*i if i == 10
end
n #=> 50
1. If you examine the doc for Kernel#loop you will see that executing break from within loop's block is equivalent to raising a StopIteration exception.
I'm looping over an array, and have an if statement inside.
[1, 2, 3, 4, 5, 6, 7, 8, 9].each do |i|
if i == 4
p i
break
end
if i > 6
p i
end
end
I want to print i if i == 4, and break, but still move on to the second if statement. How can I exit the if statement but not the for loop, still running the remaining code after the if block?
In my real scenario, I have a matrix:
1 2 3 4 5
_ ♙ _ 9 _
11 12 13 14 15
16 _ 18 19 20
21 22 23 24 _
Say my current pos is [1][1]. When I check my next element
(row + i, col), I want to increment a value. But when I encounter '_' say [3][1], I should terminate from if statement.
I know that a simple fix is just to remove the break. But the main question is, how can I break from one if statement, and still go to the second if statement.
Remove the break.
There's no need to break out of if statements. They aren't loops. If the condition is true they run their block of code once and then continue onward.
this = 42
if this < 40
p "less than 42"
end
if this > 40
p "greater than 40"
end
p "after the ifs"
This will print.
"greater than 40"
"after the ifs"
By "break from if", do you perhaps mean "interrupt execution of if's body"?
if condition
puts 'doing something'
# "break" here
puts 'doing something else'
end
If so, it can be done like this:
catch(:break_from_if) do
if condition
puts 'doing something'
throw :break_from_if
puts 'doing something else'
end
end
Note the verb: can be done, but shouldn't ever be done (in code that is of any importance)
Maybe I am missing something. Unfortunately I am not entirely sure that I understand your question and the terminology that you use. The following makes sure that 'i > 6' does not have side-effects, in the same if block we perform the following check with elsif. Here you want the effect that you move onto the next item in the list, if I understand correctly. You can do that with 'next'.
[1,2,3,4,5,6,7,8,9].each do |i|
if i > 6
p i
elsif i == 4
p i
next
end
end
Questions
Why does break within a proc jump out of three loops all the way to puts 8? It's pretty counter-intuitive.
Is there a way to make it break out of the innermost loop, that is, to puts 6?
Code
3.times do
puts "outer loop"
break_proc = proc { break }
puts 1
loop do
puts 2
loop do
puts 3
loop do
puts 4
break_proc.call
puts 5
end
puts 6
end
puts 7
end
puts 8
end
outer loop
1
2
3
4
8
outer loop
1
2
3
4
8
outer loop
1
2
3
4
8
TL;DR
The behavior you're seeing is a result of attempting to treat a Proc object like a snippet of code passed to Kernel#eval, or thinking that a toplevel break inside a Proc is the same as a bare break keyword inside a loop. An explanation for the behavior is provided, but the real solution is to avoid doing what you're doing.
Procs Carry Context
Why does break within a proc jump out of three loops all the way to puts 8?
This happens because a Proc object contains a Binding to the context in which it's created, and the break keyword is exiting the iterator block and returning to its calling context. Specifically, you're creating the Proc in the top-level loop here:
3.times do
puts "outer loop"
break_proc = proc { break }
One could be forgiven for thinking that Ruby's break just exits a loop wherever its called, but its behavior is more complex than that, especially when you're trying to do something odd like a toplevel break inside a Proc. Your use case for break is even covered in The Ruby Programming Language, where it says:
[A break] causes the block to return to its iterator and the iterator to return to the method that invoked it. Because procs work like blocks, we expect break to do the same thing in a proc. We can’t easily test this, however. When we create a proc with Proc.new, Proc.new is the iterator that break would return from. And by the time we can invoke the proc object, the iterator has already returned. So it never makes sense to have a top-level break statement in a proc created with Proc.new[.]
— David Flanagan and Yukihiro Matsumoto. The Ruby Programming Language (Kindle Locations 8185-8192). O'Reilly Media.
When you create deeply nested loops and then complicate that with objects that carry runtime bindings, the results aren't always what you expect. The behavior you're seeing is not a bug, although it may be a misfeature in some cases. You'd have to ask the language designers why it behaves this way if you want a reason for the implementation semantics rather an explanation for the behavior you're seeing.
Breaking Loops
Is there a way to make it break out of the innermost loop, that is, to puts 6?
Yes, but not with break inside a Proc. Replacing the Proc#call with an actual inline break statement does what you expect and is the "simplest thing that could possibly work," but you can also use throw and catch if you want to adjust your nesting level. For example:
3.times do
puts "outer loop"
break_proc = proc { throw :up }
puts 1
loop do
puts 2
loop do
puts 3
catch :up do
loop do
puts 4
break_proc.call
puts 5
end
end
puts 6
end
puts 7
end
puts 8
end
This will yield:
outer loop
1
2
3
4
6
3
4
6
3
4
6
and endlessly loop inside the third loop where you puts 3.
So, this will do what you're asking, but may or may not do what you want. If it helps, great! If not, you may want to ask a separate question with some real data and behavior if you want to find a more elegant data structure or decompose your task into a set of collaborating objects.
Because of context binding break escapes from the loop defined at the same level:
3.times do
puts 1
loop do
break_proc = proc {|b| break }
puts 2
loop do
puts 3
loop do
puts 4
break_proc.call
puts 5
end
puts 6
end
puts 7
raise 'break other loops'
end
puts 8
end
=>
1
2
3
4
7
1.rb:18:in `block (2 levels) in <main>': break other loops (RuntimeError)
Easiest way to break from your construction - return a boolean from the block indicating if loop should be terminated (... = proc{ true }/break if break_proc.call), or use throw:
3.times do
puts "outer loop"
break_proc = proc {|b| throw :breakit }
puts 1
loop do
puts 2
loop do
puts 3
catch :breakit do
loop do
puts 4
break_proc.call
puts 5
end
end
puts 6
raise 'break the other loops...'
end
puts 7
end
puts 8
end
If you want to break till 6 block you could do this
3.times do
puts "outer loop"
break_proc = proc { break }
puts 1
loop do
puts 2
loop do
puts 3
loop do
puts 4
break
puts 5
end
puts 6
end
puts 7
end
puts 8
end
Ruby Folks:
To use proc & lambda in Ruby, kindly consider the following info to use them error-free and use break or return with proper understanding:
Lambda and non-lambda semantics:
-------------------------------
Procs are coming in two flavors: lambda and non-lambda (regular procs). Differences are:
In lambdas, return and break means exit from this lambda;
In non-lambda procs, return means exit from embracing method (and will throw LocalJumpError if invoked outside the method);
In non-lambda procs, break means exit from the method for which the block is given. (and will throw LocalJumpError if invoked after the method returns);
In lambdas, arguments are treated in the same way as in methods: strict, with ArgumentError for mismatching argument number, and no additional argument processing;
Regular procs accept arguments more generously: missing arguments are filled with nil, single Array arguments are deconstructed if the proc has multiple arguments, and there is no error raised on extra arguments.
Ref: https://ruby-doc.org/core-3.0.2/Proc.html
c = 5
until c == 0 do
print c
c -= 1
end
/break
c = 5
until c == 0
print c
c -= 1
end
What's the difference?
Both of them display 54321 as output.
do is optional. It indicates the beginning of the block of code to be repeatedly executed.
In your example it makes no difference. However if you try re-writing the code in one line, you can see why its needed:
c = 5
until c == 0 do print c; c -= 1 end
# 54321 => nil
Now try this without do:
c = 5
until c == 0 print c; c -= 1 end
# SyntaxError: (irb):115: syntax error, unexpected tIDENTIFIER, expecting keyword_do_cond or ';' or '\n'
As you can see there is no clear beginning for block, Ruby will throw a SyntaxError.
There is no difference between until and until do. Do is optional and both will show same output. If want to get some more idea about this while and while do
There is absolutely no difference. It is like the while [condition] [block] end vs while [condition] do [block] end or if [condition] [block] end vs if [condition] then [block] end.
In Ruby, often there are tons of ways of doing the same thing - the important is to be consistent, either way.
I have a code which I need to use within eval. Sometimes I need to get out from the eval code, but my tries lead to errors.
E.g.:
# expected to see 1, 2 and 5; not 3 nor 4; and no errors
eval "puts 1; puts 2; return; puts 3; puts 4" # => Error: unexpected return
puts 5
I tried with return, end, exit, break, and I couldn't get success. exit doesn't raise errors, but then I don't get the 5.
(Note: I know that eval is evil, but in this case I need to use it.)
Thank you all, but I found a solution which fits best into my problem:
lambda do
eval "puts 1; puts 2; return; puts 3; puts 4"
end.call
puts 5
This way the intuitive return keyword can be used inside eval to get out from it successfully.
I didn't like the conditional-like solutions in this case because it would force me (or the user) to add an end at the end.
About using throw/catch or break, I consider the return keyword more intuitive.
eval'd code is just being run in this place. It's not a function or block. How would you do it without eval? Probably like this:
puts 1
puts 2
if(conditionFor3And4)
puts 3
puts 4
end
you Can't. You can return out of methods, and break out of blocks or loops, but not eval.
You could try a throw/catch block instead
eval "
should_stop = true
catch :stop do
puts 1
puts 2
throw :stop if should_stop
puts 3
end
"
or this:
should_stop = true
catch :stop do
eval "
puts 1
puts 2
throw :stop if should_stop
puts 3
"
end
or just do a conditional like Mchl said, since you probably want it to conditional stop, not just always, but throw catch will let you jump out of a block no matter how many levels down you are, which make it more robust, if you need to break out of a nested loop or something like that.
You could just use conditionals instead of early returns.
You could quite happily define a function and execute it last at the end of the script as follows:
def ev(s)
eval("def doStuff()\n" + s + "\nend\ndoStuff()")
end
ev("
1
return 2
3
")
#=> 2
Demo