Best way to find a hash that contains a key, in an array of hashes in ruby? - ruby

Say we have
[{:name=>"Joe", :age => 15},{:name=>"Josh", :age => 83},{:name=>"Jim", :age => 1203}]
What is the best way way to get the hash where the name is "Joe"? To return {:name=>"Joe", :age=>15}?

array.select will return an array of values (even if there is only one match):
my_array.select { |item| item[:name] == "Joe" }
# => [{:name => "Joe", :age => 15}]
You can use Enumerable's find method instead to return the first instance (as a hash):
my_array.find { |item| item[:name] == "Joe" }
# => {:name => "Joe", :age => 15}

array.select { |e| e[:name] == 'Joe' }.first

If this is an operation you need to perform frequently, then store the set of hashes as a hash of hashes, with the value for the name key as the key to each hash. That way you can look up your hashes in O(1) time.

Related

is there a built in way to get new hash consisting of only certain key in Ruby?

Say i have data hash like this:
data = [{..}, {..}, {..}]
each hash is like this
{ :ctiy => 'sdfd', :pop => 33, :best_food=> 'sdfa'....}
now how can I get an Array of hashes only containing certain key/value or multiple keys. So take city, if I want new array of hashes containing city only.
I know, I can loop and filter manually but is there a built in method I am missing on.
map will help:
original_array_of_hashes.map do |hash|
{ city: hash[:city] }
end
If you're using Rails, the slice method will be available:
original_array_of_hashes.map do |hash|
hash.slice(:city)
end
For multiple keys:
# without 'slice'
original_array_of_hashes.map do |hash|
{ key_one: hash[:key_one], key_two: hash[:key_two] }
end
# with 'slice'
original_array_of_hashes.map do |hash|
hash.slice(:key_one, :key_two)
end
arr = [{ :city => 'Paris', :country => 'France', :pop => 2240001 },
{ :city => 'Bardelona', :country => 'Spain', :pop => 1600002},
{ :city => 'Vancouver', :country => 'Canada', :pop => 603503 }]
def some_keys(arr, *keys_to_keep)
arr.map { |h| h.select { |k,_| keys_to_keep.include? k } }
end
some_keys (arr)
#=> [{}, {}, {}]
some_keys(arr, :city)
#=> [{:city=>"Paris"}, {:city=>"Bardelona"}, {:city=>"Vancouver"}]
some_keys(arr, :city, :pop)
#=> [{:city=>"Paris", :pop=>2240001},
# {:city=>"Bardelona", :pop=>1600002},
# {:city=>"Vancouver", :pop=>603503}]
some_keys(arr, :city, :country, :pop)
#=> [{:city=>"Paris", :country=>"France", :pop=>2240001},
# {:city=>"Bardelona", :country=>"Spain", :pop=>1600002},
# {:city=>"Vancouver", :country=>"Canada", :pop=>603503}]
This uses Enumerable#map and Hash#select (not Enumerable#select).

how can I programmatically identify which keys have sub key-value-pairs in a JSON doc? [duplicate]

This question already has answers here:
Flattening nested hash to a single hash with Ruby/Rails
(6 answers)
Closed 8 years ago.
I fetch a JSON document and need to programmatically "flatten" the keys for another third-party service.
What this means is, if my JSON doc comes back with the following:
{'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
I need to be able to know to create a "flat" key-value pair for a third-party service like this:
first_name = "Joe"
hoffman.patterns = "negativity, self-sabotage"
hoffman.right_road = "happy family"
mbti = "INTJ"
Once I know there's a sub-document, the parsing I think I have figured out just appending the sub-keys with key + '.' + "{subkey}" but right now, don't know which ones are straight key-value and which one's have sub-documents.
Question:
a) How can I parse the JSON to know which keys have sub-documents (additional key-values)?
b) Suggestions on ways to create a string from an array
You could also monkey patch Hash to do this on it's own like so:
class Hash
def flatten_keys(prefix=nil)
each_pair.map do |k,v|
key = [prefix,k].compact.join(".")
v.is_a?(Hash) ? v.flatten_keys(key) : [key,v.is_a?(Array) ? v.join(", ") : v]
end.flatten.each_slice(2).to_a
end
def to_flat_hash
Hash[flatten_keys]
end
end
Then it would be
require 'json'
h = JSON.parse(YOUR_JSON_RESPONSE)
#=> {'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
h.to_flat_hash
#=> {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
Will work with additional nesting too
h = {"first_name"=>"Joe", "hoffman"=>{"patterns"=>["negativity", "self-sabotage"], "right_road"=>"happy family", "wrong_road"=>{"bad_choices"=>["alcohol", "heroin"]}}, "mbti"=>"INTJ"}
h.to_flat_hash
#=> {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "hoffman.wrong_road.bad_choices"=>"alcohol, heroin", "mbti"=>"INTJ"}
Quick and dirty recursive proc:
# assuming you've already `JSON.parse` the incoming json into this hash:
a = {'first_name' => "Joe", 'hoffman' => {'patterns' => ['negativity', 'self-sabotage'], 'right_road' => 'happy family'}, 'mbti' => 'INTJ'}
# define a recursive proc:
flatten_keys = -> (h, prefix = "") do
#flattened_keys ||= {}
h.each do |key, value|
# Here we check if there's "sub documents" by asking if the value is a Hash
# we also pass in the name of the current prefix and key and append a . to it
if value.is_a? Hash
flatten_keys.call value, "#{prefix}#{key}."
else
# if not we concatenate the key and the prefix and add it to the #flattened_keys hash
#flattened_keys["#{prefix}#{key}"] = value
end
end
#flattened_keys
end
flattened = flatten_keys.call a
# => "first_name"=>"Joe", "hoffman.patterns"=>["negativity", "self-sabotage"], "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
And then, to turn the arrays into strings just join them:
flattened.inject({}) do |hash, (key, value)|
value = value.join(', ') if value.is_a? Array
hash.merge! key => value
end
# => {"first_name"=>"Joe", "hoffman.patterns"=>"negativity, self-sabotage", "hoffman.right_road"=>"happy family", "mbti"=>"INTJ"}
Another way, inspired by this post:
def flat_hash(h,f=[],g={})
return g.update({ f=>h }) unless h.is_a? Hash
h.each { |k,r| flat_hash(r,f+[k],g) }
g
end
h = { :a => { :b => { :c => 1,
:d => 2 },
:e => 3 },
:f => 4 }
result = {}
flat_hash(h) #=> {[:a, :b, :c]=>1, [:a, :b, :d]=>2, [:a, :e]=>3, [:f]=>4}
.each{ |k, v| result[k.join('.')] = v } #=> {"a.b.c"=>1, "a.b.d"=>2, "a.e"=>3, "f"=>4}

