Welcome to the Treehouse Community
Want to collaborate on code errors? Have bugs you need feedback on? Looking for an extra set of eyes on your latest project? Get support with fellow developers, designers, and programmers of all backgrounds and skill levels here with the Treehouse Community! While you're at it, check out some resources Treehouse students have shared here.
Looking to learn something new?
Treehouse offers a seven day free trial for new students. Get access to thousands of hours of content and join thousands of Treehouse students and alumni in the community today.
Start your free trialUnsubscribed User
3,309 PointsCan someone help me understand when and why to use Enumerators and Enumerable?
I've done some experimentation and reading. I understand we've been using Enumerable methods all along and Array includes Enumerable, ie:
p [0, 42].each # => #<Enumerator: [0, 42]:each>
p Array.included_modules # [Enumerable, Kernel]
p Enumerable.instance_methods.sort
# => [:all?, :any?, :chunk, :collect, :collect_concat, :count, :cycle,
# :detect, :drop, :drop_while, :each_cons, :each_entry, :each_slice,
# :each_with_index, :each_with_object, :entries, :find, :find_all,
# :find_index, :first, :flat_map, :grep, :group_by, :include?, :inject,
# :map, :max, :max_by, :member?, :min, :min_by, :minmax, :minmax_by,
# :none?, :one?, :partition, :reduce, :reject, :reverse_each, :select,
# :slice_before, :sort, :sort_by, :take, :take_while, :to_a, :zip]
However, you don't need to include Enumerable in order to use these. You can also define your own sort, to_s and each without include Enumerable:
class Friends
attr_reader :name, :age
def initialize(name, age)
@name, @age = name, age
end
def <=> (friend)
age <=> friend.age
end
def each &block
@name.each &block
end
def to_s
"#{name}, #{age}"
end
end
friends = [Friends.new("Chandler", 24), Friends.new("Monica", 22),
Friends.new("Joey", 23), Friends.new("Phoebe", 26)]
p Friends.included_modules # [Kernel]
p friends.sort
# => Monica, 22
# => Joey, 23
# => Chandler, 24
# => Phoebe, 25
Why is it when I switch things around (below) I have to include Enumerable to use map, select, etc., otherwise I get a NoMethodError? I can see that each isn't defined in Enumerable (as above) and the others are, but what Ruby Voodoo have I done to require Enumerable? What is it for that I can't already do?
class Friends
include Enumerable
attr_accessor :members
def initialize
@members = []
end
def each &block
@members.each &block
end
end
friends = Friends.new
p Friends.included_modules # [Enumerable, Kernel]
friends.members = ["Chandler", "Joey", "Monica", "Phoebe"]
p friends.map &:downcase # => ["chandler", "joey", "monica", "phoebe"]
friends.each { |friend| puts friend.swapcase if friend =~ /^P/} # => pHOEBE
p friends.select { |friend| friend =~ /C/ } # => ["Chandler"]
The best explanation I've found so far is: http://rubymonk.com/learning/books/4-ruby-primer-ascent/chapters/44-collections/lessons/96-enumerators-and-enumerables
6 Answers
David Clausen
11,403 PointsWell ruby array isn't a true array. Array is just a memory address that hold multiple memory address. So you can have an array store multiple location instead of have 5 objects, you have 1 array with 5 objects.
Ruby's array seems is an really just an Object that implement Enumerable MIXIN. Which allow sorting and other various methods to iterate over a list of objects inside a traditional array. Basically a mixin a class designed to mixed in with other classes. So a true array would have no way to sort or search or find. You'd need to go through the index of an array just to get its length yourself, you'd have to iterate of each index to do finds anything yourself.
Basically Array get its Enumerable methods you use from Enumerable. Enumerable was created to iterate and over data. So you made Friends capable with Enumerable. You can can make anything capable of being iterated over with Enumerable methods by defining what each method return. Digging deep into Enumerable you could ditch array for any data structure you create and implement Enumerable. Why would you do this? No clue but the tool is out there. Mostly likely you won't use Enumerable much. But let say you are designing a API that other developers use, you may use it as an ease of use if functionally you feel the object would naturally be capable of being interated over.
Example:
You roll your own game engine, you have a class called entity which holds things about it like:
Name, Location, Size, Weight, Color, Shoes
These are all different data types, each one is a class too. So Location.X means somthing, Size.Width, Weight.ConverToKilo, ect.
You feel that the class should naturally iterate over each type of data.
Each of these class has a name variable that tells you what it is, like Name.name is "name" or Weight.name is "weight".
def each &block
yield Name
yield Location
yield Size
yield Weight
yield Color
yield Shoes
end
Now it will take each one of those and see them as each distinct each in its list.
for attribute in entity
printf (attribute.name + "\n")
end
This would print:
"name"
"location"
"size"
"weight"
"color"
"shoes"
What use is this? I dont know, but you could just iterate over your class entity and access each class in the for loop, so when its on Weight you could do attribute.weight.
Source:
https://practicingruby.com/articles/building-enumerable-and-enumerator
http://kconrails.com/2010/11/30/ruby-enumerable-primer-part-1-the-basics/
http://www.sitepoint.com/guide-ruby-collections-iii-enumerable-enumerator/
Unsubscribed User
3,309 PointsIt is heavy. As you said, it's all about each and yield. I'm going to try and summarize what you said below.
When do you need to include Enumerable in your class Foo? When you are treating Foo objects as collections. Collections (or lists of things) can be arrays, hashes, file names, etc., that you iterate over. When it's a class object, ie. an instance of your class, you include Enumerable
to get those methods. If your array is just an array, it already includes Enumerable.
I'd like to clarify the difference between the Enumerator class and enumeration in general.
Why use enumerators? Enumerators allow you to store data, defer enumeration, can be chained together and more.
Many classes have Enumerable methods. The Enumerator class inherits from the Enumerable module.
p Array.ancestors # [Array, Enumerable, Object, Kernel, BasicObject]
p Hash.ancestors # [Hash, Enumerable, Object, Kernel, BasicObject]
p Enumerator.ancestors # [Enumerator, Enumerable, Object, Kernel, BasicObject]
Enumerators generate data; Enumerable methods consume it.
- Generate the data and store it, deferring enumeration
- Consume the data by yielding it to a block
p enum = (1..10).each # #<Enumerator: 1..10:each>
p enum.inject { |sum, n| sum + n } # => 55
p enum.select { |x| x.even? } # => [2, 4, 6, 8, 10]
p enum.select { |x| x.even? }.inject(:+) # => 30
The enumerable method calls each to request data; later the enumerator object provides the data by yielding it to a block.
p enum = ["cat", "bat", "rat"].map # #<Enumerator: ["cat", "bat", "rat"]:map>
p enum.with_index { |word, index| "#{index}: #{word}" } # ["0: cat", "1: bat", "2: rat"]
# Same as:
p ["cat", "bat", "rat"].map.with_index { |word, index| "#{index}: #{word}" }
# ["0: cat", "1: bat", "2: rat"]
For more on Enumerator (and Collections) see my link to RubyMonk (above) and http://patshaughnessy.net/2013/4/3/ruby-2-0-works-hard-so-you-can-be-lazy http://blog.carbonfive.com/2012/10/02/enumerator-rubys-versatile-iterator/
David Clausen
11,403 PointsYah great addition, seems you got a good understanding of them now. Seems your research has probably helped a lot with programming concepts in general as that last post display a good unserstanind of the structure surrounding that class. Enumurable, enumerator, yeids, data loops, store and retrieve.
I'd go ahead and mark your answer as answered BTW.
Unsubscribed User
3,309 PointsI wouldn't have gotten there without your help and patience, so thanks. I did "roll my own" enumerable. In order to do so, I needed to learn a dozen other things, which solidified the first things. I didn't know which answer to mark as "best answer", so I just chose one. My understanding grew as I went.
David Clausen
11,403 PointsI don't know anything about ruby. But examining your code they are different completely.
First code you are actually making an array called 'friends'. Then you are calling your class and creating a new one each time, Friends.new()
friends = [Friends.new("Chandler", 24), Friends.new("Monica", 22), Friends.new("Joey", 23), Friends.new("Phoebe", 26)]
You actually created an array with 4 Friends class members. Since friends is an ruby array then friends.sort works, because friends is an array, Friends (with a capital F) is your class.
The second code implements Enumerable in the class Friends
friends = Friends.new
This actually assigns Friends class to friends instead of making friends an array directly
friends.members = ["Chandler", "Joey", "Monica", "Phoebe"]
Is assigning that array(["Chandler", "Joey", "Monica", "Phoebe"]) to the array INSIDE Friends class, so friends.members is an array.
def each &block
@members.each &block
this is telling enumerable what an element is or each means. So it just grabs the enumerable in friends array called member.each and gives it to friends.each so now when you do enumerable commands you can do them directly to friends. When an enumerable sort or select it goes through EACH item inside it, so .each just references what each is, in your second example friends.each is each on of your strings in members array. Your first example since friends is an array then friends.each would reference each one of your Friends class in the array.
friends.sort, if you didn't you have to allow access to members and write friends.members.sort, or friends.members.select instead of friends.method.
So basically you made friends an array the first time and filled the array with 4 objects of class Friends, and the second time you made friends an class Friends with a array inside it, then you extended enumerable in friends to access the enumerables directly from Friends instead of Friends.members.
Does this make sense?
Unsubscribed User
3,309 PointsYes, you're completely right, I should have written this:
class Friends
attr_accessor :members
def initialize
@members = []
end
def <=> (friend)
members <=> friend.members
end
def each &block
@members.each &block
end
end
friends = Friends.new
friends = ["Monica", "Chandler", "Joey", "Phoebe"]
I wanted to demonstrate (from our class BankAccount example) that you could define your own sort using def (insert spaceship operator here*), each and to_s without include Enumerable
. [Note to readers: include and extend are different things in Ruby.] *The combined comparison/spaceship operator does not display in Markdown.
I appreciate the clarification on my Classes and Arrays. Now that the code is almost the same, I can clearly see that adding friends.members = ["Monica... is the Voodoo bit which makes it need Enumerable. Thanks, I didn't catch that. However, the Enumerable bit is still Voodoo - other than populating the array, what's going on?
In the context of Array - we already have the same methods and we can re-define those methods. Why and when do you use Enumerable/Enumerator, since both programs provide identical output?
David Clausen
11,403 PointsCause friends is still an array. Since in Ruby you don't have to declare a type first you can easily change it by mistake.
friends = Friends.new
friends.class
#Will tell you what class it is hint: class Friends
friends = ["Monica", "Chandler", "Joey", "Phoebe"]
friends.class
# will tell you what class it is hint: array
The voodoo your doing is the same, you are just creating an array of strings.
Do this here: You'll get an error about sort
class Friends
attr_accessor :members
def initialize
@members = []
end
def <=> (friend)
members <=> friend.members
end
def each &block
@members.each &block
end
end
friends = Friends.new
friends.class
friends.members = ["Monica", "Chandler", "Joey", "Phoebe"]
friends.members
friends.sort
Now add include Enumerable after class Friends and that same code I posted works.
Man I haven't even learn ruby, throwing stuff at me I have to learn on the fly how dare you! :)
In all seriousness once you get a grasp on programming concepts it'll get a lot easier. Keep asking and ill do my best!
David Clausen
11,403 Pointssorry friends.class the second time will say array not friends, i edited it and fixed that.
Unsubscribed User
3,309 PointsWe're about to be in vehement agreement here :) - you get a NoMethodError (as per preamble). I did a lot of p foo.class
and missed that it wasn't an array the second time. The thing is, my alternate code works. Technically, I don't need to make it p friends.class # => Friends
Excellent instruction, I might add. I never thought of Duck Typing as having gotchas. OK, so the take away is that you need to include Enumerable if your array is really an instance of a class that contains an array:
# First time
p friends.inspect # => "[\"Monica\", \"Chandler\", \"Joey\", \"Phoebe\"]"
# Second time
p friends.inspect # => "#<Friends:0x000000027ccf48 @members=[\"Chandler\", \"Joey\", \"Monica\", \"Phoebe\"]>"
But:
p Friends.ancestors # [Friends, Enumerable, Object, Kernel, BasicObject]
p Array.ancestors # [Array, Enumerable, Object, Kernel, BasicObject]
p Enumerable.instance_methods - Array.instance_methods # => []
(Going back to my preamble...) Great - since I have Array, why do I need Enumerable in general?
David Clausen
11,403 PointsDavid Clausen
11,403 PointsSorry this is probably a bit heavy. If you get confused near the end the links will one day make sense to you. For now the first half answers your question.