unable to understand this code how it works.
triangular_numbers = Enumerator.new do |yielder|
number = 0
count = 1
loop do
number += count
count += 1
yielder.yield number
end
end
5.times { print triangular_numbers.next, " " }
cant understand how yielder works for this block. how its yield work for number variable. how do loop runs 5 times. and how triangular_number.next works for the first time.
An enumerator is basically something that you can call next on and get something back. The yielder is the mechanism where it gives something back when next is called. Execution stops at the yield until the next call to next.
Farfetched analogy
You can think of an enumerator as a ticket machine like when you're waiting in line at a government office. When you press a button (next) it gives you a ticket. Inside the machine there's a chute where the ticket comes out. But the ticket machine is not constantly printing tickets. It waits for the button to be pressed before it prints the next ticket and puts it through the chute.
In this case the analogous code would be:
ticket_machine = Enumerator.new do |chute|
ticket = 0
loop do
#print_ticket
chute.yield ticket #waits here until you hit the button
ticket += 1
end
end
5.times { print ticket_machine.next, " " } # gets 5 tickets
Your code sample is basically the same thing, but instead of issuing tickets, it's issuing triangular numbers. The chute is the yielder where the numbers get passed through.
This is not the only way to use an enumerator, check the docs for more.
Enumerator::new accepts a block. This block, when run, receives an Enumerator::Yielder, which has a method #yield.
When the Enumerator#next is called, the block is executed, up to the first Enumerator::Yielder#yield. The execution is paused there; the value given to yield is the value that next returns. When you call next again on the same Enumerator, the execution is resumed, and proceeds until it encounters yield again.
So in your case, 5.times executes its block, intending to repeat it five times. triangular_numbers.next is called; this starts the execution of the block above. number and count are set to their values, and an infinite loop is started. number is set to 1, count is set to 2, and then we find yielder.yield. This pauses the execution of the block, and returns the control back to where next was called inside 5.times loop. next returns 1, because yielder.yield received number (1).
Second time through the 5.times loop, we want to print the next number. This stops the main execution line, and resumes the enumerator block from just after yielder.yield. The infinite loop continues; number is 3, count is 3, yielder.yield pauses the enumerator and resumes the main code. next gets 3, which gets printed.
Third, fourth and fifth time through the 5.times loop are exactly the same.
After five iterations, 5.times loop ends, and the execution proceeds past it. The enumerator is paused, ready to give the next number in sequence, if you ever call next on it again (since it has an infinite loop), but you never do, and the program exits.
I'll try to explain what it does bit by bit, so you can try to wrap your head around it.
Enumerator.new do |yielder|
end
So you instantiate an enumerator that will work over a variable called yielder.
Inside its scope you set some local vars (that will be kept as the object is reused):
number = 0
count = 1
And then you set a loop that increments number by count and count by 1 and then call yield over your argument passing number to it as an argument.
loop do
number += count
count += 1
yielder.yield number
end
5.times repeats the block passed to it 5 times. The block
-> { print triangular_numbers.next, " " }
calls print that takes n args and concatenates the parts to form a string, but does not append a newline.
The first argument is our enumerator next interaction (triangular_numbers.next), which will compute the current number and call yield on the Enumerator::Yielder that's implicitly created handling the control back to the calling Fiber along with any args that got passed to it.
(All Enumerators are implemented as "Fibers" on MRI)
So that yielder.yield call is similar to a Fiber.yield call and will allow the 5.times loop to run and return the number 1.
I'm adding a piece of code to the already clear explanation provided:
my_enum = Enumerator.new do |whatever_name_for_the_yielder|
n = 0
loop do
whatever_name_for_the_yielder.yield "Return this: #{n}"
n += 1
end
end
puts my_enum.next #=> Return this: 0
puts my_enum.next #=> Return this: 1
puts my_enum.next #=> Return this: 2
When you provide an end to the iteration, it stops with an error:
my_enum2 = Enumerator.new do |whatever_name_for_the_yielder|
2.times do |n|
whatever_name_for_the_yielder.yield "Return this: #{n}"
end
puts "Outside the loop"
end
puts my_enum2.next #=> Return this: 0
puts my_enum2.next #=> Return this: 1
puts my_enum2.next #=> Outside the loop
#=> ERROR: .....in `next': iteration reached an end (StopIteration)
Background: I need to determine the number of records in a remote location. The records are sequentially numbered (starting at 0, no gaps) and can only be fetched one by one based on their number.
The method to fetch the records over the network returns a truthy value upon success and a falsey value upon failure. Here'a an example:
fetch_record(0) #=> true (record #0 exists)
fetch_record(1) #=> true (record #1 exists)
fetch_record(2) #=> true (record #2 exists)
fetch_record(3) #=> nil (no record #3)
I'm trying to find an elegant way to count how many times I can call fetch_record with increasing argument until it returns nil (3 times in the above example).
What I've tried so far
Given this simplified implementation for testing purposes:
def fetch_record(index)
raise if index > 3 # just to prevent endless loops
true if index < 3
end
Here are my attempts:
A while loop with an explicit counter variable would obviously work, but I doesn't look very idiomatic:
i = 0
i += 1 while fetch_record(i)
i
#=> 3
I could use step and break with an explicit result value but that looks cumbersome:
0.step(by: 1).each { |i| break i unless fetch_record(i) }
#=> 3
A lazy enumerator is even worse:
0.step(by: 1).lazy.map { |i| fetch_record(i) }.take_while(&:itself).count
#=> 3
Is there an easier way to accomplish the above?
Assuming you are free to implement fetch_record the other way round, to return true for inexisting record:
def fetch_record index
index >= 3
end
(0..Float::INFINITY).detect &method(:fetch_record)
#⇒ 3
take_while anyone?
records = 0.step.take_while{|i| fetch_record(i)}
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
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
So, maybe I'm over-complicating something that isn't that hard, but here goes.
In Ruby, there's a method of looping called .each. I think that this is very cool--but what I'm finding less cool is the amount of stuff written about the pipe that comes after it (or any other do-type loop in Ruby, it would seem). Sometimes there is a single thing in the pipe:
basket.each do |fruit|
puts "This is a #{fruit}.\n"
end
But sometimes, there are two things in this pipe, like so:
contacts.each do |name, profession|
puts "#{name} is a #{profession}.\n"
end
So what I'm wondering now, is it possible to have more than two items in that pipe? Like if I have a huge, big, and ugly multi-dim array?
What if I add things to my pipe and they're not there? Will it give the value in the pipe nil? Or will it throw an error?
Again, sorry if this is obvious to long-time Rubyists, but I came from the land of strictly typed variables, and I'm now leaving PHP land, too. :)
EDIT
So what if I have something like this:
categories = [["Bathroom", "Bathroom Fixtures", "Plumbing"],
["Ceiling Fixtures", "Chandeliers", "Flush Mounts", "Mini Chandeliers", "Semi-Flush Mounts", "Pendants", "Track Lighting", "Mini Pendants", "Directional", "Island/Pool Table", "Recessed Lighting"],
["Outdoor", "Exterior", "Landscape Lighting"],
["Fans", "Fans", "Fan Accessories"],
["Lamps", "Lamps", "Shades"],
["Wall Fixtures", "Sconces", "Foyer/Hall Lanterns"],
["Specialty", "Undercabinet", "Light Bulbs", "Lighting Controls", "Glass", "Specialty Items", "Utility"],
["Home Decor", "Decor/Home Accents", "Furniture"]]
Can I loop through it like this?
categories.each do |category, subcats|
puts "The main category is #{category} and the sub categories are: "
subcats.each do |subcat|
puts "#{subcat}, "
end
end
Lets start with a break down of the each method.
a = [1,2,3,4,5]
a.each do |num|
puts num
end
# 1
# 2
# 3
# 4
# 5
The do ... end portion is called a block
This block accepts one parameter (an element in the array)
The way you pass parameters to a block is with |'s
If you supply more than one argument to the block:
a.each do |num, x|
puts num
puts x
end
# 1
#
# 2
#
# 3
#
# 4
#
# 5
#
x is nil for each iteration.
Lets write a method of our own that uses blocks so you can see how they work.
def my_each(a=[])
a.each do |x|
yield x if block_given?
end
end
my_each(a) do |num|
puts num
end
Here yield x is saying, execute the supplied block and pass x to it.
If you pass another parameter to your block, it is nil. Why?
Our implementation of my_each doesn't know anything about a second parameter so it does not yield anything so it remains nil.
When you have a simple array, the following things happen:
arr = [1,2,3,4]
arr.each do |x|
p x
end
1
2
3
4
=> [1,2,3,4]
arr.each do |x,y|
p x
p y
end
1
nil
2
nil
3
nil
4
nil
=> [1,2,3,4]
so if ruby doesn't know what to put into the block argument, it simply sets it to nil. Now consider a nested array:
arr = [[1,2],[3,4],[5,6]]
arr.each do |x|
p x
end
[1, 2]
[3, 4]
[5, 6]
=> [[1,2],[3,4],[5,6]]
arr.each do |x,y|
p x
p y
end
1
2
3
4
5
6
=> [[1,2],[3,4],[5,6]]
In this case, ruby assumes that you want to assign the two elements of the inner arrays to the block variables x and y. The same thing applies to hashes, where Ruby assigns the key and value to x and y:
hash = {1 => 2, 3 => 4, 5 => 6}
hash.each do |x,y|
p x
p y
end
1
2
3
4
5
6
=> {1=>2,3=>4,5=>6}
When you don't have enough elements in the nested arrays, the block variables are assigned nil, indeed. When there are too many of them, they are simply discarded:
arr = [[1,2,3],[4,5],[6]]
arr.each do |x,y|
p x
p y
end
1
2
4
5
6
nil
=> [[1,2,3],[4,5],[6]]
pretty straightforward!
EDIT:
As for your edited question: no, you cannot apply this 1:1 to Ruby code, you would have to manually apply the splat operator (*) to subcats. This way, ruby assigns all remaining elements to the 'splatted' block variable:
categories.each do |category,*subcats|
puts "The main category is #{category} and the sub categories are: "
subcats.each do |subcat|
puts "#{subcat}, "
end
end
although i would generate a comma-separated list of subcategories like this:
categories.each do |category,*subcats|
puts "The main category is #{category} and the sub categories are: "
puts subcats.join(', ')
end
EDIT 2:
Oh, and you would not handle a huge ugly evil multidimensional array by defining a lot of block parameters for its elements. You probably would iterate through it using nested loops as in almost every other language, if only because you never know how many elements it contains.
The pipes you are talking about is a parameter list of a block "variable". Actually that is some kind of a function pointer, and the pipes marks the parameter list.
Check the description of array.each.
This is not magic, the number of parameters is defined in the block, you can't add more than that, if you do, they won't get a value. The reason is for "sometime" there can be more than one, is that it's probably a hash.each, which has two parameters, a key and a value.
You can create your own functions with block parameters, read this.
For your iteration problem, you can use a hash, or you can write your own iterator.
Multiple Arguments to a Block
Array#each iterates over an array object, and passes either a single object into the block or returns an enumerator. You can redefine this behavior, but #each is the wrong method if you want multiple values at a time; see Enumerator#each_slice for an alternative.
Data Structures
Your problem would be easier to solve with the right data structure. Instead of an array, you should consider using a hash. For example:
categories =
{"Bathroom"=>["Bathroom Fixtures", "Plumbing"],
"Ceiling Fixtures"=>["Chandeliers", "Flush Mounts", "Mini Chandeliers"]}
categories.each do |key, value|
puts "#{key}:"
value.each { |v| puts "\t%s" % v }
end
This returns:
Bathroom:
Bathroom Fixtures
Plumbing
Ceiling Fixtures:
Chandeliers
Flush Mounts
Mini Chandeliers