Why use enumerator in ruby - ruby

I stumbled upon such example:
file = "./path"
var = Enumerator.new do |y|
CSV.foreach(file) do |row|
y.yield(row)
end
end
Question is, why store data of any kind, inside enumerators, insted of arrays? what is the difference in behaviour between whats above and this:
file = "./path"
var = []
CSV.foreach(file) do |row|
var << row
end
when i want to something with the data it looks the same for both cases:
var.each {|row| puts row}
So what are the advantages and disadvantages of such constructions?

In general, iterators can be used to lazily generate a sequence of objects.
This is the main advantage when creating a lazy enumeration compared to creating a collection of items which is much more efficient.
For example, if your enumerator loop iterates over just the first 5 items of 3 million items then that's all yield returns, and you didn't build up a collection of 1 million items internally first.
So, you do not need to load all the 3 millions items just for your callee function can continue and executes the rest of the code.
Iterators are means for returning sequences.
Sometimes The sequence might even be infinite.
It brings the functional programming concept of lazy evaluation to Ruby – at least for enumerations.
There is a huge difference between returning a collection and returning a collection generator.

Too see the difference of the code add a puts within the loops.
seq = (1..3)
enum = Enumerator.new do |y|
seq.each do |i|
puts "Grabbing #{i} with enumerator"
y.yield(i)
end
end
enum.each { |i| puts "Taken #{i} from enumerator" }
# Grabbing 1 with enumerator
# Taken 1 from enumerator
# Grabbing 2 with enumerator
# Taken 2 from enumerator
# Grabbing 3 with enumerator
# Taken 3 from enumerator
array = []
seq.each do |i|
puts "Grabbing #{i} with array"
array << i
end
array.each { |i| puts "Taken #{i} from array" }
# Grabbing 1 with array
# Grabbing 2 with array
# Grabbing 3 with array
# Taken 1 from array
# Taken 2 from array
# Taken 3 from array
Like mentioned by Tal Avissar the Enumerator fetches the value when it's needed which is called lazy evaluation. This behavoir is a benefit in some situations.
# infinite number sequence
numbers = Enumerator.new do |y|
n = 0
loop { y.yield(n += 1) }
end
puts numbers.take(3).join(', ')
# 1, 2, 3
When dealing with huge or infinite sequences the difference is quite important.

Related

Is there a more elegant way of writing a while loop in Ruby where the array size is not known?

