How to merge values of a single hash? - ruby

Is there any way to merge values of a single hash?
Example:
address = {
"apartment" => "1",
"building" => "Lido House",
"house_number" => "20",
"street_name" => "Mount Park Road",
"city" => "Greenfield",
"county" => nil,
"post_code" => "WD1 8DC"
}
Could we get an outcome which looks like this?
1 Lido House,
20 Mount Park Road,
Greenfield,
WD1 8DC
address.compact will remove the value which equals nil, but what if in a method you include string interpolation and you want to exclude the nil value for some addresses and include it for others without a comma at the end?
def address(hash)
hash.compact
puts "#{hash["apartment"]} #{hash["building"]}, \n#{hash["house_number"]} #{hash["street_name"]}, \n#{hash["city"]}, \n#{hash["county"]}, \n#{hash["post_code"]}"
end

You need to join the values in a string:
"#{address['house_number']} #{address['street_name']},\n#{address['city']},\n#{address['post_code']}"
You could also improve the formatting by making this a helper method, and using a HEREDOC:
def formatted_address(address)
<<~ADDRESS
#{address['house_number']} #{address['street_name']},
#{address['city']},
#{address['post_code']}
ADDRESS
end
Usage:
address = {
"house_number" => 20,
"street_name" => "Mount Park Road",
"city" => "Greenfield",
"post_code" => "WD1 8DC"
}
puts formatted_address(address)
# => 20 Mount Park Road,
# Greenfield,
# WD1 8DC

Use string formats.
"%{house_number} %{street_name},\n%{city},\n%{post_code}" % address

Related

I for some reason can't get this iteration thing right in my head

