Ruby easy search for key-value pair in an array of hashes - ruby

Suppose I have this array of hashes:
[
{"href"=>"https://company.campfirenow.com", "name"=>"Company", "id"=>123456789, "product"=>"campfire"},
{"href"=>"https://basecamp.com/123456789/api/v1", "name"=>"Company", "id"=>123456789, "product"=>"bcx"},
{"href"=>"https://company.highrisehq.com", "name"=>"Company", "id"=>123456789, "product"=>"highrise"}
]
How can I parse the "href" value of the hash where "product"=>"bcx"
Is there any easy way to do this in Ruby?

ary = [
{"href"=>"https://company.campfirenow.com", "name"=>"Company", "id"=>123456789, "product"=>"campfire"},
{"href"=>"https://basecamp.com/123456789/api/v1", "name"=>"Company", "id"=>123456789, "product"=>"bcx"},
{"href"=>"https://company.highrisehq.com", "name"=>"Company", "id"=>123456789, "product"=>"highrise"}
]
p ary.find { |h| h['product'] == 'bcx' }['href']
# => "https://basecamp.com/123456789/api/v1"
Note that this only works if the element exists. Otherwise you will be calling the subscription operator [] on nil, which will raise an exception, so you might want to check for that first:
if h = ary.find { |h| h['product'] == 'bcx' }
p h['href']
else
puts 'Not found!'
end
If you need to perform that operation multiple times, you should build yourself a data structure for faster lookup:
href_by_product = Hash[ary.map { |h| h.values_at('product', 'href') }]
p href_by_product['campfire'] # => "https://company.campfirenow.com"
p href_by_product['bcx'] # => "https://basecamp.com/123456789/api/v1"

Related

Check if multiple key-value pairs exist in a Ruby hash?

I have the following Ruby hash key-value pairs:
[
{
"trait_type"=>"Status",
"value"=>"Unbuilt",
"display_type"=>nil,
"max_value"=>nil,
"trait_count"=>4866,
"order"=>nil
}
]
What I need to check is see if the following key-value pairs are both present:
{
"value"=>"Unbuilt",
"trait_type"=>"Status"
}
Essentially wanting something to the effect of...
traits = [{"trait_type"=>"Status", "value"=>"Unbuilt", "display_type"=>nil, "max_value"=>nil, "trait_count"=>4866, "order"=>nil}]
filter_traits = {"value"=>"Unbuilt", "trait_type"=>"Status"}
traits.include? filter_traits
If you are using ruby >= 2.3, there is a fancy new Hash >= Hash operation that is conceptually similiar to a hypothetical contains?
Using your traits array:
trait = traits[0]
trait >= {"trait_type" => "Status", "value" => "Unbuilt"}
# => true
trait >= {"trait_type" => "Status", "value" => "Built"}
# => false
So you could try something like:
traits.select{|trait|
trait >= filter_traits
}.length > 0
# => true
arr = [
{
"trait_type"=>"Status",
"value"=>"Unbuilt",
"display_type"=>nil,
"max_value"=>nil,
"trait_count"=>4866,
"order"=>nil
}
]
h1 = { "value"=>"Unbuilt", "trait_type"=>"Status" }
h2 = { "value"=>"Rebuilt", "trait_type"=>"Status" }
Here are three solutions.
arr[0].slice(*h1.keys) == h1
#=> true
arr[0].slice(*h2.keys) == h2
#=> false
arr[0].values_at(h1.keys) == h1.values
#=> true
arr[0].values_at(h2.keys) == h2.values
#=> false
arr[0] == arr[0].merge(h1)
#=> true
arr[0] == arr[0].merge(h2)
#=> false
We also have the aptly named all? and any? methods that should do exactly what you're looking for in a very logical way.
All we need to do is loop through your filter_traits hash and test to see if all (or any) those key:value pairs are equal to (==) any corresponding key:value pairs inside your traits array:
traits = [{"trait_type"=>"Status", "value"=>"Unbuilt", "display_type"=>nil, "max_value"=>nil, "trait_count"=>4866, "order"=>nil}]
filter_traits = {"value"=>"Unbuilt", "trait_type"=>"Status"}
filter_traits.all? {|k, v| filter_traits[k] == traits[0][k]}
#=> true
filter_traits = {"value"=>"Built", "trait_type"=>"Status"}
filter_traits.all? {|k, v| filter_traits[k] == traits[0][k]}
#=> false
filter_traits.any? {|k, v| filter_traits[k] == traits[0][k]}
#=> true

