Calling to_enum on a MatchData object

  • Thread starter Vahagn Hayrapetyan
  • Start date
V

Vahagn Hayrapetyan

Hi, I have the following snippet of code:

def x
File.open('screen.css') do |f|
while line = f.gets
file = line.match(/"(\w*.css)"/)
puts file.class #MatchData
puts file.methods #to_enum defined
e = file.to_enum
puts e.class #Enumerator
puts e.methods #each method defined
e.each do |entry|:
puts entry
end
end
end
end

The result of the regex operation gets stored in "file" as a MatchData
object. Then I convert it to an Enumerator object, which by all accounts
has an each method defined. Yet when I try to "e.each" here's what I
get:

undefined method `each' for #<MatchData "\"reset.css\"" 1:"reset.css">
(NoMethodError).

Then I check the docs for the MatchData class:
http://www.ruby-doc.org/core/classes/MatchData.html

and find that no, "to_enum" isn't defined. But then I don't understand
why
file.methods lists it, and why no error is generated when I call
file.to_enum.

Help really appreciated.
 
7

7stud --

Vahagn Hayrapetyan wrote in post #991813:
Hi, I have the following snippet of code:

def x
File.open('screen.css') do |f|
while line = f.gets
file = line.match(/"(\w*.css)"/)
puts file.class #MatchData
puts file.methods #to_enum defined
e = file.to_enum
puts e.class #Enumerator
puts e.methods #each method defined
e.each do |entry|
puts entry
end
end
end
end

The result of the regex operation gets stored in "file" as a MatchData
object. Then I convert it to an Enumerator object, which by all accounts
has an each method defined. Yet when I try to "e.each" here's what I
get:

undefined method `each' for #<MatchData "\"reset.css\"" 1:"reset.css">
(NoMethodError).

Then I check the docs for the MatchData class:
http://www.ruby-doc.org/core/classes/MatchData.html

and find that no, "to_enum" isn't defined.

to_enum() is defined in Object, and all classes inherit from Object.
You need to hook up an enumerator's each() method to *an iteration
method of another object*. You specify the iteration method as an
argument to to_enum(). If you don't specify an iteration method, the
default is each()--and the MatchData class does not define an each()
method. Hence your error.
 
7

7stud --

By the way, one way to get a handle on which instance methods a class
defines itself and which are inherited is like this:

not_inherited = false
puts MatchData.instance_methods(not_inherited).sort

#'false' means don't display inherited methods

--output:--
==
[]
begin
captures
end
eql?
hash
inspect
length
names
offset
post_match
pre_match
regexp
size
string
to_a
to_s
values_at


Then to find to_enum() you need to look in parent classes and any
modules that MatchData includes.
 
7

7stud --

You also might want to use the synonym enum_for() instead of to_enum(),
so that it is readily apparent that you are supposed to supply an
argument.
 
V

Vahagn Hayrapetyan

Thank you for your explanations, I can see now that :each is an
inherited method and it had to be ferreted out of the MatchData set of
methods.

So in order to use an enumerator, an object itself has to define an
:)each) method and then an enumerator just augments that. I agree that
enum_for:)some_method) is better syntax. This is what I've done just to
experiment:

def x
File.open('screen.css') do |f|
while line = f.gets
file = line.match(/" (\w* [.] css) "/xms)
puts file.class
puts file.respond_to?:)each) #false
file = line.match(/" (\w* [.] css) "/xms).captures
puts file.class #Array
puts file.class.instance_methods(false).sort #each is defined

e = file.enum_for:)each) #just the default
e.each do |entry| #now the enumerator works
puts entry
end

end
end
end

Regarding the regexp: yeah - I was using the universal matcher instead
of the dot character by mistake. It worked to fetch the filenames but it
was a false positive of course.

"It would be nice if the docs specified any parent class to make the
hierarchy easy to follow."

That would be helpful, definitely.

Cheers, Vahagn
 
7

7stud --

Vahagn Hayrapetyan wrote in post #991914:
Thank you for your explanations, I can see now that :each is an
inherited method and it had to be ferreted out of the MatchData set of
methods.

I'm not sure what you are referring to because each() is neither defined
nor inherited in the MatchData class. Check the output of:

