Hash/Ruby - Select last 4 values (to average) in hash - ruby-on-rails

I have a hash, that I select all the data for a dashboard to display performance, since displaying the latest value isn't always helpful, I'm trying to select the last 4 values from a hash.
I have attempted the thing.last(4), but to no avail.
Code is below, essentially trying to display the last 4 from top_points, or average points.
Note: Ruby 1.9
metric.sort.each do |key, value|
top_point = { x: Time.parse(key).to_time.to_i, y: value['top_10'] }
top_points << top_point
average_point = { x: Time.parse(key).to_time.to_i, y: value['average'] }
average_points << average_point
end

The following uses Hash#select to avoid the need to convert the hash to an array, manipulate the array and then convert it back to a hash.
h = { "b"=>1, "d"=>6, "f"=>3, "e"=>1, "c"=>3, "a"=>7 }
sz = h.size
#=> 6
h.select { (sz -= 1) < 4 }
#=> {"f"=>3, "e"=>1, "c"=>3, "a"=>7}
Alternatively, if using Ruby 2.5+ one could use Hash#slice:
h.slice(*h.keys[2..-1])
#=> {"f"=>3, "e"=>1, "c"=>3, "a"=>7}
and if using Ruby 2.6+ one could employ an Endless range:
h.slice(*h.keys[2..])
#=> {"f"=>3, "e"=>1, "c"=>3, "a"=>7}

in order to get the last four elements of your hash, you should first map it as an array, get the indexes desired and then transform again the array into an hash.
For example:
2.2.1 :001 > hash = {a: 1, b: 2, c: 3, d: 4, e: 5}
=> {:a=>1, :b=>2, :c=>3, :d=>4, :e=>5}
2.2.1 :002 > hash.map{|h| h}[-4..-1].to_h
=> {:b=>2, :c=>3, :d=>4, :e=>5}
In your specific case, the code might look like this:
metric.sort.map{|h| h}[-4..-1].to_h.each do |key, value|
top_point = { x: Time.parse(key).to_time.to_i, y: value['top_10'] }
top_points << top_point
average_point = { x: Time.parse(key).to_time.to_i, y: value['average'] }
average_points << average_point
end
Another way to write it could be:
last_four_metrics = metric.sort.map{|h| h}[-4..-1].to_h
top_points = last_four_metrics.map{|k, v| { x: Time.parse(k).to_time.to_i, y: v['top_10'] }}
average_points = last_four_metrics.map{|k, v| { x: Time.parse(k).to_time.to_i, y: v['average'] }}
Update: compatibility with Ruby 1.9
last_four_metrics = Hash[ metric.sort.map{|h| h}[-4..-1] ]
top_points = last_four_metrics.map{|k, v| { x: Time.parse(k).to_time.to_i, y: v['top_10'] }}
average_points = last_four_metrics.map{|k, v| { x: Time.parse(k).to_time.to_i, y: v['average'] }}

metrics.sort.last(4).to_h
Will give you a hash with the last four elements.
Assuming you didn't originally want to sort, use the same idea:
metrics.to_a.last(4).to_h
Update: Given you added the 1.9 restriction and Array#to_h comes from 2.1 onward, you can replace x.to_h with Hash[x].
Or if you don't need the hash and want to iterate over the key/value pairs, omitting the .to_h part and continuing with .each do |key, value| will pretty much do the same.

You can convert the hash to a 2-element array, select the last for elements and convert back to hash:
top_points = {}
(1..10).each { |i| top_points[i] = i*2 }
# => top_points == {1=>2, 2=>4, 3=>6, 4=>8, 5=>10, 6=>12, 7=>14, 8=>16, 9=>18, 10=>20}
Hash[top_points.to_a[-4..-1]]
# => {7=>14, 8=>16, 9=>18, 10=>20}
You need to use ruby 1.9+ for this to work (since this version it keeps hash keys in the given order).

Related

How to replace missing key/value with zero using slice