Using the following example:
array = [1,20]
new_array = []
i = array[0]
while i < array[1]
new_array.push(i)
i+= 2
end
#new_array = [1,3,5,7,9,11,13,15,17,19]
Is there a more elegant way to write this loop without have to write an empty array (new_array) and an external variable loop counter (i)? I was thinking something along the lines of new_array.map{|x| } but instead of iterating through each element, it continually adds a number until it hits a certain limit.
Assuming your goal is to create an array of odd numbers up to a limit, you can use a range with a step.
limit = 20
array = (1..limit).step(2).to_a
EDIT
If you want to be able to descend as well as ascend you can use step.
#Ascending
start = 1
limit = 20
array = start.step(limit, 2).to_a
#Descending
start = 20
limit = 1
array = start.step(limit, -2).to_a
For the sake of having an alternative, you could also select (Enumerable#select) odds numbers (Integer#odds?) out of your Range:
(1..20).select(&:odd?)
#=> [1, 3, 5, 7, 9, 11, 13, 15, 17, 19]
My answer is addressed to the question stated in the title, not to the particular example presenteed.
Suppose the user is asked to enter a sequence of strings, with an empty string signifying that the user is finished. These strings are to be saved in an array which is to be returned.
This is a typical way of writing the code using a while loop:
def gettem
arr = []
until (s = gets.chomp).empty? do
arr << s
end
arr
end
One could instead use Kernel#loop and the keyword break, which some (including me) prefer to while and until loops.
def gettem
arr = []
loop do
s = gets.chomp
break if s.empty?
arr << s
end
arr
end
A third way, suggested by #Aleksei Matiushkin in his answer here (which I had not seen before) is the following:
def gettem
loop.with_object([]) do |_,arr|
s = gets.chomp
break arr if s.empty?
arr << s
end
end
This uses the form of loop that returns an enumerator (see the doc). If I run this and enter "dog", "cat" and "\n", the return value is ["dog", "cat"], as desired.
This approach has three of advantages over the other approaches:
the variable arr is confined to the block, away from prying eyes;
fewer lines of code are needed; and
the return value can be chained, as illustrated below.
def gettem
loop.with_object([]) do |_,arr|
s = gets.chomp
break arr if s.empty?
arr << s
end.then { |arr| [arr.size, arr] }
end
When executing this method and entering "dog", "cat" and "\n", the array [2, ["dog", "cat"]] is returned.
I've used an underscore for the first block variable (which always has a value of nil) to signify that it is not used in the block calculation.

Return value of a for loop in Ruby

I understand that, in Ruby, a for loop is an expression and therefore has a value which can be assigned to a variable. For example
x = for i in 0..3 do
end
sets x to 0..3 - the range over which the loop iterates.
Is the return value of a for loop always the range as above, or can it differ depending on the body of the loop?
What is returned by the for loop is dependent on how the enumerator you are looping over is implemented. For example, let's implement our own enumerator.
chipmunks = Enumerator.new { |c|
c << "Alvin"
c << "Simon"
c << "Theodore"
"Dave"
}
Notice that the last thing in the Enumerator is the string "Dave". Now let's use this in a .each loop.
result = chipmunk.each do |chipmunk|
puts chipmunk
end
#=> Alvin
#=> Simon
#=> Theodore
puts result
#=> Dave
My guess is that Range is implemented in such a way that its enumerator returns itself. By doing so, it gives you the ability to chain methods.
(1..100).each { |n| something(n) }.each {|y| something_else(y) }

Values in the while loop do not modify outside values

I have a long code but I tried to copy and adapt my problem in as few lines as possible . I have a method which creates an array( 2D ) with 0 and 1
array1 = newValue(2) - the number 2 represents how many 1 the array has
array2 = newValue(3)
and this loop
(0..9).each do|i|
(0..9).each do|j|
while((array1[i][j] == array2[i][j]) && (array2[i][j] == 1)) do
array1 = newvalue(2)
array2 = newvalue(3)
end
end
end
I'm using the while loop so I won t have a 1 in the same position in both arrays . But what is inside the while loop doesn't modify the values of the array . I also tried using map!/collect! but I think I did something wrong because nothing happened. I hope you can understand what I was trying to do .
Edit:
def newValue(value)
value = value.to_i
array = Array.new(10) { Array.new(10 , 0) }
(a lot of conditions on how to position the items in the array)
return array
end
Here's my take... hopefully it'll help out. It seems that what you noticed was true. The arrays are not getting reset. Probably because inside the each blocks, the scope is lost. This is probably because the are arrays. I took a slightly different approach. Put everything in a class so you can have instance variables that you can control and you know where they are and that they are always the same.
I pulled out the compare_arrays function which just returns the coordinates of the match if there is one. If not it returns nil. Then, youre while loop is simplified in the reprocess method. If you found a match, reprocess until you don't have a match any more. I used a dummy newValue method that just returned another 2d array (as you suggested yours does). This seems to do the trick from what I can tell. Give it a whirl and see what you think. You can access the two arrays after all the processing with processor.array1 as you can see I did at the bottom.
# generate a random 2d array with 0's and val's
def generateRandomArray(val=1)
array = []
(0..9).each do |i|
(0..9).each do |j|
array[i] ||= []
array[i][j] = (rand > 0.1) ? 0 : val
end
end
array
end
array1 = generateRandomArray
array2 = generateRandomArray
def newValue(val)
generateRandomArray(val)
end
class Processor
attr_reader :array1, :array2
def initialize(array1, array2)
#array1 = array1
#array2 = array2
end
def compare_arrays
found = false
for ii in 0..9
break unless for jj in 0..9
if ((#array2[ii][jj] == 1) && (#array1[ii][jj] == 1))
found = true
break
end
end
end
[ii,jj] if found
end
def reprocess
while compare_arrays
puts "Reprocessing"
#array1 = newValue(2)
#array2 = newValue(3)
reprocess
end
end
end
processor = Processor.new(array1, array2)
processor.reprocess
puts processor.array1.inspect

Ruby grocery list program

I am currently learning Ruby and I'm trying to write a simple Ruby grocery_list method. Here are the instructions:
We want to write a program to help keep track of a grocery list. It takes a grocery item (like "eggs") as an argument, and returns the grocery list (that is, the item names with the quantities of each item). If you pass the same argument twice, it should increment the quantity.
def grocery_list(item)
array = []
quantity = 1
array.each {|x| quantity += x }
array << "#{quantity}" + " #{item}"
end
puts grocery_list("eggs", "eggs")
so I'm trying to figure out here how to return "2 eggs" by passing eggs twice
To help you count the different items you can use as Hash. A Hash is similar to an Array, but with Strings instead of Integers als an Index:
a = Array.new
a[0] = "this"
a[1] = "that"
h = Hash.new
h["sonja"] = "asecret"
h["brad"] = "beer"
In this example the Hash might be used for storing passwords for users. But for your
example you need a hash for counting. Calling grocery_list("eggs", "beer", "milk", "eggs")
should lead to the following commands being executed:
h = Hash.new(0) # empty hash {} created, 0 will be default value
h["eggs"] += 1 # h is now {"eggs"=>1}
h["beer"] += 1 # {"eggs"=>1, "beer"=>1}
h["milk"] += 1 # {"eggs"=>1, "beer"=>1, "milk"=>1}
h["eggs"] += 1 # {"eggs"=>2, "beer"=>1, "milk"=>1}
You can work through all the keys and values of a Hash with the each-loop:
h.each{|key, value| .... }
and build up the string we need as a result, adding
the number of items if needed, and the name of the item.
Inside the loop we always add a comma and a blank at the end.
This is not needed for the last element, so after the
loop is done we are left with
"2 eggs, beer, milk, "
To get rid of the last comma and blank we can use chop!, which "chops off"
one character at the end of a string:
output.chop!.chop!
One more thing is needed to get the complete implementation of your grocery_list:
you specified that the function should be called like so:
puts grocery_list("eggs", "beer", "milk","eggs")
So the grocery_list function does not know how many arguments it's getting. We can handle
this by specifying one argument with a star in front, then this argument will
be an array containing all the arguments:
def grocery_list(*items)
# items is an array
end
So here it is: I did your homework for you and implemented grocery_list.
I hope you actually go to the trouble of understanding the implementation,
and don't just copy-and-paste it.
def grocery_list(*items)
hash = Hash.new(0)
items.each {|x| hash[x] += 1}
output = ""
hash.each do |item,number|
if number > 1 then
output += "#{number} "
end
output += "#{item}, "
end
output.chop!.chop!
return output
end
puts grocery_list("eggs", "beer", "milk","eggs")
# output: 2 eggs, beer, milk
def grocery_list(*item)
item.group_by{|i| i}
end
p grocery_list("eggs", "eggs","meat")
#=> {"eggs"=>["eggs", "eggs"], "meat"=>["meat"]}
def grocery_list(*item)
item.group_by{|i| i}.flat_map{|k,v| [k,v.length]}
end
p grocery_list("eggs", "eggs","meat")
#=>["eggs", 2, "meat", 1]
def grocery_list(*item)
Hash[*item.group_by{|i| i}.flat_map{|k,v| [k,v.length]}]
end
grocery_list("eggs", "eggs","meat")
#=> {"eggs"=>2, "meat"=>1}
grocery_list("eggs", "eggs","meat","apple","apple","apple")
#=> {"eggs"=>2, "meat"=>1, "apple"=>3}
or as #Lee said:
def grocery_list(*item)
item.each_with_object(Hash.new(0)) {|a, h| h[a] += 1 }
end
grocery_list("eggs", "eggs","meat","apple","apple","apple")
#=> {"eggs"=>2, "meat"=>1, "apple"=>3}
Use a Hash Instead of an Array
When you want an easy want to count things, you can use a hash key to hold the name of the thing you want to count, and the value of that key is the quantity. For example:
#!/usr/bin/env ruby
class GroceryList
attr_reader :list
def initialize
# Specify hash with default quantity of zero.
#list = Hash.new(0)
end
# Increment the quantity of each item in the #list, using the name of the item
# as a hash key.
def add_to_list(*items)
items.each { |item| #list[item] += 1 }
#list
end
end
if $0 == __FILE__
groceries = GroceryList.new
groceries.add_to_list('eggs', 'eggs')
puts 'Grocery list correctly contains 2 eggs.' if groceries.list['eggs'] == 2
end
Here's a more verbose, but perhaps more readable solutions to your challenge.
def grocery_list(*items) # Notice the asterisk in front of items. It means "put all the arguments into an array called items"
my_grocery_hash = {} # Creates an empty hash
items.each do |item| # Loops over the argument array and passes each argument into the loop as item.
if my_grocery_hash[item].nil? # Returns true of the item is not a present key in the hash...
my_grocery_hash[item] = 1 # Adds the key and sets the value to 1.
else
my_grocery_hash[item] = my_grocery_hash[item] + 1 # Increments the value by one.
end
end
my_grocery_hash # Returns a hash object with the grocery name as the key and the number of occurences as the value.
end
This will create an empty hash (called dictionaries or maps in other languages) where each grocery is added as a key with the value set to one. In case the same grocery appears multiple times as a parameter to your method, the value is incremented.
If you want to create a text string and return that instead of the hash object and you can do like this after the iteration:
grocery_list_string = "" # Creates an empty string
my_grocery_hash.each do |key, value| # Loops over the hash object and passes two local variables into the loop with the current entry. Key being the name of the grocery and value being the amount.
grocery_list_string << "#{value} units of #{key}\n" # Appends the grocery_list_string. Uses string interpolation, so #{value} becomes 3 and #{key} becomes eggs. The remaining \n is a newline character.
end
return grocery_list_string # Explicitly declares the return value. You can ommit return.
Updated answer to comment:
If you use the first method without adding the hash iteration you will get a hash object back which can be used to look up the amount like this.
my_hash_with_grocery_count = grocery_list("Lemonade", "Milk", "Eggs", "Lemonade", "Lemonade")
my_hash_with_grocery_count["Milk"]
--> 1
my_hash_with_grocery_count["Lemonade"]
--> 3
Enumerable#each_with_object can be useful for things like this:
def list_to_hash(*items)
items.each_with_object(Hash.new(0)) { |item, list| list[item] += 1 }
end
def hash_to_grocery_list_string(hash)
hash.each_with_object([]) do |(item, number), result|
result << (number > 1 ? "#{number} #{item}" : item)
end.join(', ')
end
def grocery_list(*items)
hash_to_grocery_list_string(list_to_hash(*items))
end
p grocery_list('eggs', 'eggs', 'bread', 'milk', 'eggs')
# => "3 eggs, bread, milk"
It iterates an array or hash to enable building another object in a convenient way. The list_to_hash method uses it to build a hash from the items array (the splat operator converts the method arguments to an array); the hash is created so that each value is initialized to 0. The hash_to_grocery_list_string method uses it to build an array of strings that is joined to a comma-separated string.

Sort Array by Popularity and Time in Ruby

I am a Ruby Rails newbie.
Is there a way to know the popularity of elements in an Array over time?
For example lets say for the last 15 min..
The array has like ["abc", "ab", "abc", "a", "abc", "ab"........] being pushed into the array.. can we get "abc" and "ab" as the most popular ones.. just for the last 15 minutes?
If you take for an entire hour.. typical for the entire hour.."abcd" is the most popular.. it should return "abcd" as the most popular element in an array..
Is there a way to achieve this?
Create your own class which inherits from Array, or delegates all its functionality to an Array. For example:
class TimestampedArray
def initialize
#items = []
end
def <<(obj)
#items << [Time.now,obj]
end
# get all the items which were added in the last "seconds" seconds
# assumes that items are kept in order of add time
def all_from_last(seconds)
go_back_to = Time.now - seconds
result = []
#items.reverse_each do |(time,item)|
break if time < go_back_to
result.unshift(item)
end
result
end
end
If you have an old version of Ruby, which doesn't have reverse_each:
def all_from_last(seconds)
go_back_to = Time.now - seconds
result = []
(#items.length-1).downto(0) do |i|
time,item = #items[i]
break if time < go_back_to
result.unshift(item)
end
result
end
Then you need something to find the "most popular" item. I often use this utility function:
module Enumerable
def to_histogram
result = Hash.new(0)
each { |x| result[x] += 1 }
result
end
end
On which you could base:
module Enumerable
def most_popular
h = self.to_histogram
max_by { |x| h[x] }
end
end
So then you get:
timestamped_array.all_from_last(3600).most_popular # "most popular" in last 1 hour

Resources