Enumeration, by definition, is “the act of mentioning a certain amount of something one by one.” In programming, instead of mentioning, we choose any action that we want to perform, be it simple display on a monitor or performing some kind of sampling and / or conversion on an element.
In programming, we have many ways to sample and process a collection per unit of time, by adding an additional transformation function to the chain at each step. And each step can either consume the entire collection before passing the processing results to the next step, or it can handle the collection “lazily” by passing one or more elements of the collection through all the transformation steps.
How Ruby Enums Work
In this post, I will give you a brief overview of what blocks (
block ) and
yield do .
The blocks we are interested in in Ruby are pieces of code defined inside methods or
proc /
lambda . You can think of
yield as the current block of code where another
block of code is inserted from somewhere else. Let me demonstrate.
def my_printer puts "Hello World!" end def thrice 3.times do yield end end thrice &method(:my_printer)
Methods take two forms of blocks for the
yield command: proc's or blocks. The
method method transforms the
method definition into
proc , which can then be passed inward as a
block , as in the example
my_printer above.
')
Above at the place where the
yield command is written, it is the same as if the code transmitted as a block would be at the
yield point. So in the first case, imagine calling
yield , replaced with
puts “Hello World!” And the second
yield replaced with
puts “Ruby” .
yield can also work as a simple enumerator. You can pass any value inwards as a parameter to
block /
proc by adding them after
yield .
def simple_enum yield 4 yield 3 yield 2 yield 1 yield 0 end simple_enum do |value| puts value end
Minimum enumerator requirements
The standard way to create an enumerator in Ruby is -
each , yields / yildit values. With this in mind, you can declare the
each method on any Ruby object and get all the benefits of more than 50 methods for processing and executing collections from the
Enumerable module. Just add
include Enumerable inside the object that has the valid
each method, and you can fully use all these methods (meaning the methods of the Enumerable module).
The enumerators are not limited to simple
Array collections
(arrays) , they can work with any collections that have the
each method declared in them (and will usually have an Enumerable module in their “grandparents / ancestors”).
Array.ancestors
"Lazy" and not "lazy" enumerators
Lazy enumerators are usually regarded as the best way to handle collections, because they allow you to go around step by step endless sequence, as far as you need.
Imagine an assembly line where people collect pizza, where each person is responsible only for one step in cooking / transforming pizza. The first person throws the dough into the correct shape, the next one adds the sauce, the next cheese, according to the person for each additive (sausages, peppers, tomatoes), another one puts everything in the oven, and the last person delivers the finished pizza to you. In this example, the “lazy” version of the assembly in Ruby is to have any number of orders for pizza, but everyone else will wait until the first pizza passes through all the processing steps / steps before continuing to make the next pizza.
If you are not using a “lazy” enumerator, then each step / stage could wait until it has completed one step per unit of time throughout the collection. For example, if you have 20 pizza orders, the person who throws the dough will have to make them 20 before someone can add the sauce to the next person on the line. And each step in the queue waits in a similar manner. Now, the more collection you need to process, the more ridiculous it seems to keep the rest of the assembly line waiting.
A more vital example: processing a list of letters that you need to send to all users. If the error in the code and the entire list is not lazily processed, then it is likely that no one will receive emails. In the case of "lazy" performance, potentially, you would send letters to most users before, say, an incorrect postal address would cause a problem / error. If the submission record contains the submission success status, it is easier to track which record (where) the error occurred.
Creating a “lazy” enumerator in Ruby is as simple as calling
lazy on an object with the
Enumerable module included in it, or calling
to_enum.lazy on an object, with the
each method declared inside it.
class Thing def each yield "winning" yield "not winning" end end a = Thing.new.to_enum.lazy Thing.include Enumerable b = Thing.new.lazy a.next
Calling to_enum returns an object that is both an Enumerator and an Enumerable , and which will have access to all of their methods.
It is important to pay attention to which methods of the enumerator will “consume” the entire collection at a time, and which will “consume” (carry out) it - “lazily”. For example, the
partition method consumes the entire collection at once, so that it is not acceptable for infinite collections. The best choice for lazy execution might be methods like
chunk or
select .
x = (0..Flot::INFINITY) y = x.chunk(&:even?)
In the case of using
select with infinite collections, you must first call the
lazy method from preventing the entire collection from being consumed by the
select method, and force the program to terminate because of its infinite execution.
Creating a “lazy” enumerator
Ruby has an
Enumerator :: Lazy class that allows you to write your own enumerator methods as
take to Ruby.
(0..Float::INFINITY).take(2)
For a visual example, we are implementing FizzBuzz, which can be run on any number and which will allow you to get endless FizzBuzz results.
def divisible_by?(num) ->input{ (input % num).zero? } end def fizzbuzz_from(value) Enumerator::Lazy.new(value..Float::INFINITY) do |yielder, val| yielder << case val when divisible_by?(15) "FizzBuzz" when divisible_by?(3) "Fizz" when divisible_by?(5) "Buzz" else val end end end x = fizzbuzz_from(7)
With the help of
Enumerator :: Lazy , no matter what you submit to
yielder - there will be a value returned at each step in the sequence. The enumerator keeps track of current progress when you use
next . But when you call
each after several
next calls, it will start from the very beginning of the collection.
The parameter that you pass to
Enumerator :: Lazy.new is the collection through which we will go through the enumerator. If you wrote this method for an
Enumerable or a compatible object, you can simply pass
self as a parameter.
val will be the only value produced per unit of time by the collection method
each , and
yielder will be the only entry point for any block of code you want to pass, as if it were with
each .
Advanced use of enumerator
When processing a collection of data, it is recommended to set restrictive filters first, and then the code processing of your data will take much less time. If you get data to process from a database, set restrictive filters in the internal database language, if possible, before transferring the data further to Ruby. So it will be much more effective.
require "prime" x = (0..34).lazy.select(&Prime.method(:prime?)) x.next
After the
select method above, you can add other methods to the data processing. These methods will deal only with a limited set of data inside primes, not all primes.
Grouping
One great way to process data for dividing it into columns is to use
group_by to convert the result into an associative array of groups. After that, just pull the results as if you were interested in all the results.
[0,1,2,3,4,5,6,7,8].group_by.with_index {|_,index| index % 3 }.values
If you display the results on a web page, the data will be arranged in the following order:
0 3 6 1 4 7 2 5 8
Calling
group_by above passes both the value and the index to the inside of the block. We use the underscore for the value from the array to indicate that we are not interested in this value, but only in the index. What we get as a result is an associative array with keys 0, 1, and 2 indicating each group of values that we have grouped. Since we do not have to worry about keys, we call
values on this associative array to get an array of arrays and further map how we should.
If we wanted to arrange the results from left to right in the form of columns, we could do the following:
threes = (0..2).cycle [0,1,2,3,4,5,6,7,8].slice_when { threes.next == 2 }.to_a
The
threes enumerator simply runs infinitely from 0 to 2, in a “lazy” manner. As a result, we get the following conclusion:
0 1 2 3 4 5 6 7 8
Ruby also has a
transpose method that reverses the results from one view to another.
x = [[0, 1, 2], [3, 4, 5], [6, 7, 8]] x = x.transpose
"Folding"
Let's look at how to build collections into the result. In other languages, this is usually done through the
fold method. In Ruby, this has long been done with
reduce and
inject . A more recent solution, and the preferred way to do this is with
each_with_object . The main idea is the processing of one collection to another, acting as a result.
Summing integers is as simple as:
[1,2,3].reduce(:+)
each_with_object usually requires an object that can be updated. You cannot change an integer object from itself, which is why for this trivial example we created an AddStore object.
These methods can be better demonstrated in the work, if you take data from one collection and put them in another collection. Notice that
inject and
reduce are the same method aliases in Ruby and must return such a value at the end of the block in order to continue to build an enumerator based on it.
each_with_object does not need the last piece of the block to return an element on which it will continue to build the enumerator.
collection = [:a, 2, :p, :p, 6, 7, :l, :e] collection.reduce("") { |memo, value| memo << value.to_s if value.is_a? Symbol memo
Structures
Object structures in Ruby are also enumerated objects, which you can use to create convenient objects to describe methods in them.
class Pair < Struct.new(:first, :second) def same?; inject(:eql?) end def add; inject(:+) end def subtract; inject(:-) end def multiply; inject(:*) end def divide; inject(:/) end def swap! members.zip(entries.reverse) {|a,b| self[a] = b} end end x = Pair.new(23, 42) x.same?
Structures are usually not used for large collections, but are used more as useful data objects, as a way to organize data together, which in turn encourages transparent use of data rather than overgrowth of data.
Data bins are when two or more variables are always used in a group, but they would not make any sense if they were used separately. This group of variables must be grouped into an object / class.
So, structures in Ruby are usually small collections of data, but at the same time, nothing suggests that the data itself could represent a completely different collection of data. In this case, the structure can be a way to implement transformations on the same data collections, which most likely you would do the same by writing your own class.
Summarize
Ruby is a pretty wonderful language that allows you to work as well as manipulate collections of data. Studying every bit of what Ruby offers will allow you to write more elegant code, as well as test and optimize code for better performance.
If performance is important, then measure the performance of individual implementations and be sure to set the filters and limits / limits on the processing as early as possible, of course, if possible. Consider limiting your input to small chunks using the
readline method on files rather than
read or
readlines or use the
LIMIT number in SQL.
Lazy iteration can be of great help in separating tasks into different threads or background processing tasks. The concept of "lazy" iterations does not really have flaws, so you can choose them for consumption of any collection in any place. It offers flexibility, and some languages, like Rust with iterators, have been taken for the standard to be “implemented lazily”.
The possibilities are endless when we are faced with how to manipulate and manipulate data. And this is a fun process, examine and create every way of manipulating data sets in programming. Ruby has well documented examples for each of its
enumerable methods, so it helps to learn from them. I support you to experiment and discover many new things that will help make the programming process more enjoyable and enjoyable.