I have a hash. I need to extract some key/value pairs, but some desired keys are missing.
How can I replace the missing pairs with "key" => 0.0 when I call attributes.slice on the record and keys as follows:
record = Model.last
record.attributes.slice('k1','k2','k3','k4','k5') # this returns
=> {"k1"=> 343, k3=> 0.0}
If some keys are missing then they won't appear in the result. How can I get the remaining missing keys assigned with 0.0?
Suppose
h = { 'k2'=>2, 'k1'=>1 }
and
all_keys = ['k1', 'k2', 'k3', 'k4']
then
all_keys.map { |k| h.fetch(k,0.0) }
#=> [1, 2, 0.0, 0.0]
See Hash#fetch.
We can take advantage of the fact that a hash can be overwritten with new key/value pairs:
{a: 0, b: 0}.merge(a: 2) # => {:a=>2, :b=>0}
Knowing that, we can do something like this:
desired_keys = [:a, :b]
foo = {a: 1}
desired_keys.zip([0] * desired_keys.size).to_h.merge(foo.slice(*desired_keys))
# => {:a=>1, :b=>0}
desired_keys is a predefined list of the key/value pairs we want, foo is the actual hash the real values are coming from.
[0] * 2 # => [0, 0] creates an array of a given size.
desired_keys.zip([0] * desired_keys.size).to_h creates a temporary hash of the values being used as filler.
merge(foo.slice(*desired_keys)) grabs the pairs we wanted. In this situation, * AKA "splat" explodes the array into its individual elements, so they're passed as separate parameters to slice. Here's what's happening:
def bar(*a)
a
end
bar(%w[a b]) # => [["a", "b"]]
bar(*%w[a b]) # => ["a", "b"]
Notice that the first call is passing in an array, whereas the second passes separate values.
Breaking it down a little to make it a bit more apparent:
desired_keys.zip([0] * desired_keys.size).to_h # => {:a=>0, :b=>0}
.merge(foo.slice(*desired_keys)) # => {:a=>1, :b=>0}
Because we know the record fields we're retrieving, it's easy to create that temporary hash once, in advance, then reuse it every time, resulting in very fast code:
DESIRED_KEYS = [:a, :b]
ZERO_HASH = DESIRED_KEYS.zip([0] * DESIRED_KEYS.size).to_h # => {:a=>0, :b=>0}
foo = {a: 1}
ZERO_HASH.merge(foo.slice(*DESIRED_KEYS))
# => {:a=>1, :b=>0}
All the methods, including * are part of Array or Hash.
How to use 0.0 instead of 0 is left as an exercise for the reader.
I think this should work in your case
Model.slice('k1','k2','k3','k4','k5').transform_values! { |v| v ? v : 0.0 }

Using .map function to create hashes

I have an array [5,2,6,4] and I would like to create a structure such as the first minus the second etc until the last row.
I have tried using map, but not sure how to proceed since i might need indxes.
I would like to store the result in something that looks like:
{1 => (5, 2, 3), 2 =>(2,6,-4), 3 => (6,4,2)}
So an array of x should return x-1 hashes.
Anybody knows how to do? should be a simple one.
Thank you.
First, you want to work with the array elements in pairs: 5,2, 2,6, ... That means you want to use each_cons:
a.each_cons(2) { |(e1, e2)| ... }
Then you'll want the index to get the 1, 2, ... hash keys; that suggests throwing a Enumerator#with_index into the mix:
a.each_cons(2).with_index { |(e1, e2), i| ... }
Then you can use with_object to get the final piece (the hash) into play:
a.each_cons(2).with_index.with_object({}) { |((e1, e2), i), h| h[i + 1] = [e1, e2, e1 - e2] }
If you think all the parentheses in the block's arguments are too noisy then you can do it in steps rather than a single one-liner.
You can use each_index:
a = [5, 2, 6, 4]
h = {}
a[0..-2].each_index { |i| h[i+1] = [a[i], a[i+1], a[i] - a[i+1]] }
h
=> {1=>[5, 2, 3], 2=>[2, 6, -4], 3=>[6, 4, 2]}
Try to use
each_with_index
Suppose you have an array:
arr = [3,[2,3],4,5]
And you want to covert with hash(key-value pair). 'Key' denotes an index of an array and 'value' denotes value of an array. Take a blank hash and iterate with each_with_index and pushed into the hash and finally print the hash.
Try this:
hash={}
arr.each_with_index do |val, index|
hash[index]=val
end
p hash
Its output will be:
{0=>3, 1=>[2, 3], 2=>4, 3=>5}
If you want that index always starts with 1 or 2 etc then use
arr.each.with_index(1) do |val, index|
hash[index] = val
end
Output will be:
{1=>3, 2=>[2, 3], 3=>4, 4=>5}

Find key that contains certain characters in a hashtable with Ruby

I have a hash that's something like this:
hash = { "key1-one" => 3, "key1-two" => 6, "key2-one" => 5, "key2-two" => 9 }
Now I want to find values with keys starting with key1, regardless of what follows. I've tried has_key? but it doesn't seem to work. I know I can use regex but is there already a built-in method for Ruby?
hash.select{ |key, _| key.start_with?("key1") }.values
I believe your hash would be look like this:
hash = { "key1-one"=>3, :"key1-two"=>6, "key2-one"=>5, "key2-two"=> 9 }
And try this:
hash.select { |k, _v| k.to_s.include? "key1" }.values
There's no need to create a new hash to extract the desired values:
hash.values_at *hash.keys.select { |k| k.start_with? 'key1' }
#=> [3, 6]
The could of course use a regex in the block instead:
{ |k| k =~ /^key1/ }