Once again I am a new student to this whole computer coding thing and I am doing a boot camp to try to get the basics and my foot in the door but for some reason I can't make this whole iteration thing stick in my brain we just started doing hashes in ruby and I have literally been staring at the checkpoint problem for a day and a half and I just can't make my brain know what the next logical step is to get the answer provided. It is in a pre work section before my actual live classes start here in a few weeks and it's only my second full week doing any coding at all so the most bare bones basic hints/answers would be greatly appreciated.
This is the problem:
Write a loop to give each person an email address that consists of their first name + last name # gmail.com. For example, Robert Garcia will have an email of robertgarcia#gmail.com. The program should end with: p people
people = [
{
"first_name" => "Robert",
"last_name" => "Garcia",
"hobbies" => ["basketball", "chess", "phone tag"]
},
{
"first_name" => "Molly",
"last_name" => "Barker",
"hobbies" => ["programming", "reading", "jogging"]
},
{
"first_name" => "Kelly",
"last_name" => "Miller",
"hobbies" => ["cricket", "baking", "stamp collecting"]
}
]
outer_index = 0
names = []
last_names = []
while outer_index < people.length
names << people[outer_index]["first_name"].downcase
last_names << people[outer_index]["last_name"].downcase
outer_index += 1
end
email = email = [names[0] + last_names[0] + "#gmail.com"]
this is all the farther I have gotten because everything I've tried to get it to go back trough and pick up the second and third names hasn't worked.
According to them this is what it is supposed to look like in the end:
so that you can see if the correct modifications were made to each hash. The result should be:
people =[
{
"first_name" => "Robert",
"last_name" => "Garcia",
"hobbies" => ["basketball", "chess", "phone tag"],
"email" => "robertgarcia#gmail.com"
},
{
"first_name" => "Molly",
"last_name" => "Barker",
"hobbies" => ["programming", "reading", "jogging"],
"email" => "mollybarker#gmail.com"
},
{
"first_name" => "Kelly",
"last_name" => "Miller",
"hobbies" => ["cricket", "baking", "stamp collecting"],
"email" => "kellymiller#gmail.com"
}
]
(Note that your output won't be indented nicely).
I am completely at a loss and I cannot see where I am going wrong so any help would be insanely helpful so I can get through this checkpoint and finish up week two and move on to week three asap.
It's pretty straightforward to loop over each element of the people array. We also can use string interpolation to easily compose the email address.
people.each do |h|
h["email"] = "#{h["first_name"]}#{h["last_name"]}#gmail.com".downcase
end
If we want to break this up a bit, we can.
people.each do |h|
fn = h["first_name"]
ln = h["last_name"]
h["email"] = "#{fn}#{ln}#gmail.com"
h["email"].downcase!
end
You're really overcomplicating it, there is no need to use while to simply loop across an array. Instead use #each from the Enumerable module:
people.each do |hash|
hash.merge!(
"email" => "#{hash['first_name']}#{hash['last_name']}#gmail.com".downcase
)
end
Or if you want a non-destructive version that doesn't alter the original data:
people.map do |hash|
hash.merge(
"email" => "#{hash['first_name']}#{hash['last_name']}#gmail.com".downcase
)
end

How to generate direct access keys to nested hash which contains hash and arrays as values?

I want to compare two XML files where one is input and the other is output. I am converting both into a hash.
My idea is to get all the keys from the input XML converted to hash, and search each key in both the input and output hashes for their respective key/value pairs.
I have a hash:
{
"requisition_header" => {
"requested_by" => {"login" => "coupasupport"},
"department" => {"name" => "Marketing"},
"ship_to_address" => {"name" => "Address_1431693296"},
"justification" => nil,
"attachments" => [],
"requisition_lines" => [
{
"description" => "Cleaning Services for Building A",
"line_num" => 1,
"need_by_date" => 2010-09-23 07:00:00 UTC,
"source_part_num" => nil,
"supp_aux_part_num" => nil,
"unit_price" => #<BigDecimal:a60520c,'0.3E4',9(18)>,
"supplier" => {"name" => "amazon.com"},
"account" => {
"code" => "SF-Marketing-Indirect",
"account_type" => {"name" => "Ace Corporate"}
},
"currency" => {"code" => "USD"},
"payment_term" => {"code" => "Net 30"},
"shipping_term" => {"code" => "Standard"},
"commodity" => {"name" => "Marketing-Services"}
}
]
}
}
It is nested and all the values are not directly accessible.
I want a way to generate direct access to each value in the hash.
For example:
requisition_header.requested_by.login
will access "coupasupport".
requisition_header.department.name
will access "Marketing".
requisition_header.requisition_lines[0].description
will access "Cleaning Services for Building A".
requisition_header.requisition_lines[0].line_num
will access "1".
requisition_header.requisition_lines[0].need_by_date
will access "2010-09-23 07:00:00 UTC".
Each key built can be used to search for the value directly inside the hash.
That could be done with the following method, that translates the nested hash into nested OpenStructs:
require 'ostruct'
def deep_structify(hash)
result = {}
hash.each do |key, value|
result[key] = value.is_a?(Hash) ? deep_structify(value) : value
end if hash
OpenStruct.new(result)
end
hash = {"requisition_header"=>{"requested_by"=>{"login"=>"coupasupport"}, "department"=>{"name"=>"Marketing"}, "ship_to_address"=>{"name"=>"Address_1431693296"}, "justification"=>nil, "attachments"=>[], "requisition_lines"=>[{"description"=>"Cleaning Services for Building A", "line_num"=>1, "need_by_date"=>2010-09-23 07:00:00 UTC, "source_part_num"=>nil, "supp_aux_part_num"=>nil, "unit_price"=>#<BigDecimal:a60520c,'0.3E4',9(18)>, "supplier"=>{"name"=>"amazon.com"}, "account"=>{"code"=>"SF-Marketing-Indirect", "account_type"=>{"name"=>"Ace Corporate"}}, "currency"=>{"code"=>"USD"}, "payment_term"=>{"code"=>"Net 30"}, "shipping_term"=>{"code"=>"Standard"}, "commodity"=>{"name"=>"Marketing-Services"}}]}}
struct = deep_structify(hash)
struct.requisition_header.department.name
#=> "Marketing"
You can do it by overriding OpenStruct#new as well,
require 'ostruct'
class DeepStruct < OpenStruct
def initialize(hash=nil)
#table = {}
#hash_table = {}
if hash
hash.each do |k,v|
#table[k.to_sym] = (v.is_a?(Hash) ? self.class.new(v) : v)
#hash_table[k.to_sym] = v
new_ostruct_member(k)
end
end
end
def to_h
#hash_table
end
end
Now you can do:
require 'deep_struct'
hash = {"requisition_header"=>{"requested_by"=>{"login"=>"coupasupport"}, "department"=>{"name"=>"Marketing"}, "ship_to_address"=>{"name"=>"Address_1431693296"}, "justification"=>nil, "attachments"=>[], "requisition_lines"=>[{"description"=>"Cleaning Services for Building A", "line_num"=>1, "need_by_date"=>2010-09-23 07:00:00 UTC, "source_part_num"=>nil, "supp_aux_part_num"=>nil, "unit_price"=>#<BigDecimal:a60520c,'0.3E4',9(18)>, "supplier"=>{"name"=>"amazon.com"}, "account"=>{"code"=>"SF-Marketing-Indirect", "account_type"=>{"name"=>"Ace Corporate"}}, "currency"=>{"code"=>"USD"}, "payment_term"=>{"code"=>"Net 30"}, "shipping_term"=>{"code"=>"Standard"}, "commodity"=>{"name"=>"Marketing-Services"}}]}}
mystruct = DeepStruct.new hash
mystruct.requisition_header.requested_by.login # => coupasupport
mystruct.requisition_header.to_h # => {"requested_by"=>{"login"=>"coupasupport"}
You could use BasicObject#method_missing:
Code
class Hash
def method_missing(key,*args)
(args.empty? && key?(key)) ? self[key] : super
end
end
Example
hash = { animals: {
pets: { dog: "Diva", cat: "Boots", python: "Stretch" },
farm: { pig: "Porky", chicken: "Little", sheep: "Baa" }
},
finishes: {
tinted: { stain: "Millers", paint: "Oxford" },
clear: { lacquer: "Target", varnish: "Topcoat" }
}
}
hash.finishes.tinted.stain
#=> "Millers
hash.animals.pets.cat
#=> "Boots"
hash.animals.pets
#=> {:dog=>"Diva", :cat=>"Boots", :python=>"Stretch"}
hash.animals
#=> {:pets=>{:dog=>"Diva", :cat=>"Boots", :python=>"Stretch"},
# :farm=>{:pig=>"Porky", :chicken=>"Little", :sheep=>"Baa"}}
Reader challenge
There is a potential "gotcha" with this approach. I leave it to the reader to identify it. My example contains a clue. (Mind you, there may be other problems I haven't thought of.)

Iterating over an array to create a nested hash

I am trying to create a nested hash from an array that has several elements saved to it. I've tried experimenting with each_with_object, each_with_index, each and map.
class Person
attr_reader :name, :city, :state, :zip, :hobby
def initialize(name, hobby, city, state, zip)
#name = name
#hobby = hobby
#city = city
#state = state
#zip = zip
end
end
steve = Person.new("Steve", "basketball","Dallas", "Texas", 75444)
chris = Person.new("Chris", "piano","Phoenix", "Arizona", 75218)
larry = Person.new("Larry", "hunting","Austin", "Texas", 78735)
adam = Person.new("Adam", "swimming","Waco", "Texas", 76715)
people = [steve, chris, larry, adam]
people_array = people.map do |person|
person = person.name, person.hobby, person.city, person.state, person.zip
end
Now I just need to turn it into a hash. One issue I am having is, when I'm experimenting with other methods, I can turn it into a hash, but the array is still inside the hash. The expected output is just a nested hash with no arrays inside of it.
# Expected output ... create the following hash from the peeps array:
#
# people_hash = {
# "Steve" => {
# "hobby" => "golf",
# "address" => {
# "city" => "Dallas",
# "state" => "Texas",
# "zip" => 75444
# }
# # etc, etc
Any hints on making sure the hash is a nested hash with no arrays?
This works:
person_hash = Hash[peeps_array.map do |user|
[user[0], Hash['hobby', user[1], 'address', Hash['city', user[2], 'state', user[3], 'zip', user[4]]]]
end]
Basically just use the ruby Hash [] method to convert each of the sub-arrays into an hash
Why not just pass people?
people.each_with_object({}) do |instance, h|
h[instance.name] = { "hobby" => instance.hobby,
"address" => { "city" => instance.city,
"state" => instance.state,
"zip" => instance.zip } }
end

rails + activerecord: how create a hash from table with particular field's value as key

Given a table ZipCodeInfos with fields zipcode, state, city (all strings), where zipcode is unique:
zipcode,city,state
"10000", "Fooville", "AA"
"10001", "Smallville", "AA"
"10002", "Whoville", "BB"
What is the fastest way to generate a hash object of the entire table where the zipcode is a key like this:
{ "10000" => {:city => "Fooville", :state => "AA" },
"10001" => {:city => "Smallville", :state => "AA" },
"10002" => {:city => "Whoville", :state => "BB" } }
I know for a given record I can use .attributes to generate a hash with key,value pairs of field-names, field-values, for example Zipcode.first.attributes gives me
{"id" => 1, "zipcode" => "10000", "city" => "Fooville", "state => "AA" }
But, short of brute force iterating over each record (via .map), I cannot quite figure out how to create the desired hash with the zipcode as the key for each node of the hash.
This is the best I could come up with, and I suspect there is some nifty Ruby goodness that is faster?
zip_info_hash = {}
ZipCodeInfo.all.map{|x| zip_info_hash[x.zip] =
{'state' => x.state, 'city' => x.city }}
You could also try:
ZipCodeInfos.all.group_by &:zipcode
will get you a hash of zip code to array of ZipCodeInfos activerecords.
You can use inject method.
Here is what I generally use.
def visitors_name_email
visitors.inject({}) do |result, visitor|
result.merge(visitor.name => visitor.email)
end
end
I can't think of a way to avoid map here. I'd make only some minor changes to your code:
zip_info=Hash[*ZipCodeInfo.all
.map{|x| [x.zip, {:city => x.city, :state => x.state}]}
.flatten]

Parsing text using Ruby

I have the following text which will always follow the same format:
1
"13"
"241"
"Rabun"
"06"
"County"
2
"13"
"281"
"Towns"
"06"
"County"
I would like to assign each section to a hash like:
locality= {:id => "", :fips1 => "", :fips2 => "", :county => "", :stateid => "", :type => ""}
How would I go about doing this in Ruby? Any help is greatly appreciated.
fields = [:fips1,:fips2,:county,:stateid,:type]
arraywithhashes = yourtextdata.split("\n\n").map { |loc|
Hash[
[[:id,loc[/\d+/]]] +
fields.zip(loc.scan(/"([^"]+)"/).map &:first)
]
}
If you add new fields to your file, the only you'll need to edit is to add it to fields.
for each section, use a regular expression with groups corresponding to each entry in the section, then simply create hash table as you described from these groups.
locality.each_key { |k| locality.store(k, "foo") }
Another newbie-ish person here, but that might be a start for you.
You might want to consider using a Struct instead of a Hash.
Locality = Struct.new(:id, :fips1, :fips2, :county, :stateid, :type)
localities = []
DATA.each_slice(7) do |chunk|
chunk.pop if chunk.size == 7
localities << Locality.new(*chunk.map{|line| line.scan(/\w+/) })
end
p localities # => [#<struct Locality id=["1"], fips1=["13"], fips2=["241"], etc.
puts localities[1].fips2 # => 281
__END__
1
"13"
"241"
"Rabun"
"06"
"County"
2
"13"
"281"
"Towns"
"06"
"County"
each_slice(7) takes 7 lines of
DATA (the stuff after __END__ ).
The last line is removed unless there
are only six lines (the last
'record').
A cleaned-up copy of the remaining
lines is made. With these values a
new Locality is created and added to
an array

Resources