Initializing a hash with an empty array keyed to an array of strings - Ruby

I have:
people=["Bob","Fred","Sam"]
holidays = Hash.new
people.each do |person|
a=Array.new
holidays[person]=a
end
gifts = Hash.new
people.each do |person|
a=Array.new
gifts[person]=a
end
Feels clunky. I can't seem to figure a more streamline way with an initialization block or somesuch thing. Is there an idiomatic approach here?
Ideally, I'd like to keep an array like:
lists["holidays","gifts",...]
... and itterate through it to initialize each element in the lists array.
people = %w|Bob Fred Sam|
data = %w|holidays gifts|
result = data.zip(data.map { people.zip(people.map { [] }).to_h }).to_h
result['holidays']['Bob'] << Date.today
#⇒ {
# "holidays" => {
# "Bob" => [
# [0] #<Date: 2016-11-04 ((2457697j,0s,0n),+0s,2299161j)>
# ],
# "Fred" => [],
# "Sam" => []
# },
# "gifts" => {
# "Bob" => [],
# "Fred" => [],
# "Sam" => []
# }
# }
More sophisticated example would be:
result = data.map do |d|
[d, Hash.new { |h, k| h[k] = [] if people.include?(k) }]
end.to_h
The latter produces the “lazy initialized nested hashes.” It uses the Hash#new with a block constructor for nested hashes.
Play with it to see how it works.
A common way of doing that would be to use Enumerable#each_with_objrect.
holidays = people.each_with_object({}) { |p,h| h[p] = [] }
#=> {"Bob"=>[], "Fred"=>[], "Sam"=>[]}
gifts is the same.
If you only want a number of such hashes then, the following should suffice:
count_of_hashes = 4 // lists.count; 4 is chosen randomly by throwing a fair die
people = ["Bob", "Fred", "Sam"]
lists = count_of_hashes.times.map do
people.map {|person| [person, []]}.to_h
end
This code also ensures the arrays and the hashes all occupy their own memory. As can be verified by the following code:
holidays, gifts, *rest = lists
holidays["Bob"] << "Rome"
And checking the values of all the other hashes:
lists
=> [
{"Bob"=>["Rome"], "Fred"=>[], "Sam"=>[]},
{"Bob"=>[], "Fred"=>[], "Sam"=>[]},
{"Bob"=>[], "Fred"=>[], "Sam"=>[]},
{"Bob"=>[], "Fred"=>[], "Sam"=>[]}
]

compare array of hashes and print expected & actual results