Merge two arrays into a Hash

My desired outcome is something like this:
{date: 12/02/2014, minutes: 36}
I'm scraping with Nokogiri using:
dates = doc.css('td:nth-child(3)')
minutes = doc.css('td:nth-child(10)')
Then I do some filtering and pushing results into arrays:
dates.each do |x|
if x.text.length == 10
date_array << x.text
end
end
minutes.each do |x|
minutes_array << x.text
end
How can I zip these two arrays together to create my desired outcome?
i've tried something like this, but it's not quite right (gives me {"2013-10-29"=>"32:14"} )
result = Hash[date_array.zip(minutes_array)]
or even something like this:
result = Hash[date_array.zip(minutes_array).map {|d, m| {:date => d, :minutes => m}}
but i get this error: wrong element type Hash at 163
i've also tinkered with .flatten but to no avail. Can anybody help?
assuming you have 2 equal length arrays x and y
x = [:key1, :key2, :key3]
y = [:value1, :value2, :value3]
z = {}
x.each_with_index { |key,index| z[key] = y[index] }
puts z
=> {:key1=>:value1, :key2=>:value2, :key3=>:value3}
is that what you are looking for?
then maybe this:
x = [:key1, :key2, :key3]
y = [:value1, :value2, :value3]
z = []
x.each_with_index { |key,index| z << { date: key, minutes: y[index]} }
puts z
{:date=>:key1, :minutes=>:value1}
{:date=>:key2, :minutes=>:value2}
{:date=>:key3, :minutes=>:value3}
Stealing from nPn (I can't comment on his answer because I've got no reputation )
Assuming you have
x = [ "date1", "date2", "date3"]
y = [ "time1", "time2", "time3"]
Then you can do:
z = []
x.each_with_index { |k, i| z << { date: k, time: y[i] } }
puts z
=> [ { date: "date1", time: "time1" },
{ date: "date2", time: "time2" },
{ date: "date3", time: "time3" } ]
Is this what you are looking for ?
You are trying to have the same key (date, minutes) for multiple values. You can instead have an array of hash for all those date-minute combos though, with this -
date.zip(minutes).reduce([]) { |memo, combo| memo << Hash[*[:date, :minutes].zip(combo).flatten] }
Here is how it looks -
2.1.5 :035 > date=["10/10,2010","11/10/2010","12/10/2010","13/10/2010","14/10/2010"]
=> ["10/10,2010", "11/10/2010", "12/10/2010", "13/10/2010", "14/10/2010"]
2.1.5 :036 > minutes = [10,20,30,40,50]
=> [10, 20, 30, 40, 50]
2.1.5 :037 > date.zip(minutes).reduce([]) { |memo, combo| memo << Hash[*[:date, :minutes].zip(combo).flatten] }
=> [{:date=>"10/10,2010", :minutes=>10}, {:date=>"11/10/2010", :minutes=>20}, {:date=>"12/10/2010", :minutes=>30}, {:date=>"13/10/2010", :minutes=>40}, {:date=>"14/10/2010", :minutes=>50}]
2.1.5 :038 >
Word of caution - you should really use a Struct, and then create an array of that Struct instances, instead of working on arrays of hashes like this.
If
dates = ["12/02/14", "6/03/14"]
minutes = [12, 19]
then if I've not misunderstood the question, it's just:
dates.zip(minutes).map { |d,m| {date: d, minutes: m} }
#=> [{:date=>"12/02/14", :minutes=>12}, {:date=>"6/03/14", :minutes=>19}]

Crop hash structure: Ruby on rails

I want to delete data from a hash table using a specific range of values.
Example:
hash = { t:1, y:9, k:10, a:30, b:40, c:50, d:80, e:60, z:100, l:3, n:9, f:20 }
Given an array of numbers: array = [10, 30, 40, 50, 80, 60, 100] (is exactly the range of the center of the table)
I want the result to be:
hash: {k:10, a:30, b:40, c:50, d:80, e:60, z:100}
Notes that never eliminated data that is in the middle of the structure.
Look at the select method.
[6] pry(main)> hash.select { |k,v| array.include?(v) }
=> {:k=>10, :a=>30, :b=>40, :c=>50, :d=>80, :e=>60, :z=>100}
results = {}
hash.each { |k, v| results[k] = v if array.include?(v) }
puts results
output:
{:k=>10, :a=>30, :b=>40, :c=>50, :d=>80, :e=>60, :z=>100}
array.each_with_object({}){|e, h| h[hash.key(e)] = e if hash.value?(e)}

Resources