ruby - enumerable and enumerator

38
Ruby - Enumerable and Enumerator @farhan_faruque

Upload: farhan-faruque

Post on 15-Jul-2015

78 views

Category:

Technology


1 download

TRANSCRIPT

Ruby - Enumerable and Enumerator@farhan_faruque

Enumerable❏ Collection objects in Ruby typically include the Enumerable module.❏ Classes that use Enumerable enter into a kind of contract: the class has to

define an instance method called eachmix Enumerable into your own classes:

class C

include Enumerable

end

we must define an each instance method in our class:class C

include Enumerable

def each

# relevant code here

end

end

Enumerator Enumerators are objects that encapsulate knowledge ofhow to iterate through a particular collection

Gaining enumerability through each

Any class that aspires to be enumerable must have an each method whose job it is to yield items to a supplied code block, one at a time. each will vary from one class to another

class Rainbow

include Enumerable

def each

yield "red"

yield "orange"

yield "yellow"

end

end

r = Rainbow.newr.each do |color| puts "Next color: #{color}"end

Output:Next color:redNext color:orangeNext color:yellow

methods of Enumerable>> Enumerable.instance_methods(false).sort=> [:all?, :any?, :collect, :count, :cycle, :detect, :drop, :drop_while,:each_cons, :each_slice, :each_with_index, :each_with_object, :entries,:find, :find_all, :find_index, :first, :grep, :group_by, :include?,:inject, :map, :max, :max_by, :member?, :min, :min_by, :minmax, :minmax_by,:none?, :one?, :partition, :reduce, :reject, :reverse_each, select, :sort, :sort_by, :take, :take_while, :to_a, :zip] Each of these methods is built on top of each

Enumerable boolean queriesGiven an array states, containing the names of all the state in the Bangladesh.# Does the array include Dhaka?>> states.include?("Dhaka") => true# Do all states include a space?>> states.all? {|state| state =~ / / } => false# Does any state include a space? >> states.any? {|state| state =~ / / } => true# Is there one, and only one, state with "Dhaka" in its name?>> states.one? {|state| state =~ /Dhaka/ } => true# Are there no states with "East" in their names?>> states.none? {|state| state =~ /East/ } => true

Enumerable searching and selecting

❏ Getting the first match with find - find locates the first element in an array for which the code block, when

called with that element as an argument, returns true.>> [1,2,3,4,5,6,7,8,9,10].find {|n| n > 5 } => 6

if block returns anything with true then find stops iterating.a Proc object—as an argument to find, in which case that function will be called if the find operation fails.>> failure = lambda { 11 } => #<Proc:0x434810@(irb):6 (lambda)>>> over_ten = [1,2,3,4,5,6].find(failure) {|n| n > 10 } => 11

find_all and reject❏ find_all - returns a new array containing all the elements of

the original array that match the criteria in the code block.If no matching elements are found, find_all returns an empty array.

>> a = [1,2,3,4,5,6,7,8,9,10] => [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]>> a.find_all {|item| item > 5 } =>[6, 7, 8, 9, 10]

❏ reject - find out which elements of an array do not return a true value when yielded to the block>> a.reject {|item| item > 5 }=> [1, 2, 3, 4, 5]

reject! - makes the item removal permanent in the original object

“threequal” matches with grep❏ The Enumerable#grep method lets you select from an

enumerable object based on the case equality operator, ===.>> colors = %w{ red orange yellow blue indigo violet }=> ["red", "orange", "yellow", "blue", "indigo", "violet"]>> colors.grep(/o/)=> ["orange", "yellow", "indigo", "violet"]

enumerable.grep(expression) is functionally equivalent to this:enumerable.select {|element| expression === element }

The full grep syntax:enumerable.grep(expression) {|item| ... }

group_by and partition❏ A group_by operation on an enumerable object takes a block and returns

a hash. For each unique block return value, the result hash gets a key;>> colors = %w{ red orange yellow blue indigo violet }=> ["red", "orange", "yellow", "blue", "indigo", "violet"]>> colors.group_by {|color| color.size }=> {3=>["red"], 6=>["orange", "yellow", "indigo", "violet"], 4=>["blue"]}

