2

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
Nomad
  • 250
  • 3
  • 11
  • 27

4 Answers4

5

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[-4..-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[-4..])
  #=> {"f"=>3, "e"=>1, "c"=>3, "a"=>7}
stevec
  • 41,291
  • 27
  • 223
  • 311
Cary Swoveland
  • 106,649
  • 6
  • 63
  • 100
  • 1
    I really like `h.slice(*h.keys[-4..-1])` because it's expressive and unambiguous. – stevec Mar 04 '23 at 01:39
  • 1
    @stevec, thanks! Not only is your change more expressive but it corrects an error I made. I had written `h.slice(*h.keys[2..-1])`, but that needed to be `h.slice(*h.keys[h.size-4..-1])`. Please understand I was just a kid when I wrote that. – Cary Swoveland Mar 04 '23 at 06:29
  • 1
    Even better you can do `h.slice(*h.keys.last(4))` if you just want the last 4. – 7200rpm Apr 06 '23 at 23:12
3

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'] }}
mabe02
  • 2,676
  • 2
  • 20
  • 35
  • I sort of like this best. I would although have it sepereated since what I'm doing is the graph will display as is, but the "key" or "legend" would say the averages. Ran into this when trying to implement to_h NoMethodError 70214561670280 undefined method `to_h' for # I am using 1.9 – Nomad May 02 '16 at 15:30
  • actually the method `.to_h` is supported from Ruby 2.1. An alternative way is `Hash[your_array]` – mabe02 May 02 '16 at 15:36
  • This worked like a charm! Now to integrate this as a seperate just for my legend. Thanks! And to average the last 4? – Nomad May 02 '16 at 16:00
  • Try to have a look at this [question](http://stackoverflow.com/questions/1341271/how-do-i-create-an-average-from-a-ruby-array) – mabe02 May 03 '16 at 09:44
  • You could write `hash.to_a.last(4).to_h` instead of ` hash.map{|h| h}[-4..-1].to_h`. – Cary Swoveland May 03 '16 at 13:50
1
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.

ndnenkov
  • 35,425
  • 9
  • 72
  • 104
0

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).

Matouš Borák
  • 15,606
  • 1
  • 42
  • 53