Skipping iteration from Proc - ruby

I'm wondering if it's possible to use a Proc for skipping iteration in Ruby?
I wrote some piece of code
def validation i
pr = Proc.new do |i|
if i < 3
next
end
end
pr.call(i)
end
(1..5).each do |i|
validation i
puts "#{i} is bigger than 3"
end
and I expected something like this as result:
3 is bigger than 3
4 is bigger than 3
5 is bigger than 3
but instead I got:
1 is bigger than 3
2 is bigger than 3
3 is bigger than 3
4 is bigger than 3
5 is bigger than 3
So is it possible to use somehow next in Proc for skipping from outer iteration or there is some other way?

You can't call next in your validation method because the loop is external. What you can do is use next within your (1..5).each loop that's dependent on a call to validation. The following code produces your desired result.
Edit - The code has been refactored to make appropriate use of Proc.
pr = Proc.new {|i| i < 3}
(1..5).each do |i|
next if pr.call(i)
puts "#{i} is bigger than 3"
end

Related

Is there a nicer way to call the current method recursively, without using its name?

For example:
def recurse(value)
if value < 5
self.send(__method__, value + 1)
else
value
end
end
This works, but it's a bit ugly.
Basically I'm looking for a prettier way to call the currently executing method, without referring to it explicitly by name.
If there is a less-cryptic syntax for this, I would probably use it (to avoid the name duplication, reduce effort required for renaming a function, etc). If there isn't a nicer syntax for this, I'll just hard-code the name like normal.
It's a comment rather, as #sagarpandya82 mentioned, you can omit some redundant parts and use both variants. I would refactor it a bit:
def recurse(value)
return value unless value < 5 # return value if value >= 5
send(__method__, value + 1) # or just recurse(value + 1)
end
Non-recursion version with a block:
def non_recurse(value)
if value >= 5
yield value
else
(value..5).each do |i|
yield i
end
end
end
non_recurse(3) {|i| puts i}
#=> 3, 4, 5
non_recurse(6) {|i| puts i}
#=> 6
If you really want to use __method__, your method is correct and reasonably readable. To comply with usual Ruby guidelines, you could just remove returns and use 2 spaces as indent (as mentioned by #sagarpandya82 in the comments):
def recurse(value)
if value < 5
self.send(__method__, value + 1)
else
value
end
end
I don't see any reason to use self.send(__method__) here, so you could write :
def recurse(value)
if value < 5
recurse(value + 1)
else
value
end
end
Actually, I'd say that you don't need recursion at all. All your method does is to keep adding 1 to the value until it reaches 5. If the value is bigger than 5, it returns the value :
For integers:
def no_recurse(value)
[value, 5].max
end
no_recurse(4)
# 5
no_recurse(-3)
# 5
no_recurse(7)
# 7
no_recurse(-2**1000)
# 5
no_recurse(4.5)
# 5 # <- That's wrong
For floats, you'd just need to add the decimal part to 5. This will work for any number:
def no_recurse(value)
[value, 5 + value % 1].max
end
no_recurse(4.5)
# 5.5
no_recurse(5.5)
# 5.5
no_recurse(6)
# 6
no_recurse(-7)
# 5

Callback after every `each` iteration when `next` is called

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.

How to modify an iterator while iterating over an array

I want to skip a loop x times according to a condition that is determined at runtime. How can I do this?
for i in (0..5)
if i==0
3.times {next} # i=i+3 also doesnt work
end
puts i
end
Expect to output
3
4
5
EDIT:
To clarify, the question is both the condition (ie i==0) and skipping x times iteration are determined dynamically at runtime, more convoluted example:
condition = Array.new(rand(1..100)).map{|el| rand(1..10000)} #edge cases will bug out
condition.uniq!
for i in (0..10000)
if condition.include? i
rand(1..10).times {next} # will not work
end
puts i
end
simple method to skip by a defined multiple.
array_list = (0..5).to_a
# Use a separate enum object to hold index position
enum = array_list.each
multiple = 3
array_list.each do |value|
if value.zero?
multiple.times { enum.next }
end
begin puts enum.next rescue StopIteration end
end

Equivalent of "continue" in Ruby

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

ruby loop next case example

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)

Resources