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
Related
I have the following:
text_counter = 0
MAXTEXT_COUNTER = 10
puts "hello, this will start"
loop do
puts "hello"
text_counter += 1
sleep(2)
if text_counter >= MAXTEXT_COUNTER
break
end
end
sleep(7200)
print "ended test"
Once the break has happened, how can I get it to start again from the top?
I'm now thinking I could nest this loop in an until loop with the condition of text_counter == 1000. This would break, then sleep for 2 hours, then start again until it hits 1000.
It looks like you need a loop within a loop where you repeat one N times, the other M times:
MAXTEXT_COUNTER = 10
puts "hello, this will start"
loop do
MAXTEXT_COUNTER.times do
puts "hello"
sleep(2)
end
print "ended test"
sleep(7200)
end
The outer loop is perpetual. The inner one runs a certain number of times and stops using the times method.
You're looking for next
It functions similarly to break, but returns control back to the top of the loop. It's great for creating flat control flow.
For example
0.upto(100) do |i|
if i % 7 == 0
puts "#{i} is a multiple of 7"
next
end
puts i
end
There is a retry keyword which repeats the loop from top, just what you've asked.
Or you can wrap your loop into a method and continuously call that method.
I want to run some code after every iteration of each. Is there a way to do this without repeating the code? I tried this:
(1..10).each do |n|
continue = Proc.new {
puts "ended #{n}"
next
}
continue.call if n == 2
puts n
continue.call
end
but it didn't work.
In my actual code, I have lots of next calls. That's why it's unpractical to call a method every time I call next.
Approach 1
Define the contents of the loop in a method that you call from the loop. You can put an ensure block in the method. That way, your method can use return anywhere you want to move onto the next iteration, but you still guarantee you execute the ensure code:
def doit(x)
return if x == 2
puts "I like the number #{x}"
ensure
puts "LOOP"
end
[1,2,3,4].each{|x| doit(x)}
results in
I like the number 1
LOOP
LOOP
I like the number 3
LOOP
I like the number 4
LOOP
Approach 2
Similar to approach 1, but allows you reuse the "callback" code for different concerns. It also keeps you using next instead of return. This is to define a method that yields and then does other stuff:
def ensure_LOOP(x)
yield
puts "LOOP"
end
[1,2,3,4].each do |x|
ensure_LOOP(x) do
next if x == 2
puts "I really like the number #{x}"
end
end
Results in
I really like the number 1
LOOP
LOOP
I really like the number 3
LOOP
I really like the number 4
LOOP
And
[1,2,3,4].each do |x|
ensure_LOOP(x) do
next unless x == 2
puts "I don't like the number #{x}"
end
end
results in
LOOP
I don't like the number 2
LOOP
LOOP
LOOP
As I understand the question, you don't want anything executed after continue.call if n==2. If that's correct, you could use the control expression next with an argument.
def m(n)
puts "Only #{n} more days!"
end
(1..6).each do |n|
next m(n) if n==3
puts n
m(n)
end
1
Only 1 more days!
2
Only 2 more days!
Only 3 more days!
4
Only 4 more days!
5
Only 5 more days!
6
Only 6 more days!
Just call it within the each loop?
(1..10).each do |n|
puts n
puts "ended #{n}"
end
The code you provided actually does run, and outputs the following:
1
ended 1
ended 2
2
ended 2
3
ended 3
4
ended 4
5
ended 5
6
ended 6
7
ended 7
8
ended 8
9
ended 9
10
ended 10
As you can see, the Proc gets called twice for the number 2, as your if condition passes in that case and calls the Proc.
Stepping back, defining a method outside the context of the iteration is probably a better choice. It will make the code easier to read and avoid redefining the Proc each time the each block executes.
That said, technically what you have here seems to run okay.
I'm creating an app that tells what month of the year each number from 1 to 12 represents, like 1 = January, 2 = February, etc. This is the initial code
print "Please, tell me the month's number!"
number = gets.chomp.to_i
while number > 12 do
print "Please, re-type it again!"
number = gets.chomp.to_i
end
case number
when 1 then print "This is January!"
when 2 then print "This is February!"
when 3 then print "This is March!"
when 4 then print "This is April!"
when 5 then print "This is May!"
when 6 then print "This is June!"
when 7 then print "This is July!"
when 8 then print "This is August!"
when 9 then print "This is September!"
when 10 then print "This is October!"
when 11 then print "This is November!"
when 12 then print "This is December!"
else print "I can't undestand you, i'm sorry!"
end
Now, the basic logic is set. The only thing that I think is missing is the second condition in the while loop that defines that, if the input isn't an integer, I need to retype it. I'm trying to define the second condition in that while loop correctly, but no results so far. How can I do it?
How can I make the code better? And is the while loop in this context the right method for the job?
Here is another way to make your code better:
require 'date'
print "Please, tell me the month's number! "
number = gets.to_i
until number.between?(1,12) do
print "Please, re-type it again! "
number = gets.to_i
end
month_name = Hash.new
(1..12).each do |month_num|
month_name[month_num] = Date::MONTHNAMES[month_num]
end
print "This is #{month_name[number]}!"
As noted in other answers chomp is not necessary if using to_i because converting to an integer will take care of the new line.
I am using number.between?(1,12) as suggested by #steenslag to check if the input is valid.
I tried a Hash approach instead of the case statement. A while loop is one way to do this but this is just one other way to get it done.
print "Please, tell me the month's number!"
number = gets.to_i
You never need chomp if you are going to use to_i because the very nature of a number states that it will never have a line ending.
until number > 1 && number < 12 do
You do want the number to be inclusive of 1 to 12, right?
You could alternative do this,
until (1..12).include?(number) do
Or as #teenslag states,
until number.between?(1, 12) do
I think either reads easier, and between? method may be the better choice.
print "Please, re-type it again!"
number = gets.to_i
end
This can be very DRY, or using the built in Date object, as mentioned. But let's see what we can do with the code as you have it, in the same spirit:
case number
when 1 then print "This is January!"
when 2 then print "This is February!"
when 3 then print "This is March!"
when 4 then print "This is April!"
when 5 then print "This is May!"
when 6 then print "This is June!"
when 7 then print "This is July!"
when 8 then print "This is August!"
when 9 then print "This is September!"
when 10 then print "This is October!"
when 11 then print "This is November!"
when 12 then print "This is December!"
end
turns to this:
answer = case number
when 1
"January"
when 2
"February"
when 3
"March"
when 4
"April"
when 5
"May"
when 6
"June"
when 7
"July"
when 8
"August"
when 9
"September"
when 10
"October"
when 11
"November"
when 12
"December"
end
print "This is #{answer}!"
Though it would be nicer just using the Date class.
If that wasn't available for you, then perhaps I would consider using a Hash here instead. I will let you explore those options.
Or even an Array. Remembering that Array elements start at position 0:
print "Please, tell me the month's number!"
until (number = gets.to_i).between?(1,12) do
print "Please, re-type it again!"
end
months = %w[January February March April May June July August September October November December]
puts "This is #{months[number - 1]}!"
number will always be an integer because you converted it into an integer.
"foo".to_i
=> 0
You probably want to use a range instead.
until (1..12).include?(gets.chomp.to_i)
print "Please, re-type it again!"
end
You can also DRY your code by using the built-in number to month conversion:
number = 4
Date::MONTHNAMES[number]
=> "April"
As the above posters have mentioned, this particular instance does not require a second condition. However, in the event you need a multiple condition while loop you would use the logical AND or the logical OR operator.
The logical AND operator is &&. The logical OR operator is ||. You would use && if both conditions need to be true to continue the loop. You'd use the || if one or the other would need to be true to continue the loop.
For example:
while number > 12 || number == 0 do
stuff...
end
while number > 0 && number < 13 do
stuff...
end
In the first snippet, you will enter the loop if the number entered is either above 12 OR equal to 0. In the second you will enter the loop if the number entered is greater than 0 AND less than 13. (Obviously, the second one would be exactly the opposite of what you were using the while loop for here, but is included for demonstration).
A simple loop that you need:
until month_num > 0 && month_num < 13 do
# prompt again
end
A more idiomatic way would be to use between? or include?
until month_num.between? (1, 12) do
# prompt again
end
And instead of multiple when then, you could use a hash map from the Date module:- Date::MONTHNAMES[month_num]
In C and many other languages, there is a continue keyword that, when used inside of a loop, jumps to the next iteration of the loop. Is there any equivalent of this continue keyword in Ruby?
Yes, it's called next.
for i in 0..5
if i < 2
next
end
puts "Value of local variable is #{i}"
end
This outputs the following:
Value of local variable is 2
Value of local variable is 3
Value of local variable is 4
Value of local variable is 5
=> 0..5
next
also, look at redo which redoes the current iteration.
Writing Ian Purton's answer in a slightly more idiomatic way:
(1..5).each do |x|
next if x < 2
puts x
end
Prints:
2
3
4
5
Inside for-loops and iterator methods like each and map the next keyword in ruby will have the effect of jumping to the next iteration of the loop (same as continue in C).
However what it actually does is just to return from the current block. So you can use it with any method that takes a block - even if it has nothing to do with iteration.
Ruby has two other loop/iteration control keywords: redo and retry.
Read more about them, and the difference between them, at Ruby QuickTips.
I think it is called next.
Use next, it will bypass that condition and rest of the code will work.
Below i have provided the Full script and out put
class TestBreak
puts " Enter the nmber"
no= gets.to_i
for i in 1..no
if(i==5)
next
else
puts i
end
end
end
obj=TestBreak.new()
Output:
Enter the nmber
10
1
2
3
4
6
7
8
9
10
Use may use next conditionally
before = 0
"0;1;2;3".split(";").each.with_index do |now, i|
next if i < 1
puts "before it was #{before}, now it is #{now}"
before = now
end
output:
before it was 0, now it is 1
before it was 1, now it is 2
before it was 2, now it is 3
I am trying to loop through multiple statements, but want to go through each one once, example:
while count < 5 do
count+= (not sure if this how ruby increments counts)
puts "In condition one"
next if count > 1
puts "In condition two"
next if count > 1
#..
end
Update 1:
Thanks for the reply, what I'm trying to do is loop through an array and have each element of the array be applied to 10 different conditions. For example: array[has 100 elements] element 1 gets condition 1, element 2 goes on to condition 2, and so on. Since there are 10 conditions, the 11th element in the array would get condition 1 again, and so on (condition 1 condition 2 condition 3 ...)
Update 2:
Thanks again for taking the time to reply. I apologize that I'm not being very clear. The array contains emails. I have 10 email servers and want to send the 200 emails I have in my array through each server (only 1 email per server). I hope that makes sense
If I'm reading you correctly, you want to send a large number of emails through a small number of servers while balancing the load. Try creating a class to manage the servers (here's the basic idea)
class ServerFarm
def initialize
#servers = []
end
attr_accessor :servers
def add_server(server)
#servers << server
end
def remove_server(x)
if x.is_a?(Numeric) then
#servers.delete_at(x)
elsif x.is_a?(Server)
#servers.delete(x)
end
end
def server_available?
#servers.each {|s| return true if s.available? }
false
end
def dispatch_message(email)
#servers.each_with_index {|s, i|
next unless s.available?
s.dispatch(email)
return i
}
nil
end
end
Now, all you will have to do is call ServerFarm.dispatch_message for an email and it will be sent using one of the available servers. This class assumes that you have a class named Server that holds the info for your individual servers, etc etc.
array = (1..100).to_a
conditions = (1..10).to_a
array.each_with_index do |elem, i|
puts "element %d, using condition %d" % [elem, conditions[i % conditions.length]]
end
produces
element 1, using condition 1
element 2, using condition 2
element 3, using condition 3
element 4, using condition 4
element 5, using condition 5
element 6, using condition 6
element 7, using condition 7
element 8, using condition 8
element 9, using condition 9
element 10, using condition 10
element 11, using condition 1
element 12, using condition 2
etc.
Does this help? I can't tell what you are trying to do.
5.times do |count|
puts 'In condition ' + %w(one two three four five)[count]
end
The 5.times do |count| will excecute the block five times with count starting at zero and incrementing each time. %w(one two three four five) is the same as ["one", "two", "three", "four", "five"].
If you want to do five different things consecutively, you do not need a loop. Just put the statements in a row:
# do thing 1
# do thing 2
# do thing 3
# ...
Edit:
"I have an array that I want to loop through, but each element in the array needs to go through a different condition each time and then restart at the first condition."
To loop through an array endlessly, testing each element against conditions:
arr = ['sdfhaq', 'aieei', 'xzhzdwz']
loop do
arr.each do |x|
case x
when /..h/
puts 'There was a \'h\' at the third character.'
when /.{6}/
puts 'There were at least six characters.'
else
puts 'None of the above.'
end
end
end
Edit 2:
"Thanks for the reply, what I'm trying to do is loop through an array and have each element of the array be applied to 10 different conditions, example: array[has 100 elements] element 1 gets condition 1 element 2 goes on to condition 2 and so on, since there are 10 conditions the 11th element in the array would get condition 1 again and so on. condition 1 condition 2 condition"
You will need to use the % method on numbers.
arr = Array.new(130) # an array of 130 nil elements.
num_conditions = 10
arr.each_with_index do |x, i|
condition = (i + 1) % num_conditions
puts "Condition number = #{condition}"
end
More information: http://ruby-doc.org/core/classes/Fixnum.html#M001059
Edit three:
def send_an_email(email, server)
puts "Sending an email with the text #{email.inspect} to #{server}."
end
email_servers = ['1.1.1.1', '2.2.2.2']
emails = ['How are you doing?', 'When are you coming over?', 'Check out this link!']
emails.each_with_index do |email, i|
send_an_email email, email_servers[i % email_servers.length]
end
You can modify email_servers and emails and have it still work, even if the lengths are changed.
array.each_slice(10) do |emails|
servers.zip(emails) { |server,email| server<<email }
end
(Ruby 1.9.2)