How to implement an enumerator in Ruby? - ruby

For example:
a = [1,2,3,4,5]
a.delete_if { |x| x > 3 }
is equivalent to:
a = [1,2,3,4,5]
a.delete_if.each.each.each.each { |x| x > 3 }
I know a.delete_if returns an enumerator. But how does it know it should delete object when the each block returns true? How to implement delete_if by hand(and in Ruby)?

You can take a look at the Rubinius source code: enumerable module
Here an example of the reject method:
def reject
return to_enum(:reject) unless block_given?
ary = []
each do |o|
ary << o unless yield(o)
end
ary
end

In the implementation of delete_if, the code can verify the value returned from yield to decide whether or not to delete the given entry from the array.
You can read Implementing Iterators in the Programming Ruby guide for more details, but it would looks something like:
class Array
def delete_if
reject { |i| yield i }.to_a
end
end
The above uses yield to pass each item in the array to the block associated with the call to delete_if, and implicitly returns the value of the yield to the outer reject call.

Related

Undefined method error when using 'yield' and 'even?'

When I try to call a method with select and num.even? as follows,
def selection(array)
puts "This is inside the method"
return yield(array)
end
collection = [1,2,3,4,5]
selection(collection.select) { |num| num.even? }
I get a no defined Method error:
undefined method `even?' for #<Enumerator: [1, 2, 3, 4, 5]:select>
I'm looking for a return of even numbers in the array. I can get the select even? combo work in other examples of an array.
Array#select returns an Enumerator instance if no block was given to it
then you call selection method passing the result of call to collection.select as an argument and { |num| num.even? } as block
inside your selection function you yield the argument (an Enumerator instance) to the block
in the block you call even? on the block argument, resulting in the error message you receive.
I am unsure what’s wrong with collection.select(&:even?), but if you want to re-implement it yourself, here you go:
def selection(array)
# convention: return enumerator unless block is given
return enum_for(:selection) unless block_given?
enumerator = array.each
result = []
loop do
(value = enumerator.next) rescue return result
result.push(value) if yield value
end
end
selection([1,2,3,4,5]) { |num| num.even? }
#⇒ [2, 4]
You are seeing this error because you are passing an enumerator object in to your "selection" method ... that is, the result of "collection.select" is an Enumerator and enumerators do not implement an "even" method.
I believe that you are trying to implement your own version of "select". The following is one way to achieve your stated intent: "I'm looking for a return of even numbers in the array."
def selection(array)
results = []
for item in array do
results << item if yield item
end
results
end
collection = [1,2,3,4,5]
puts selection(collection) { |num| num.even? }
# => [2,4]
https://mixandgo.com/learn/mastering-ruby-blocks-in-less-than-5-minutes is a nice reference
yield(array) is passing the whole array in one go to the block given to the method, so it is trying to call even? on the array.

How can I modify my ruby method so it takes in a block of code as well?

I have a method called myFilter that takes in an array, and filters out the elements that don't meet the requirement.
For example.
arr = [4,5,8,9,1,3,6]
answer = myfilter(arr) {|i| i>=5}
this run would return an array with elements 5,8,9,6 since they are all greater than or equal to 5.
How would I preform this? the algorithm is easy, but I don't understand how we take in that condition.
Thank you.
I take for granted you don't want to use select method or similar but you want to understand how blocks work.
def my_filter(arr)
if block_given?
result = []
arr.each { |element| result.push(element) if yield element } # here you use the block passed to this method and execute it with the current element using yield
result
else
arr
end
end
The idiomatic way would be:
def my_filter(arr)
return enum_for(:my_filter, arr) unless block_given?
arr.each_with_object([]) do |e, acc|
acc << e if yield e
end
end
More info on Enumerator::Lazy#enum_for.
you can do
def my_filter(arr, &block)
arr.select(&block)
end
then call
my_filter([1, 2, 3]) { |e| e > 2 }
=> [3]
but instead you can just call select with a block directly :)

Return value of a for loop in Ruby

I understand that, in Ruby, a for loop is an expression and therefore has a value which can be assigned to a variable. For example
x = for i in 0..3 do
end
sets x to 0..3 - the range over which the loop iterates.
Is the return value of a for loop always the range as above, or can it differ depending on the body of the loop?
What is returned by the for loop is dependent on how the enumerator you are looping over is implemented. For example, let's implement our own enumerator.
chipmunks = Enumerator.new { |c|
c << "Alvin"
c << "Simon"
c << "Theodore"
"Dave"
}
Notice that the last thing in the Enumerator is the string "Dave". Now let's use this in a .each loop.
result = chipmunk.each do |chipmunk|
puts chipmunk
end
#=> Alvin
#=> Simon
#=> Theodore
puts result
#=> Dave
My guess is that Range is implemented in such a way that its enumerator returns itself. By doing so, it gives you the ability to chain methods.
(1..100).each { |n| something(n) }.each {|y| something_else(y) }

Trying to write my own all? method in Ruby

There is a method called all? in Enumerable.
I'm trying to learn all the methods of Enumberable's library by writing them myself.
This is what I've come up so far for the all? method. I sorta understand it but I got stumped when trying to pass initialized values to my method.
EDIT for the record, I'm aware that enum method that I have is not the right way ie, it's hard-coded array. This is for self-learning purposes. I'm just trying to figure out how to pass the initialized values to my all? method. That's why I wrote enum in the first place, to see that it is working for sure. Please don't take this class as a literal gospel. Thank you.
class LearningMethods
def initialize(values)
#values = values
end
def enum
array = [10, 3, 5]
end
def all?(a)
yield(a)
end
end
c = LearningMethods.new([10, 3, 5])
p c.enum.all? {|x| x >= 3 } #this works
p c.all?(10) { |x| x >= 3 } #this works
p c.all?(#values) { |x| x >= 3 } #this doesn't work. Why not? And how do I pass the initialized values?
I'm not sure why you need enum at all? Enumerable is a module included in array, so if you're not familiar with this I recommend you read about "modules and mix-ins" in Ruby.
all? works simply by passing EACH of the array elements to the block. If there is ANY element (at least 1) for which the block returns false, then all? evaluates to false. Try analyzing this code:
class MyAllImplementation
def initialize(array)
#array = array
end
def my_all?
#array.each do |element| # for each element of the array
return true unless block_given? # this makes sure our program doesn't crash if we don't give my_all? a block.
true_false = yield(element) # pass that element to the block
return false unless true_false # if for ANY element the block evaluates to false, return false
end
return true # Hooray! The loop which went over each element of our array ended, and none evaluted to false, that means all elements must have been true for the block.
end
end
a = MyAllImplementation.new([1,2,3])
p a.my_all? { |x| x > 0 } #=> true
p a.my_all? { |x| x > 1 } # false, because 1 is not bigger than 1, it's equal to 1

Are there something like Python generators in Ruby?

I am new to Ruby, is there a way to yield values from Ruby functions? If yes, how? If not, what are my options to write lazy code?
Ruby's yield keyword is something very different from the Python keyword with the same name, so don't be confused by it. Ruby's yield keyword is syntactic sugar for calling a block associated with a method.
The closest equivalent is Ruby's Enumerator class. For example, the equivalent of the Python:
def eternal_sequence():
i = 0
while True:
yield i
i += 1
is this:
def eternal_sequence
Enumerator.new do |enum|
i = 0
while true
enum.yield i # <- Notice that this is the yield method of the enumerator, not the yield keyword
i +=1
end
end
end
You can also create Enumerators for existing enumeration methods with enum_for. For example, ('a'..'z').enum_for(:each_with_index) gives you an enumerator of the lowercase letters along with their place in the alphabet. You get this for free with the standard Enumerable methods like each_with_index in 1.9, so you can just write ('a'..'z').each_with_index to get the enumerator.
I've seen Fibers used in that way, look at an example from this article:
fib = Fiber.new do
x, y = 0, 1
loop do
Fiber.yield y
x,y = y,x+y
end
end
20.times { puts fib.resume }
If you are looking to lazily generate values, #Chuck's answer is the correct one.
If you are looking to lazily iterate over a collection, Ruby 2.0 introduced the new .lazy enumerator.
range = 1..Float::INFINITY
puts range.map { |x| x+1 }.first(10) # infinite loop
puts range.lazy.map { |x| x+1 }.first(10) # [2, 3, 4, 5, 6, 7, 8, 9, 10, 11]
Ruby supports generators out of the box using Enumerable::Generator:
require 'generator'
# Generator from an Enumerable object
g = Generator.new(['A', 'B', 'C', 'Z'])
while g.next?
puts g.next
end
# Generator from a block
g = Generator.new { |g|
for i in 'A'..'C'
g.yield i
end
g.yield 'Z'
}
# The same result as above
while g.next?
puts g.next
end
https://ruby-doc.org/stdlib-1.8.7/libdoc/generator/rdoc/Generator.html
Class Enumerator and its method next behave similar
https://docs.ruby-lang.org/en/3.1/Enumerator.html#method-i-next
range = 1..Float::INFINITY
enumerator = range.each
puts enumerator.class # => Enumerator
puts enumerator.next # => 1
puts enumerator.next # => 2
puts enumerator.next # => 3

Resources