md = /h/.match('hello')
puts md.methods.sort

And Enumerator defines its own each() method.

So in order to use an enumerator, an object itself has to define an
:)each) method and then an enumerator just augments that.

No. An enumerator can be hooked up to any "iteration method" for an
object. An "iteration method" is any method that calls yield(). Here
is an example:

class Dog
def bark
yield 'woof'
yield 'whooooo'
yield 'ruuuff'
end
end

dog = Dog.new
e = dog.enum_for:)bark)

results = e.select{|sound| sound[0] == 'w'}
p results

--output:---
["woof", "whooooo"]


results = e.map{|sound| "#{sound.capitalize}!}" }
p results

--output:--
["Woof!}", "Whooooo!}", "Ruuuff!}"]


if e.include?('woof')
puts 'yes'
end

--output:--
yes


You may be confusing Enumer-ator with something you read about
Enumer-able. If you want to make the Enumer-able methods available to
your class, e.g. group_by(), each_slice(), select(), map(), include?(),
etc., then your class has to:

1) Define an each() method, and
2) include Enumerable

Now the confusing part: the Enumer-ator class defines a few of its own
methods, but it also includes Enumer-able, so an enumerator object has
both the Enumerator and Enumerable methods available to it.

An enumerator can also be hooked up to a block that you define:

e = Enumerator.new do |y|
10.upto(14) do |num|
y << num
end
end

results = e.map{|x| x + 5}
p results

--output:--
[15, 16, 17, 18, 19]
 
7

7stud --

Here's how you can use an enumerator to create an infinite array, from
which you can print out finite chunks:

e = Enumerator.new do |y|
(0..9).cycle do |x|
y << x
end
end


p e.take(15)

--output:--
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4]
 
G

Gary Wright

def x
File.open('screen.css') do |f|
while line = f.gets
file = line.match(/" (\w* [.] css) "/xms)
puts file.class
puts file.respond_to?:)each) #false
file = line.match(/" (\w* [.] css) "/xms).captures
puts file.class #Array
puts file.class.instance_methods(false).sort #each is defined

e = file.enum_for:)each) #just the default
e.each do |entry| #now the enumerator works
puts entry
end

end
end
end


You are doing lots of unnecessary work there. Since MatchData#captures
returns an array and Array already defines each, you don't need to create
any enumerator at all to iterate through the #captures array:
def x
File.open('screen.css') do |f|
while line = f.gets
file = line.match(/" (\w* [.] css) "/xms)
puts file.class
puts file.respond_to?:)each) #false
file = line.match(/" (\w* [.] css) "/xms).captures
file.each do |entry|
puts entry
end
end
end
end



Gary Wright
 
7

7stud --

Gary Wright wrote in post #991944:
You are doing lots of unnecessary work there. Since MatchData#captures
returns an array and Array already defines each, you don't need to
create
any enumerator at all to iterate through the #captures array:

As the op said:
 
V

Vahagn Hayrapetyan

Hi,
I'm not sure what you are referring to because each() is neither defined
nor inherited in the MatchData class. Check the output of:

md = /h/.match('hello')
puts md.methods.sort

And Enumerator defines its own each() method.

Sorry - I meant to say "enum_for / to_enum" was inherited and had to be
ferreted out. The reason why I made this mistake is because of this:

o = Object.new
e = o.enum_for #No argument given, so :each is implied. No errors!
e.next #NoMethodError: undefined method `each' for #<Object:0x12ed00>
o.respond_to?:)each) #false. Aha!

My confusion with this stems from line 2 above - that you can define a
method using a non-existent method as default argument and not get any
error message as a consequence of that. But hey - "Ruby is dynamic and
so is human nature", so I think I am going to like this behavior anyway.
You may be confusing Enumer-ator with something you read about
Enumer-able.

Exactly! I was mixing them up with regards to "each" and "yield". "The
Well-Grounded Rubyist" is a great book though.

Thanks again for your help - your examples are excellent.
 
V

Vahagn Hayrapetyan

7stud -- wrote in post #991942:
Here's how you can use an enumerator to create an infinite array--from
which you can print out finite chunks:

e = Enumerator.new do |y|
(0..9).cycle do |num|
y << num
end
end


p e.take(15)

--output:--
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4]

