Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Cover image for Ruby Enumerator : the what, the why, the how
David Boureau
David Boureau

Posted on • Edited on • Originally published atalsohelp.com

     

Ruby Enumerator : the what, the why, the how

Article originally published here :https://alsohelp.com/blog/ruby-enumerator-what-why-how/

What are Enumerators ?

This section will give you a general understanding of a few important terms and their distinctions. Before we start using them, we need to understand the concept of Enumeration and the tools Ruby gives us for Enumeration. Also, we will be assuming that you are comfortable with blocks in Ruby.

Enumeration, TheEnumerable Module andEnumerator

Enumeration: As a concept simply means to traverse a list of items according to some logic. In programming, we often come across lists and the need to traverse theselists is a common programming necessity.

Enumerable: When using lists we commonly use either thefor loop or the.each method to iterate over the items in them. We know thefor loop is a form of flow-control but where does this.each method come from ? You might think it comes from whicheverClass the list belongs to but in fact, it is an inherited method from theEnumerable module.

This module, when included inside aClass that contains a set of elements, allows anyClass to inherit a number of methods that one would need to traverse that set of elements.
There are many built-in enumerables in Ruby such asArray,Hash, andRange and all of them receive their enumeration capabilities by using theEnumerable module.

This module relies on a method called.each, which needs to be implemented in anyClass it's included in. Other important methods like.map,.reduce and.select that rely on the implementation of the.each method to function can then be used for free.

When called with a block on an array, the.each method will execute the block for each of the array's elements:

nums=[1,2,3]# An Array is an Enumerablenums.each{|i|puts"*#{i}"}# => 1# => 2# => 3
Enter fullscreen modeExit fullscreen mode

Enumerator: A class that is instantiated by either defining aEnumerator.new or by calling an instance method of anEnumerable object. So if we call the.each method on an array without passing a block to execute for each of its elements, we'll receive an instance ofEnumerator. Sounds a bit confusing I know. Lets simplify with a basic example:

nums=[1,2,3]putsnums.each# => <Enumerator: 0x00007fa3657f90f8>
Enter fullscreen modeExit fullscreen mode

Why it's nice

We previously discussed how thefor loop can also be used to iterate over a list. So why do we need to use the methods provided by theEnumerable module? Well, you see when we use afor loop we risk introducing a bug into our code by inadvertently overwriting a previously declared variable's value.

Sample below:

fighter='Jackie'fighters_list=['Bruce','Rocky','Rambo']forfighterinfighters_listputsfighterendputs"Your Fighter has changed => "+fighter# Unintended change
Enter fullscreen modeExit fullscreen mode

Console output:

=> Bruce=> Rocky=> Rambo=> Your Fighter has changed=>  Rambo
Enter fullscreen modeExit fullscreen mode

Clearly, this is not desirable. This is whereEnumerable#each can be pretty handy. If we were to use the following code, the value of the"fighter" variable would remain unchanged.

fighters_list.each{|fighter|putsfighter}
Enter fullscreen modeExit fullscreen mode

This is why it is almost always better to use the.each method provided by an Enumerable to iterate over it. There are also many other common things we wish to do with lists such as reduce a list down to a single value like its sum, modify the whole list, and write our own custom logic to iterate over a list if we want to protect some data on the client-side. We can do all of that and more if we use anEnumerable. We will discuss usage in detail in just a bit.

How do I use enumerables ?

So how do we end up using Enumerable in our code? You have a lot of freedom in this and there are plenty of places where usingenumerables is useful.

Chained Enumeration

You can use the .each method to traverse over the list, but what if we wanted to modify the list and our mapping logic uses the index of each element. TheEnumerable#map method seems like a good first thought. However, this can modify the list but cannot track indexes of individual elements.

nums_enum=[1,2,3].mapnums_enum.each{|num|putsnum}# => 1# => 2# => 3
Enter fullscreen modeExit fullscreen mode

We also have theEnumerable#each_with_index method. This won't modify the list but we can at least track indexes with it. You can already see where I am going with this. Yes indeed with the help ofEnumerators we can chain these two together to create our very own "Map with Index"-like function call ! Sample below:

nums_enum=[1,2,3].map# Called without a block so returns #Enumerator# => #<Enumerator: ...>pnums_enum.each_with_index{|n,i|n*i}# Called with block so will iterate with index# => [0, 2, 6]p[1,2,3].map.each_with_index{|n,i|n*i}# Shortened to a single line# => [0, 2, 6]
Enter fullscreen modeExit fullscreen mode

You can play around with this and chain a large number of methods as long as it serves your goals.

10.times.reverse_each.cycle.first(11)=>[9,8,7,6,5,4,3,2,1,0,9]
Enter fullscreen modeExit fullscreen mode

Manual Iteration

In some cases, you may want to have manual control over your iterations. The Enumerable module provides the.next method and you can call this on an Enumerator as well:

nums_enum=[1,2].eachnums_enum.next# => 1nums_enum.next# => 2nums_enum.next# => `next': iteration reached an end (StopIteration)
Enter fullscreen modeExit fullscreen mode

Custom Classes and Iterations

The beauty of Enumeration in Ruby is the amount of customization it provides. What if you wanted to half custom traversal logic for some list orClass.

1. Enumerator

digits=(1..10).to_a# Range to Arraydefodd_digits(digits)index=-1Enumerator.newdo|yielder|loopdoindex+=1yielder<<digits[index]ifdigits[index]%2==0endendend
Enter fullscreen modeExit fullscreen mode

Console:

$> puts odd_digits(digits).first(4)=> 2=> 4=> 6=> 8
Enter fullscreen modeExit fullscreen mode

2.Enumerable Implementation

By far the coolest and most detailed implementation use case is when you have a custom class that contains a set of elements. As discussed previously we will need to add two things to theClass to grant it the powers of Enumeration. Firstly we need to include theEnumerable module. Secondly, we need to implement theeach method in the class to iterate over the elements. Most cases allow falling back to theeach method of another object such as an array.

Let' implement a linked listClass with a custom structure so that it cannot rely on theArray#each method to iterate over the nodes.

classLinkedListdefinitialize(head,tail=nil)@head,@tail=head,tailenddefadd(item)LinkedList.new(item,self)endendLinkedList.new(0).add(5).add(10)# => <LinkedList:0x1 @head=10, @tail=<LinkedList:0x2 @head=5, @tail=<LinkedList:0x3 @head=0, @tail=nil>>>
Enter fullscreen modeExit fullscreen mode

We have created our linked list but have no way of iterating over the individual nodes/elements. To do this, we finally visit the last and possibly most important piece of code.

classLinkedListincludeEnumerable# We inherit enumerable methods that use .eachdefinitialize(head,tail=nil)@head,@tail=head,tailenddefadd(item)LinkedList.new(item,self)enddefeach(&block)# Implement our custom eachifblock_given?block.call(@head)@tail.each(&block)if@tailelseto_enum(:each)# Return enumerator if block not providedendendend
Enter fullscreen modeExit fullscreen mode

So what does this code do? The inclusion ofEnumerable is self-explanatory by this point. More importantly, we implement the.each method to let other enumerable methods know how to iterate our linked list.

In the.each method if a block is given we simply call the block on the current node and recursively call.each on the rest of the list until the final node isnil. If a block was not provided then return anEnumerator that uses our.each method. This last bit will allow the chaining of methods discussed in an earlier use case.

Console:

$> linked_list= LinkedList.new(1).add(5).add(10)$> linked_list.each{ |node| puts node}=> 0=> 5=> 10$> linked_list.select{ |node| node % 5== 0}# Select if divisible by 5=>[10, 5]
Enter fullscreen modeExit fullscreen mode

Congratulations! Our linked list implementation now has access to other useful methods like.map and.select as shown in the example above.

Hopefully, all of this made sense. In case some things are still hazy, then try practicing by creating your own enumerators and using them in your code.

Thanks for reading. That’s a wrap!

Top comments(0)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Rails & JS dev. Build products.
  • Location
    Nantes, France.
  • Joined

More fromDavid Boureau

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp