Sort Array by Popularity and Time in Ruby - 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

Related

Why does this use of the Hash#each method work only when I remove the splat operator from the parameter?

I'm going through a problem on Ruby Monk, https://rubymonk.com/learning/books/1-ruby-primer/problems/155-restaurant#solution4804
Their solution is great; I like it and it's more compact than mine. Problem is for mine, I just don't understand why it only works when I remove the splat operator from the cost parameter orders. Even if I shouldn't be doing it this way, I'm struggling to figure out what's up. I know sometimes it's unnecessary to understand everything, and it's best to just move on.. but curious.
Here is mine:
class Restaurant
def initialize(menu)
#menu = menu
end
def cost(*orders)
total_cost = 0
orders.each do |item, number|
total_cost += #menu[item] * number
end
end
menu = {:rice => 3, :noodles => 2}
orders = {:rice => 1, :noodles => 1}
eat = Restaurant.new(menu)
puts eat.cost(orders)
Edit:
To include their suggested solution below
class Restaurant
def initialize(menu)
#menu = menu
end
def cost(*orders)
orders.inject(0) do |total_cost, order|
total_cost + order.keys.inject(0) {|cost, key| cost + #menu[key]*order[key] }
end
end
end
Edit:
To clear up and answer my own question in the comment
I tried these experiments and it shows inject "removing" the array brackets that splat "put on". Perhaps not the most proper way to think about it? It does help clear up my confusion.
order = { :rice => 1, :noodles => 1 }
menu = { :rice => 3, :noodles => 2 }
[order].inject(0) do |bla, blu|
p bla #=> 0
p blu #=> {:rice=>1, :noodles=>1}
p blu.keys #=> [:rice, :noodles]
end
When you write:
def cost(*orders)
end
then all the parameters passed to the cost method will be put into a single array named orders. These two are thus equivalent:
def cost(*orders)
p orders.class #=> Array
p orders #=> [1,2,3]
end
cost(1,2,3)
def cost(orders)
p orders.class #=> Array
p orders #=> [1,2,3]
end
cost( [1,2,3] ) # note the array literal brackets
In your case, when you remove the "splat" you are saying "set orders to reference whatever was passed in directly". In this case you're passing it a Hash, and when you iterate a hash you get key/value pairs for each entry. This is just what you want.
When you do have the splat, though, you're getting this:
def cost(*orders)
p orders.class #=> Array
p orders #=> [{:rice=>1, :noodles=>1}]
end
orders = {:rice=>1, :noodles=>1}
cost(orders)
So you're wrapping your hash in an array, and then iterating over the elements of the array. Thus, the first value passed to the block is the entire hash, and there is no second parameter.
def cost(*orders)
p orders.class #=> Array
p orders #=> [{:rice=>1, :noodles=>1}]
orders.each do |item,number|
p item #=> {:rice=>1, :noodles=>1}
p number #=> nil
end
end
orders = {:rice=>1, :noodles=>1}
cost(orders)
At this point you can't multiply anything by nil and so your code breaks.

Making my own sort method in Ruby

So I'm new to programming, and I'm working on Chris Pine's Learn to Program, which teaches Ruby. I'm on chapter 10 trying to make my own method for an array. I was at a total loss and tried modelling mine off his suggested answer. After fiddling around, I can't get an output. I run the program and it simply ends. I even tried using his code and it's giving me the same problem.
Here's what I have so far.
unsorted_array = ['gamma', 'delta', 'beta', 'alpha', 'zeta']
sorted_array = []
def sort some_array
recursive_sort(some_array, [])
end
def recursive_sort(unsorted_array, sorted_array)
if unsorted_array.length <= 0
return sorted_array
end
still_unsorted =[]
smallest = unsorted_array.pop
sorted_array = []
unsorted_array.each do |tested_obj|
if '#{tested_obj}' > smallest
sorted_array.push(smallest)
else
still_unsorted.push(smallest)
smallest = unsorted_array.pop
end
end
recursive_sort(still_unsorted, sorted_array)
end
puts sort(recursive_sort(unsorted_array, sorted_array))
Any advice would be appreciated.
Here are a few observations about your code:
since test_obj is a string, '#{tested_obj}' is the same as #{tested_obj}, which is the same as tested_obj.
declaring sorted_array = [] has no effect. Being a local variable, it is not within the scope of teh method recursive_sort. That method receives an array that it calls sorted_array, so you would not want it initialized anyway.
you don't need to create the new array, still_unsorted; simply transfer elements from unsorted_array to sorted_array.
Below I've fixed and tightened up your code.
def recursive_sort(unsorted_array, sorted_array = [])
return sorted_array unless unsorted_array.length > 0
smallest = unsorted_array.min
unsorted_array.each {|e| sorted_array << e if e == smallest}
unsorted_array.delete(smallest)
recursive_sort(unsorted_array, sorted_array)
end
unsorted_array = ['gamma', 'alpha', 'delta', 'beta', 'gamma', 'alpha', 'zeta']
p recursive_sort unsorted_array
# => ["alpha", "alpha", "beta", "delta", "gamma", "gamma", "zeta"]
Here's what's happening:
by giving the second argument of recursive_sort (sorted_value) a default value of [] (an empty array), there is no need for the method sort you had previously.
sorted_array is returned if sorting is finished (same as return sorted_array if unsorted_array.length == 0).
use Enumerable#min to find the smallest value of the unsorted items (smallest).
add each instance of smallest in unsorted_array to sorted_array.
delete all instances of smallest in unsorted_array.
call the same method again, to remove the next smallest unsorted item, etc.
Note
unsorted_array.each {|e| sorted_array << e if e == smallest}
could be expressed in many different ways. Here's one:
sorted_array += [smallest]*(unsorted_array.count {|e| e == smallest})
To see how this works, suppose smallest = 'alpha'. Then
unsorted_array.count {|e| e == 'alpha'} # => 2
so the above expression is:
sorted_array += ['alpha']*2
which is
sorted_array += ['alpha', 'alpha']
which adds two "alpha"'s to sorted_array.

How do I replace a for-loop in Ruby?

In Ruby it is bad style to use for-loops. This is commonly understood.
A style guide recommended to me:
(https://github.com/bbatsov/ruby-style-guide#source-code-layout)
says:
"Never use for, unless you know exactly why. Most of the time iterators should be used instead. for is implemented in terms of each (so you're adding a level of indirection), but with a twist - for doesn't introduce a new scope (unlike each) and variables defined in its block will be visible outside it."
The example given is:
arr = [1, 2, 3]
#bad
for elem in arr do
puts elem
end
# good
arr.each { |elem| puts elem }
I have researched and I can't find an explanation as to how to simulate a for loop that provides an iterating value I can pass to places or perform arithmetic on.
For example, with what would I replace:
for i in 0...size do
puts array1[i]
puts array2[size-1 - i]
puts i % 2
end
It's easy if it's one array, but I often need the current position for other purposes.
There's either a simple solution I'm missing, or situations where for is required. Additionally, I hear people talk about for as if it is never needed. What then is their solution to this?
Can it be improved? And what is the solution, if there is one? Thanks.
If you want to iterate over a collection and keep track of the index, use each_with_index:
fields = ["name", "age", "height"]
fields.each_with_index do |field,i|
puts "#{i}. #{field}" # 0. name, 1. age, 2. height
end
Your for i in 0...size example becomes:
array1.each_with_index do |item, i|
puts item
puts array2[size-1 - i]
puts i % 2
end
Don't forget you can do cool things like this too
fields = ["name", "age", "height"]
def output name, idx
puts "#{idx}. #{name}"
end
fields.each_with_index &method(:output)
Output
0. name
1. age
2. height
You can use this technique as a class or instance method too
class Printer
def self.output data
puts "raw: #{data}"
end
end
class Kanon < Printer
def initialize prefix
#prefix = prefix
end
def output data
puts "#{#prefix}: #{data}"
end
end
def print printer, data
# separating the block from `each` allows
# you to do interesting things
data.each &printer.method(:output)
end
example using class method
print Printer, ["a", "b", "c"]
# raw: a
# raw: b
# raw: c
example using instance method
kanon = Kanon.new "kanon prints pretty"
print kanon, ["a", "b", "c"]
# kanon prints pretty: a
# kanon prints pretty: b
# kanon prints pretty: c

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.

difficulty modifying two dimensional ruby array

Excuse the newbie question. I'm trying to create a two dimensional array in ruby, and initialise all its values to 1. My code is creating the two dimensional array just fine, but fails to modify any of its values.
Can anyone explain what I'm doing wrong?
def mda(width,height)
#make a two dimensional array
a = Array.new(width)
a.map! { Array.new(height) }
#init all its values to 1
a.each do |row|
row.each do |column|
column = 1
end
end
return a
end
It the line row.each do |column| the variable column is the copy of the value in row. You can't edit its value in such way. You must do:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.each do |row|
row.map!{1}
end
return a
end
Or better:
def mda(width,height)
a = Array.new(width)
a.map! { Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
a = Array.new(width){ Array.new(height) }
a.map do |row|
row.map!{1}
end
end
Or better:
def mda(width,height)
Array.new(width) { Array.new(height){1} }
end
each passes into the block parameter the value of each element, not the element itself, so column = 1 doesn't actually modify the array.
You can do this in one step, though - see the API docs for details on the various forms of Array#new. Try a = Array.new(width) {|i| Array.new(height) {|j| 1 } }
you can create it like this?
a=Array.new(width) { Array.new(height,1) }
column in your nested each loop is a copy of the value at that place in the array, not a pointer/reference to it, so when you change its value you're only changing the value of the copy (which ceases to exist outside the block).
If you just want a two-dimensional array populated with 1s something as simple as this will work:
def mda(width,height)
[ [1] * width ] * height
end
Pretty simple.
By the way, if you want to know how to modify the elements of a two-dimensional array as you're iterating over it, here's one way (starting from line 6 in your code):
#init all its values to 1
a.length.times do |i|
a[i].length.times do |j|
a[i][j] = 1
end
end

Resources