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 trial

Ruby Ruby Foundations Ruby Core Enumerable

Can 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
David Clausen
11,403 Points

Well 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/

David Clausen
David Clausen
11,403 Points

Sorry 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.

It 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.

  1. Generate the data and store it, deferring enumeration
  2. 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
David Clausen
11,403 Points

Yah 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.

I 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
David Clausen
11,403 Points

I 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?

Yes, 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
David Clausen
11,403 Points

Cause 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
David Clausen
11,403 Points

sorry friends.class the second time will say array not friends, i edited it and fixed that.

We'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?