Crop hash structure: Ruby on rails - 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)}

Related

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}

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

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

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}]

How to refactor each function with map in Ruby?

I have a loop building a hash for use in a select field. The intention is to end up with a hash:
{ object.id => "object name", object.id => "object name" }
Using:
#hash = {}
loop_over.each do |ac|
#hash[ac.name] = ac.id
end
I think that the map method is meant for this type of situation but just need some help understanding it and how it works. Is map the right method to refactor this each loop?
Data transformations like this are better suited to each_with_object:
#hash = loop_over.each_with_object({}) { |ac, h| h[ac.name] = ac.id }
If your brain is telling you to use map but you don't want an array as the result, then you usually want to use each_with_object. If you want to feed the block's return value back into itself, then you want inject but in cases like this, inject requires a funny looking and artificial ;h in the block:
#hash = loop_over.inject({}) { |h, ac| h[ac.name] = ac.id; h }
# -------------------- yuck -----------------------------^^^
The presence of the artificial return value is the signal that you want to use each_with_object instead.
Try:
Hash[loop_over.map { |ac| [ac[:name], ac[:id]] }]
Or if you are running on Ruby 2:
loop_over.map { |ac| [ac[:name], ac[:id]] }.to_h
#hash = Hash[loop_over.map { |ac| {ac.name => ac.id} }.map(&:flatten)]
Edit, a simpler solution as per suggestion in a comment.
#hash = Hash[ loop_over.map { |ac| [ac.name, ac.id] } ]
You can simply do this by injecting a blank new Hash and performing your operation:
loop_over.inject({}){ |h, ac| h[ac.name] = ac.id; h }
Ruby FTW
No a map isn't the correct tool for this.
The general use-case of a map is to take in an array, perform an operation on each element, and spit out a (possibly) new array (not a hashmap) of the same length, with the individual element modifications.
Here's an example of a map
x = [1, 2, 3, 4].map do |i|
i+1 #transform each element by adding 1
end
p x # will print out [2, 3, 4, 5]
Your code:
#hash = {}
loop_over.each do |ac|
#hash[ac.name] = ac.id
end
There is nothing wrong with this example. You are iterating over a list, and populating a hashmap exactly as you wished.
Ruby 2.1.0 introduces brand new method to generate hashes:
h = { a: 1, b: 2, c: 3 }
h.map { |k, v| [k, v+1] }.to_h # => {:a=>2, :b=>3, :c=>4}
I would go for the inject version, but use update in the block to avoid the easy to miss (and therefore error prone) ;h suffix:
#hash = loop_over.inject({}) { |h, ac| h.update(ac.name: ac.id) }

How can I write a Ruby one-liner to read a small AR table into a hash?

I have a model called Category that contains about a thousand records. It changes extremely infrequently. I want to avoid millions of database hits by caching it, which is no big deal.
But I found that I didn't know how to do it in one line. I can do it in two:
category_hash = {}
Category.each { |c| category_hash[c.id] => category }
I know how to return a 2D array from the block. But is there a way to create and return a hash from such a block?
In Rails there's Enumerable#index_by:
category_hash = Category.all.index_by(&:id)
Without Rails I'd use:
Hash[Category.all.map{ |c| [c.id, c] }]
Hash::[] creates a Hash from both, flat and nested Arrays:
Hash["a", 100, "b", 200] #=> {"a"=>100, "b"=>200}
Hash[ [ ["a", 100], ["b", 200] ] ] #=> {"a"=>100, "b"=>200}
Category.all.reduce(Hash.new) { |h, c| h[c.id] = c; h }
You can do this way:
Category.each_with_object({}) { |c,category_hash| category_hash[c.id] = category }

Resources