Is there any shorter way to write this merger of hash? - ruby-on-rails

I have two hashes. The first hash should be prioritized. It should be overwritten by the second hash only when it is nil or blank.
main_hash.merge!(option_hash) do |key, main_hash, option_hash|
main_hash.presence || option_hash.presence
end

Use your code, which is fine, but use curly braces for the block and shorter block variables. And you don't need the second presence
main_hash.merge!(option_hash){|_k, h1, h2| h1.presence || h2}
#merge! (and #merge) with a block only invokes the block to handle cases where a key is present in both hashes. Where option_hash keys are not present in main_hash, the key/value pair is simply inserted.
You can use #merge! with a block to do some neat tricks, like setting up a combined total of hash values.
hash_1 = {a: 1, b: 1, c: 1}
hash_2 = {b: 1, c: 1, d: 1}
hash_1.merge!(hash_2{|_k, v1, v2| v1 + v2}
=> {:a => 1, :b => 2, :c => 2, :d => 1}

This is shorter, but I think your way is more readable.
main_hash = {a:1, b:nil, c:3}
option_hash = {a:5, b:2, c:8}
main_hash = option_hash.merge(main_hash.reject{|_,v| v.blank?})
#=> {a:1, b:2, c:3}
You could do it this way and give the result a variable name that makes what is happening a bit clearer like merged_main_option_hash instead of just main_hash

[1] pry(main)> main_hash = { key1: 1, key2: 2, key3: '', key4: nil }
=> {:key1=>1, :key2=>2, :key3=>"", :key4=>nil}
[2] pry(main)> option_hash = { key2: 2.2, key3: 3, key4: 4 }
=> {:key2=>2.2, :key3=>3, :key4=>4}
[3] pry(main)> main_hash.reject!{ |key, value| value.blank? }.merge!(option_hash) { |key, main_hash_value, option_hash_value| main_hash_value }
=> {:key1=>1, :key2=>2, :key3=>3, :key4=>4}
[4] pry(main)>
Refer apidock:merge!, apidock:reject! for more details.

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 }

ruby: add boundaries \b option when using Regexp.union (array)?

I am creating a regex matcher using:
Regexp.new(Regexp.union(some_hash.keys))
is it possible to add a boundaries filter to each element of the array so I have:
/\bkey1\b|\bkey2\b|,....../
For regexp keys:
Regexp.union(some_hash.keys.map { |k| /\b#{k}\b/ })
or for literal keys:
Regexp.union(some_hash.keys.map { |k| /\b#{Regexp.escape(k)}\b/ })
The result of Regexp.union is already a Regexp, no need for Regexp.new. In fact, we can also use plain strings inside Regexp.union, the difference being we don't initialise the flags in each subexpression:
Regexp.union(some_hash.keys.map { |k| "\\b#{k}\\b" })
Regexp.union(some_hash.keys.map { |k| "\\b#{Regexp.escape(k)}\\b" })
You can generate regex in simplest way without using Regexp as
hash = {'a' => 1, 'b' => 2, 'c' => 3, 'd' => 4}
/\b#{hash.keys.join('\b|\b')}\b/
=>/\ba\b|\bb\b|\bc\b|\bd\b/
Not exactly but you can use the scan method ...
for example:-
a = "cruel world"
a.scan(/\w+/) #=> ["cruel", "world"]
a.scan(/.../) #=> ["cru", "el ", "wor"]
a.scan(/(...)/) #=> [["cru"], ["el "], ["wor"]]
a.scan(/(..)(..)/) #=> [["cr", "ue"], ["l ", "wo"]]

Ruby - how to replace key in a hash by a key from different one?

I have this hash:
CARS = {"Audi" => 0,
"BMW" => 1,
...}
And this output from ActiveRecord (#top_cars):
{1=>18, 0=>17, 3=>13, 5=>10, 2=>5, 4=>1}
How do I replace the keys from #top_cars by the car names from CARS?
Thank you
EDIT:
So the desired output would be like {"BMW"=>18, "Audi"=>17, "Renault"=>13, "Mercedes"=>10, "Ford"=>5, "Porsche"=>1}
This would do the trick:
#top_cars.map {|key, value| [CARS.key(key), value]}.to_h
possible solution:
#top_cars.inject({}) {|memo, (key,value)| memo.merge(CARS.key(key) => value)}
You could merge cars with itself:
cars = { "Audi" => 0,
"Mercedes" => 1,
"Ford" => 2,
"Renault" => 3,
"BMW" => 4,
"Porsche" => 5
}
top_cars = {1=>18, 0=>17, 3=>13, 5=>10, 2=>5, 4=>1}
cars.merge(cars) { |*,n| top_cars[n] }
#=> {"Audi"=>17, "Mercedes"=>18, "Ford"=>5, "Renault"=>13, "BMW"=>1, "Porsche"=>10}
This uses the form of Hash#merge where a block is employed to determine the values of keys that are present in both hashes being merged, which here is all the keys.

How to convert an array with a single element that is a hash?

I have a minor but annoying issue: my API call often returns an array of a single element, which is a hash as follows:
foo = [{"key1" => 1, "key2" => 10}]
I have to extract a value from the hash as follows: foo[0]["key2"]. Is there a more optimal/correct way of doing this?
You can get the first element (the hash) either by [0] / first:
foo = [{"key1" => 1, "key2" => 10}]
foo[0]
# => {"key1" => 1, "key2" => 10}
foo.first
# => {"key1" => 1, "key2" => 10}
or by using Ruby's parallel assignment:
foo, _ = [{"key1" => 1, "key2" => 10}]
foo
# => {"key1" => 1, "key2" => 10}
This assigns just the first element to foo instead of the whole array.
If you are doing this over and over again, it might be a good idea to implement a custom method on top of the API you are using.
Like Tilo's:
results.each{|r| do_something_with( r['key2] )}
You could also do:
results = [{"key1"=>1, "key2"=>10}]
results, _ = results
results.each{|key, result| do_something_with( result )}
Replacing result['key2'] with just result
Depending on what you want to do with the extracted values, either .each or .collect sounds like your best option. Here's an example with collect:
results = [{"key1"=>1, "key2"=>10}]
key2_array = results.collect{|result| result["key2"]}
# key2_array contains [10]
results = [{"key1"=>1, "key2"=>10},{"key1"=>2, "key2"=>20},{"key1"=>3, "key2"=>30}]
key2_array = results.collect{|result| result["key2"]}
# key2_array contains [10,20,30]
If the question is just of making your code look a bit cleaner, you can write a wrapper method around your API call:
def my_api_wrapper(x, y, z)
fb_api_call(x, y, z).first
end
# Later...
foo = my_api_wrapper(x, y, z)
foo["key2"]

How to rotate by 90° an Array with ActiveRecord objects

I have got
#my_objects = [ #<MyObject id: 1, title: "Blah1">,
#<MyObject id: 2, title: "Blah2">,
#<MyObject id: 3, title: "Blah3">,
#<MyObject id: 4, title: "Blah4"> ]
I need to turn it into:
#my_objects = { :id => [ 1, 2, 3, 4],
:title => [ "Blah1" ... ] }
Is there built in method or some standart approach?
I can imagine only this
#my_objects.inject({}){ |h, c| c.attributes.each{ |k,v| h[k] ||= []; h[k] << v }; h }
This question was born while I was thinking on this particular question
First, use Enumerable#map (something like #o.map { |e| [e.id, e.title] }) to get the ActiveRecord array into a simplified pure Ruby object that looks like this:
a = [[1, "Blah1"], [2, "Blah2"], [3, "Blah3"], [4, "Blah4"]]
Then:
a.transpose.zip([:id, :title]).inject({}) { |m, (v,k)| m[k] = v; m }
Alternate solution: It might be less tricky and easier to read if instead you just did something prosaic like:
i, t = a.transpose
{ :id => i, :title => t }
Either way you get:
=> {:title=>["Blah1", "Blah2", "Blah3", "Blah4"], :id=>[1, 2, 3, 4]}
Update: Tokland has a refinement that's worth citing:
Hash[[:id, :title].zip(a.transpose)]
You're on the right track there, there's no custom method for this sort of pivot, and it should work, but remember that ActiveRecord attribute keys are strings:
#my_objects.inject({ }) { |h, c| c.attributes.each { |k,v| (h[k.to_sym] ||= [ ]) << v }; h }
You can use the (x ||= [ ]) << y pattern to simplify that a bit if you're not too concerned with it being super readable to a novice.
Functional approach (no eachs!):
pairs = #my_objects.map { |obj| obj.attributes.to_a }.flatten(1)
Hash[pairs.group_by(&:first).map { |k, vs| [k, vs.map(&:second)] }]
#=> {:title=>["Blah1", "Blah2", "Blah3", "Blah4"], :id=>[1, 2, 3, 4]}
As usual, Facets allows to write nicer code; in this case Enumerable#map_by would avoid using the ugly and convoluted pattern group_by+map+map:
#my_objects.map { |obj| obj.attributes.to_a }.flatten(1).map_by { |k, v| [k, v] }
#=> {:title=>["Blah1", "Blah2", "Blah3", "Blah4"], :id=>[1, 2, 3, 4]}

Resources