❏ The partition method is similar to group_by - it splits the elements of the enumerable into two arrays based on whether the code block returns true for the element.

teens = people.partition {|person| person.teenager? }

Element-wise enumerable operations

❏ The first method>> [1,2,3,4].first => 1>>{1 => 2, "one" => "two"}.first =>[1, 2]

❏ The take and drop methods>> states = %w{ NJ NY CT MA VT FL } => ["NJ", "NY", "CT", "MA", "VT", "FL"]>> states.take(2) => ["NJ", "NY"] # Grabs first two elements>> states.drop(2) => ["CT", "MA", "VT", "FL"] #Grabs collection except first two elements

take and drop operations can be constrained by providing a block and using the variant forms take_while and drop_while

Element-wise enumerable operations❏ The min and max methods>> [1,3,5,4,2].max => 5>> %w{ Ruby C APL Perl Smalltalk }.min => "APL"

we can use min_by or max_by, which takes a code block and bases the selection on the results of the block:>> %w{ Ruby C APL Perl Smalltalk }.min_by {|lang| lang.size }=> "C"

minmax method , which gives a pair of values, one for the minimum and one for the maximum:>> %w{ Ruby C APL Perl Smalltalk }.minmax => ["APL", "Smalltalk"]

The relatives of each❏ The each_with_index method

Enumerable#each_with_index differs from each in that it yields an extra item each time through the collection

>> names = ["Dhaka", "Rangpur", "Rajshahi"]=> ["Dhaka", "Rangpur", "Rajshahi"]>> names.each_with_index do |pres, i|

puts "#{i+1}. #{pres}">> end

1.Dhaka2.Rangpur3.Rajshahi

The each_index method❏ Not every enumerable object has knowledge of what an index is.enumerables can perform an each_index operation>> %w{a b c }.each_index {|i| puts i }

012

The each_slice and each_cons methods

❏ The each_slice and each_cons methods are specializations of each that walk through a collection a certain number of elements at a time, yielding an array of that many elements to the block on each iteration.

>> array = [1,2,3,4,5,6,7,8,9,10]=> [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]>> array.each_slice(3) {|slice| p slice }

[1, 2, 3][4, 5, 6][7, 8, 9][10]

=> nil

The each_slice and each_cons methods>> array = [1,2,3,4,5,6]>> array.each_cons(3) {|cons| p cons }[1, 2, 3][2, 3, 4][3, 4, 5][4, 5, 6] => nil

each_slice handles each element only onceeach_cons takes a new grouping at each element and thus produces overlapping yielded arrays

The relatives of each❏ The cycle method

Enumerable#cycle yields all the elements in the object again and again in a loop.

use cycle to decide dynamically how many times want to iterate through a collection—essentially, how many each-like runs want to perform consecutively.

Enumerable reduction with injectThe inject method works by

- initializing an accumulator object - then iterating through a collection (an enumerable object)- performing a calculation on each iteration - resetting the accumulator, for purposes of the next iteration, to the result

of that calculation.

>> [1,2,3,4].inject(0) {|acc,n| acc + n }=> 10

accumulator is initialized to 0

The map methodThe map method is one of the most powerful and important enumerable or collection operations available in Ruby.

- map always returns an array- The returned array is always the same size as the original

enumerable>> names = %w{ David Yukihiro Chad Amy }=> ["David", "Yukihiro", "Chad", "Amy"]>> names.map {|name| name.upcase }=> ["DAVID", "YUKIHIRO", "CHAD", "AMY"]

The return value of mapThe return value of map , and the usefulness of that return value, is what distinguishes map from each . The return value of each doesn’t matter.each returns its receiver.map returns a new object: a mapping of the original object to a new object.

In-place mapping with map!>> names = %w{ David Yukihiro Chad Amy }>> names.map!(&:upcase)

The map! method of Array is defined in Array , not in Enumerable

Strings as quasi-enumerablesWe can iterate through the raw bytes or the characters of a string using convenient iterator methods that treat the string as a collection of bytes, characters, or lineseach_bytestr = "abc"str.each_byte {|b| p b }

each_charstr.each_char {|c| p c }

each_linestr = "This string\nhas three\nlines"str.each_line {|l| puts "Next line: #{l}" }