I have 2 array of hashes:
actual = [{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"},
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}]
expected = [{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"NUMBER"},
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"NUMBER"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}]
I need to compare these 2 hashes and find out the ones for which the column_data_type differs.
to compare we can directly use:
diff = actual - expected
This will print the output as:
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"}
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"}
My expected output is that in the result i want to print the actual and expected datatype, means the datatypes for the missing `column_name' from both the actual and expected array of hashes, something like:
{"column_name"=>"NONINTERESTEXPENSE", "expected_column_data_type"=>"NUMBER", "actual_column_data_type" => "VARCHAR"}
{"column_name"=>"TRANSACTIONDATE", "expected_column_data_type"=>"NUMBER","actual_column_data_type" => "TIMESTAMP" }
This will work irrespective of order of hashes in your array.
diff = []
expected.each do |elem|
column_name = elem['column_name']
column_type = elem['column_data_type']
match = actual.detect { |elem2| elem2['column_name'] == column_name }
if column_type != match['column_data_type']
diff << { 'column_name' => column_name,
'expected_column_data_type' => column_type,
'actual_column_data_type' => match['column_data_type'] }
end
end
p diff
[actual, expected].map { |a| a.map(&:dup).map(&:values) }
.map(&Hash.method(:[]))
.reduce do |actual, expected|
actual.merge(expected) do |k, o, n|
o == n ? nil : {name: k, actual: o, expected: n}
end
end.values.compact
#⇒ [
# [0] {
# :name => "NONINTERESTEXPENSE",
# :actual => "VARCHAR",
# :expected => "NUMBER"
# },
# [1] {
# :name => "TRANSACTIONDATE",
# :actual => "TIMESTAMP",
# :expected => "NUMBER"
# }
# ]
The method above easily expandable to merge N arrays (use reduce.with_index and merge with key "value_from_#{idx}".)
(expected - actual).
concat(actual - expected).
group_by { |column| column['column_name'] }.
map do |name, (expected, actual)|
{
'column_name' => name,
'expected_column_data_type' => expected['column_data_type'],
'actual_column_data_type' => actual['column_data_type'],
}
end
What about this?
def select(hashes_array, column_name)
hashes_array.select { |h| h["column_name"] == column_name }.first
end
diff = (expected - actual).map do |h|
{
"column_name" => h["column_name"],
"expected_column_data_type" => select(expected, h["column_name"])["column_data_type"],
"actual_column_data_type" => select(actual, h["column_name"])["column_data_type"],
}
end
PS: surely this code can be improved to look more elegant
Code
def convert(actual, expected)
hashify(actual-expected, "actual_data_type").
merge(hashify(expected-actual, "expected_data_type")) { |_,a,e| a.merge(e) }.values
end
def hashify(arr, key)
arr.each_with_object({}) { |g,h| h[g["column_name"]] =
{ "column_name"=>g["column_name"], key=>g["column_data_type"] } }
end
Example
actual = [
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"},
{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}
]
expected = [
{"column_name"=>"NONINTERESTINCOME", "column_data_type"=>"NUMBER"},
{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"NUMBER"},
{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"NUMBER"},
{"column_name"=>"UPDATEDATE", "column_data_type"=>"TIMESTAMP"}
]
convert(actual, expected)
#=> [{"column_name"=>"TRANSACTIONDATE",
# "actual_data_type"=>"TIMESTAMP", "expected_data_type"=>"NUMBER"},
# {"column_name"=>"NONINTERESTEXPENSE",
# "actual_data_type"=>"VARCHAR", "expected_data_type"=>"NUMBER"}]
Explanation
For the example above the steps are as follows.
First hashify actual and expected.
f = actual-expected
#=> [{"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"TIMESTAMP"},
# {"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"VARCHAR"}]
g = hashify(f, "actual_data_type")
#=> {"TRANSACTIONDATE"=>{"column_name"=>"TRANSACTIONDATE",
# "actual_data_type"=>"TIMESTAMP"},
# "NONINTERESTEXPENSE"=>{ "column_name"=>"NONINTERESTEXPENSE",
# "actual_data_type"=>"VARCHAR"}}
h = expected-actual
#=> [{"column_name"=>"NONINTERESTEXPENSE", "column_data_type"=>"NUMBER"},
# {"column_name"=>"TRANSACTIONDATE", "column_data_type"=>"NUMBER"}]
i = hashify(h, "expected_data_type")
#=> {"NONINTERESTEXPENSE"=>{"column_name"=>"NONINTERESTEXPENSE",
# "expected_data_type"=>"NUMBER"},
# "TRANSACTIONDATE"=>{"column_name"=>"TRANSACTIONDATE",
# "expected_data_type"=>"NUMBER"}}
Next merge g and i using the form of Hash#merge that employs a block to determine the values of keys that are present in both hashes being merged. See the doc for the definitions of the three block variables (the first of which, the common key, I've represented by an underscore to signify that it is not used in the block calculation).
j = g.merge(i) { |_,a,e| a.merge(e) }
#=> {"TRANSACTIONDATE"=>{"column_name"=>"TRANSACTIONDATE",
# "actual_data_type"=>"TIMESTAMP", "expected_data_type"=>"NUMBER"},
# "NONINTERESTEXPENSE"=>{"column_name"=>"NONINTERESTEXPENSE",
# "actual_data_type"=>"VARCHAR", "expected_data_type"=>"NUMBER"}}
Lastly, drop the keys.
k = j.values
#=> [{"column_name"=>"TRANSACTIONDATE", "actual_data_type"=>"TIMESTAMP",
# "expected_data_type"=>"NUMBER"},
# {"column_name"=>"NONINTERESTEXPENSE", "actual_data_type"=>"VARCHAR",
# "expected_data_type"=>"NUMBER"}]

How to map a Ruby hash?

Is there a better way to map a Ruby hash? I want to iterate over each pair and collect the values. Perhaps using tap?
hash = { a:1, b:2 }
output = hash.to_a.map do |one_pair|
k = one_pair.first
v = one_pair.last
"#{ k }=#{ v*2 }"
end
>> [
[0] "a=2",
[1] "b=4"
]
Ruby's hash includes the Enumerable module which includes the map function.
hash = {a:1, b:2}
hash.map { |k, v| "#{k}=#{v * 2}" }
Enumerable#map | RubyDocs
Err, yes, with map, invoked directly on the hash:
{ a:1, b:2 }.map { |k,v| "#{k}=#{v*2}" } # => [ 'a=2', 'b=4' ]
hash = { a:1, b:2 }
hash.map{|k,v| [k,v*2]* '='}
# => ["a=2", "b=4"]

Best way to merge key value pairs in a hash based on number of values for that key in Ruby

I have a hash of arrays in ruby as :
#people = { "a" => ["john", "mark", "tony"], "b"=> ["tom","tim"],
"c" =>["jane"], "others"=>["rob", "ryan"] }
I would like to merge all key value pairs where there are less than 3 items in the array for a particular keys values. They should be merged into the key called "others" to give roughly the result of
#people = { "a" => ["john", "mark", "tony"],
"others"=> ["rob", "ryan", "tom", "tim", "jane"] }
Using the following code is problematic as duplicate key values in a hash cannot exist:
#people = Hash[#people.map{|k,v| v.count<3 ? ["others",v] : [k,v]} ] %>
Whats the best way to elegantly solve this?
You almost have it, the problem is, as you notice, that you can't build the Hash's key/value pairs on the fly because of duplicates. One way around the problem is to start out with the skeleton of what you're trying to build:
#people = #people.each_with_object({ 'others' => [ ] }) do |(k,v), h|
if(v.length >= 3)
h[k] = v
else
h['others'] += v
end
end
Or, if you don't like each_with_object, you could:
h = { 'others' => [ ] }
#people.each do |k, v|
# as above
end
#people = h
Or you could use pretty much the same structure with inject (taking care, as usual, to return the right thing from the block).
There are certainly other ways to do this but these approaches are pretty clear and easy to understand; IMO clarity should be your first goal.
try:
>> #people = { "a" => ["john", "mark", "tony"], "b"=> ["tom","tim"],
"c" =>["jane"], "others"=>["rob", "ryan"] }
>> #new_people = {"others" => []}
>> #people.each_pair {|k,v| (v.size >= 3 && k!="others") ? #new_people.merge!(k=>v) : #new_people['others']+= v}
>> #new_people
=> {"others"=>["rob", "ryan", "jane", "tom", "tim"], "a"=>["john", "mark", "tony"]}
Hash[ #people.group_by { |k,v| v.size < 3 ? 'others' : k }.
map { |k,v| [k, v.flat_map(&:last)] } ]
=> {"a"=>["john", "mark", "tony"],
"others"=>["tom", "tim", "jane", "rob", "ryan"]}
What about this:
> less_than_three, others = #people.partition {|(key, values)| values.size >= 3 }
> Hash[less_than_three]
# => {"a"=>["john", "mark", "tony"]}
> Hash["others" => others.map {|o| o.last}.flatten]
# => {"others"=>["tom", "tim", "jane", "rob", "ryan"]}
#people[:others] = []
#people.each do |k, v|
#people[:others] |= #people.delete(k) if v.size < 3
end
#people.inject({}) do |m, (k, v)|
m[i = v.size >= 3 ? k : 'others'] = m[i].to_a + v
m
end

Resources