I just tried out (0..9).cycle {|n| puts n} in an irb session and had to
Ctrl+C it to stop the loop. However when wrapped in a Enumerator like
your example above, just an Enumerator::Generator object is returned.
What happens to the infinite loop - does it still crunch the numbers out
in the background or does it "stop" at some point? I looked at puts
e.methods.sort
but they aren't very revealing of what's going on with the loop...
 
V

Vahagn Hayrapetyan

You are doing lots of unnecessary work there. Since MatchData#captures
As the op said:

Exactly - that was just to get proof of Enumerator working. In my
method, I just use :each of an Array object:

def x
File.open('screen.css') do |f|
while line = f.gets
file = line.match(/" (\w* [.] css) "/xms).captures
file.each do |entry|
puts entry
y(entry)
end
end
end
end
 
R

Robert Klemme

7stud -- wrote in post #991942:
Here's how you can use an enumerator to create an infinite array--from
which you can print out finite chunks:

e =3D Enumerator.new do |y|
=A0 (0..9).cycle do |num|
=A0 =A0 y << num
=A0 end
end


p e.take(15)

--output:--
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 0, 1, 2, 3, 4]

I just tried out (0..9).cycle {|n| puts n} in an irb session and had to
Ctrl+C it to stop the loop. However when wrapped in a Enumerator like
your example above, just an Enumerator::Generator object is returned.
What happens to the infinite loop - does it still crunch the numbers out
in the background or does it "stop" at some point? I looked at puts
e.methods.sort
but they aren't very revealing of what's going on with the loop...

The logic is a bit tricky. :) The Enumerator::Generator is just
something which can enumerate something. There is no infinite loop
until you start iterating that instance via #each or any other method
defined in Enumerable. Actually the loop is "interrupted" every time
"y << num" is invoked.

Demonstration without infinite loop:

irb(main):003:0> e =3D Enumerator.new {|y| puts "+"; 5.times {|i| puts
"-#{i}"; y << i}}
=3D> #<Enumerator: #<Enumerator::Generator:0x1093d1a8>:each>
irb(main):004:0> e.each {|a| puts "*#{a}"}
+
-0
*0
-1
*1
-2
*2
-3
*3
-4
*4
=3D> 5

You can see how the block from line 4 and the last block in line 3 are
called interleavingly. Here's another way to look at it:

irb(main):013:0> e =3D Enumerator.new {|y| puts "+", y.class; y << 0}
=3D> #<Enumerator: #<Enumerator::Generator:0x1097b340>:each>
irb(main):014:0> e.each { puts caller(0) }
+
Enumerator::Yielder
(irb):14:in `block in irb_binding'
(irb):13:in `<<'
(irb):13:in `block in irb_binding'
(irb):14:in `each'
(irb):14:in `each'
(irb):14:in `irb_binding'
/opt/lib/ruby/1.9.1/irb/workspace.rb:80:in `eval'
...
/opt/bin/irb19:12:in `<main>'
=3D> #<Enumerator::Yielder:0x10978f14>

Actually the block of #each is invoked through Enumerator::Yielder#<<.

Kind regards

robert

--=20
remember.guy do |as, often| as.you_can - without end
http://blog.rubybestpractices.com/
 
G

Gary Wright

Exactly - that was just to get proof of Enumerator working. In my
method, I just use :each of an Array object:

OK. I guess I didn't read closely enough.

Gary Wright
 
V

Vahagn Hayrapetyan

Posted by Robert K. (robert_k78) on 2011-04-13 13:45
The logic is a bit tricky. :) The Enumerator::Generator is just
something which can enumerate something. There is no infinite loop
until you start iterating that instance via #each or any other method
defined in Enumerable. Actually the loop is "interrupted" every time
"y << num" is invoked.

OK. So I guess this syntax:

e = Enumerator.new do |y|
(0..9).cycle do |num|
y << num
end
end

Is more declarative in that it just declares an enumerator without
setting off the infinite loop inside. To execute the loop one needs
something like:

e.take(15)

And then there is no infinite loop either, as the amount of iterations
is scoped by the argument to take().

Great examples in your post, I appreciate it!
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,754
Messages
2,569,521
Members
44,995
Latest member
PinupduzSap

Latest Threads

Top