Sorting enumerablesContainer object, as an enumerable, has two sorting methods, sort and sort_by , which you can use to sort the collection.

Sorting Objects - need to define define the spaceship operator (<=>)class Painting …. def <=>(other_painting) self.price <=> other_painting.price endend>> sort = [pa1, pa2, pa3, pa4, pa5].sort

Sorting enumerablesComplete sort steps - 1 . Teach your objects how to compare themselves with each other, using <=> 2 . Put those objects inside an enumerable object (probably an array).3 . Ask that object to sort itself. It does this by asking the objects to compare themselves to each other with <=>.

Defining sort-order logic with a block

In cases where no <=> method is defined for these objects, we can supply a block

year_sort = [pa1, pa2, pa3, pa4, pa5].sort do |a,b|a.year <=> b.year

end

It can also come in handy when the objects being sorted are of different classes ->> ["2",1,5,"3",4,"6"].sort {|a,b| a.to_i <=> b.to_i }=> [1, "2", "3", 4, 5, "6" ]

Concise sorting with sort_bysort_by is an instance method of Enumerablesort_by always takes a block

>> ["2",1,5,"3",4,"6"].sort_by {|a| a.to_i }=> [1, "2", "3", 4, 5, "6"]

Enumerators and the next dimension of enumerability

Enumerators add a whole dimension of collection manipulation power to Ruby.

Enumerators are closely related to iterators, but they aren’t the same thing. An iterator is a method that yields one or more values to a code block. An enumerator is an object, not a method.

Creating enumerators with a code block

e = Enumerator.new do |y|y << 1y << 2y << 3

end

y is a yielder, an instance of Enumerator::Yielder , automatically passed to the block.

Attaching enumerators to other objectsnames = %w{ David Black Yukihiro Matsumoto }e = names.enum_for(:select)

Specifying :select as the argument means that we want to bind this enumerator to the select method of the names array.e.each {|n| n.include?('a') } #Output: ["David", "Black", "Matsumoto"]

Implicit creation of enumerators by blockless iterator calls

>> str.bytes=> #<Enumerator:0x3dbddc>

The enumerator we get is equivalent to what we would get if we did this:>> str.enum_for(:bytes)

Enumerator semantics and uses❏ How to use an enumerator’s each>> array = %w{ cat dog rabbit }=> ["cat", "dog", "rabbit"]>> e = array.map=> #<Enumerator:0x3aedc8>>> e.each {|animal| animal.capitalize }=> ["Cat", "Dog", "Rabbit"]

Protecting objects with enumeratorsa method that expects, say, an array as its argument.def give_me_an_array(array)

the method can alter that object.array << "new element"

The enumerator will happily allow for iterations through the array, but it won’t absorb changes.give_me_an_array(Enumerator.new(original_array))

Fine-grained iteration with enumeratorsEnumerators maintain state: they keep track of where they are in their enumeration.Code: OutPut: names = %w{ David Yukihiro } Davide = names.to_enum Yukihiroputs e.next Davidputs e.nexte.rewindputs e.next

An enumerator vs an iteratorAn enumerator is an object, and can therefore maintain state.

An iterator is a method. When you call it, the call is atomic; the entire call happens, and then it’s over.

Adding enumerability with an enumeratormodule Music

class Scale

NOTES = %w{ c c# d d# e f f# g a a# b }

def play

NOTES.each {|note| yield note }

end

end

end

enum = scale.enum_for(:play)

p enum.map {|note| note.upcase }

The enumerator, enum , has an each method; that method performs the same iteration that the scale’s play method performs.

Enumerator method chainingputs names.select {|n| n[0] < 'M' }.map(&:upcase).join(", ")

left-to-right, conveyor-belt style of processing data is powerful and, for the most part, straightforward.But it comes at a price: the creation of intermediate objects.

Eumerators don’t solve all the problems of method chaining. But they do mitigate the problem of creating intermediate objects in some cases

Indexing enumerables with with_indexnames = %w{ David Yukihiro Joe Jim}names.each_with_index do |name,i|

puts nameendend

('a'..'z').map.with_index {|letter,i| [letter,i] }