New hash from array of hashes

The objective of the code below is to produce a hash with the keys being the :id field of
the hashes in original_array, and the values being all elements in original_array which have that :id.
original_array = [
{:id => '123', :name => 'test'},
{:id => '123', :name => 'another test'},
{:id => '456', :name => 'yet another test'}
]
new_hash = {}
original_array.each do |a|
new_hash[a[:id]] = original_array.select {|x| x[:id] == a[:id]}
end
My code does that, but there must be some better way to do it, ideally where the hash can be created in one step. If anyone can suggest and explain one (in the hope that I might improve my understanding of this sort of thing), then it would be appreciated.
This should do it
new_hash = original_array.group_by{|h| h[:id]}
Documentation: Enumerable#group_by.

ruby sort_by method

I have just started to learn ruby. I have an array of hashes. I want to be able to sort the array based on an elementin the hash. I think I should be able to use the sort_by method. Can somebody please help?
#array of hashes
array = []
hash1 = {:name => "john", :age => 23}
hash2 = {:name => "tom", :age => 45}
hash3 = {:name => "adam", :age => 3}
array.push(hash1, hash2, hash3)
puts(array)
Here is my sort_by code:
# sort by name
array.sort_by do |item|
item[:name]
end
puts(array)
Nothing happens to the array. There is no error either.
You have to store the result:
res = array.sort_by do |item|
item[:name]
end
puts res
Or modify the array itself:
array.sort_by! do |item| #note the exclamation mark
item[:name]
end
puts array
You can do it by normal sort method also
array.sort { |a,b| a[:name] <=> b[:name] }
Above is for ascending order, for descending one replace a with b. And to modify array itself, use sort!
You can use sort by method in one line :
array.sort_by!{|item| item[:name]}

How can I assign multiple values to a hash key?

For the sake of convenience I am trying to assign multiple values to a hash key in Ruby. Here's the code so far
myhash = { :name => ["Tom" , "Dick" , "Harry"] }
Looping through the hash gives a concatenated string of the 3 values
Output:
name : TomDickHarry
Required Output:
:name => "Tom" , :name => "Dick" , :name => "Harry"
What code must I write to get the required output?
myhash.each_pair {|k,v| v.each {|n| puts "#{k} => #{n}"}}
#name => Tom
#name => Dick
#name => Harry
The output format is not exactly what you need, but I think you get the idea.
The answers from Rohith and pierr are fine in this case. However, if this is something you're going to make extensive use of it's worth knowing that the data structure which behaves like a Hash but allows multiple values for a key is usually referred to as a multimap. There are a couple of implementations of this for Ruby including this one.
You've created a hash with the symbol name as the key and an array with three elements as the value, so you'll need to iterate through myhash[:name] to get the individual array elements.
re: the issue of iterating over selective keys. Try using reject with the condition inverted instead of using select.
e.g. given:
{:name=>["Tom", "Dick", "Harry"], :keep=>[4, 5, 6], :discard=>[1, 2, 3]}
where we want :name and :keep but not :discard
with select:
myhash.select { |k, v| [:name, :keep].include?(k) }
=> [[:name, ["Tom", "Dick", "Harry"]], [:keep, [4, 5, 6]]]
The result is a list of pairs.
but with reject:
myhash.reject { |k, v| ![:name, :keep].include?(k) }
=> {:name=>["Tom", "Dick", "Harry"], :keep=>[4, 5, 6]}
The result is a Hash with only the entries you want.
This can then be combined with pierr's answer:
hash_to_use = myhash.reject { |k, v| ![:name, :keep].include?(k) }
hash_to_use.each_pair {|k,v| v.each {|n| puts "#{k} => #{n}"}}

Resources