How do you iterate over an array to create a hash? - ruby

I need to create a hash from the array below, and have it look like - peoples_ages = {"Joe"=> 25}. I can iterate over it using each_with_index, but I don't need the index as the value, I need the person's age. Instead I was thinking of using either Hash[people_array]... or .each_with_object. Is it best to use .map instead and just put .to_h after?
class Person
attr_reader :name, :age
def initialize(name, age)
#name = name
#age = age
end
end
nick = Person.new("Nick", 25)
jim = Person.new("Jim", 37)
bob = Person.new("Bob", 23)
rob = Person.new("Rob", 29)
sue = Person.new("Sue", 31)
peeps = [nick, jim, bob, rob, sue]
# iterate over peeps array to create a hash that looks like this:
# people_ages = {
# "Nick" => 25,
# "Jim" => 37,
# "Bob" => 23,
# etc...
# }

peeps.each_with_object({}){|e, h| h[e.name] = e.age}

Hash[peeps.map {|person| [person.name, person.age]} ]
Or if Ruby 2.0
peeps.map {|person| [person.name, person.age]}.to_h

Related

Ruby - Set age in array with multiple objects

I am trying to figure out how to look through an array of objects, find said object by the persons name, and then use a class method to edit the age value of that object. In the example below, I am trying to set the persons age by using the .set_age method that I have configured. However, it does not return any change to any objects, and I know it's because I'm not accessing the object properly, but I can't figure out how to do so. Can anyone help me access an object in an array and then find the person by their name and then set their age?
class Person
attr_accessor :name, :age, :mood
def initialize(name, age, mood)
#name = name
#age = age
#mood = mood
end
def set_age(amount)
#age = amount
self
end
end
person1 = Person.new("John", 18, "happy")
person2 = Person.new("Jackie", 20, "happy")
person3 = Person.new("Charlie", 19, "sad")
persons_array = []
persons_array << person1
persons_array << person2
persons_array << person3
while true
p "1. Change persons age"
p "2. Exit"
choice = gets.chomp
case choice
when "1"
p "Name of person to change."
person_name = gets.chomp
p "What would you like to set their age to?"
person_age = gets.chomp.to_i
persons_array.each_with_index {|key, value|
if key == person_name
person_name.set_age(person_age)
p "changed #{person_name} age"
p persons_array
end
}
p persons_array
when "2"
break
end
end
It looks like you're trying to match the person_name against the index of the array. When you do persons_array.each_with_index, you're telling Ruby that you'd like to iterate through the array and have the index of each element available to the block as well.
However, an Array is indexed by integers, so you're basically comparing the person's name from the stdin to an integer. You'd be better off comparing the object's actual name.
Something like:
found_person = persons_array.find { |person| person.name == person_name }
if found_person
found_person.set_age(person_age)
p "changed #{person_name} name"
end

Find max value in a class in ruby

I've tried for several hours making this work.
i have this code where i have around 20 different persons, with different ages and names.
how can i make ruby searching through all the ages and show the highest age as and integer
i've been searching a lot, but cant seem to make it work. i've even tried to make it sort the numbers and the print the last age, which must be the highest number
def initialize(firstname, familyname, age)
#firstname = firstname
#familyname = familyname
#age = age
Best regards
If you have a class like this:
class Person
attr_accessor :age
def initialize(age)
#age = age
end
end
And an array like this:
people = [Person.new(10), Person.new(20), Person.new(30)]
Finding the maximum age
You can get the ages with Array#map:
people.map { |person| person.age }
#=> [10, 20, 30]
# or shorter
people.map(&:age)
#=> [10, 20, 30]
And the maximum value with Enumerable#max:
people.map(&:age).max
#=> 30
Finding the oldest person
Or you could find the oldest person with Enumerable#max_by:
oldest = people.max_by { |person| person.age }
#=> #<Person:0x007fef4991d0a8 #age=30>
# or shorter
oldest = people.max_by(&:age)
#=> #<Person:0x007fef4991d0a8 #age=30>
And his or her age with:
oldest.age
#=> 30
Say for example your class looks like this:
class Person
attr_reader :firstname, :familyname, :age
def initialize(firstname, familyname, age)
#firstname = firstname
#familyname = familyname
#age = age
end
end
And say that you have an array of these objects called people. Then you could do something like:
puts people.max_by { |p| p.age }.age
Use max
e.g
class C
attr_accessor :age
def initialize(age)
#age = age
end
end
a,b,c,d = C.new(10), C.new(2), C.new(22), C.new(15)
[a,b,c,d].map(&:age).max #=> 22
[a.age,b.age,c.age,d.age].max #=> 22
Once you've collected all the instances into an array, using one of the techniques shown in How to get class instances in Ruby?, you can use the technique shown in Finding the element of a Ruby array with the maximum value for a particular attribute to find the maximum age. For example, if Foo is your class and age is an attribute of your class, the following should work:
ObjectSpace.each_object.select{|foo| obj.class == Foo}.max_by {|foo| foo.age}

Match an item to a list of items

Let's say I want to group States into Regions. For instance, the Southwest Region might be: Texas, Oklahoma, Colorado, New Mexico, Utah, Arizona, Nevada.
Somehow I need to create the list of states and define the region groupings. I also need to be able to lookup a region given the name of a state, something like region_for('Texas') which would return 'Southwest'.
What is the best, cleanest, "Ruby Way" to do something like this? I'd like to do it using plain 'ol ruby, no database or frameworks.
You can almost type this data structure directly into Ruby...
result = {
'Southwest' => %W{Texas Oklahoma Colorado New\ Mexico Utah Arizona Nevada},
'West' => %W{California Oregon Washington},
}.inject({}) do |m, (k, v)|
m[k] = v
v.each { |s| m[s] = k }
m
end
This produces a single Hash that has both states and regions as keys identifying each other. The data structure looks something like:
{"Colorado" => "Southwest",
"New Mexico" => "Southwest",
"Oklahoma" => "Southwest",
"California" => "West",
"Oregon" => "West",
"Texas" => "Southwest",
"Washington" => "West",
"Utah" => "Southwest",
"Nevada" => "Southwest",
"Arizona" => "Southwest"
"Southwest" =>
["Texas", "Oklahoma", "Colorado", "New Mexico", "Utah", "Arizona", "Nevada"],
"West" =>
["California", "Oregon", "Washington"],
}
Another approach would create a separate hash for states. Then you could get a list of regions or states by using Hash#keys, but you could do that here as well by using Enumerable#select or Enumerable#reject based on the type of the value.
The 'pure' ruby way is just to use hashes, and then have keys to do your lookups. There's a gem that kind of does something like this: ruport. It might be worth it to look at the source-code. For the use case you've illustrated, I'd have something like:
class RegionMapper
#potentially put this in a config file
REGIONS = Hash[[['California', 'Southwest'], ...]]
def initialize
#region_map = REGIONS.inject({}) {|r, e| r[e.second] ||= []; r[e.second] << e.first; r}
end
def region_for_state(state)
REGIONS[state]
end
def states_for_region(region)
#region_map(region)
end
end
The point is, to be efficient, you want to have a hash to do the lookups on each key you want to search by. But you dont' want to expose the data-duplication, so you put it all in a class.
If you have multiple values / keys, then you really have a table. If you want to keep constant time lookups, then you build a hash for each column (like the #region_map)
Try:
class State
attr_accessor :name, :region
def initialize(name, region=nil)
#name = name
#region = region
end
end
class Region
attr_accessor :name, :states
def initialize(name, states)
#name = name
#states = states
end
def set_state_regions
self.states.each {|state| state.region = self.name}
end
end
mo = State.new("missouri")
il = State.new("illionois")
oh = State.new("ohio")
midwest = Region.new("midwest", [mo, il, oh])
midwest.states.each {|state| puts state.name}
midwest.set_state_regions
I may come back and reflect on this later, I think it violates some OO principles.
I builded a very similar answer like Caley.
Main difference: I stored my data in a yaml-structure.
require 'yaml'
class Region
##all = {}
def self.[](key)
##all[key]
end
def initialize(name)
#name = name
#states = []
##all[#name] = self
end
def <<(state)
#states << state
state.region = state
end
def each_state
#states.each{|state| yield state } if block_given?
#states
end
attr_reader :name
end
class State
##all = {}
def self.[](key)
##all[key]
end
def initialize(name, region = nil)
#name = name
#region = region
##all[#name] = self
end
attr_accessor :name
attr_accessor :region
end
YAML.load(DATA).each{|region,states|
r = Region.new(region)
states.each{|state| r << State.new(state) }
}
p Region['Southwest Region']
p Region['Southwest Region'].each_state
Region['Southwest Region'].each_state{|state|
p state.name
}
__END__
Southwest Region:
- Texas
- Oklahoma
- Colorado
- New Mexico
- Utah
- Arizona
- Nevada.
Pacific:
- California
- Oregon
- Washington
A Hash is fine, you don't need anything fancier for this.
region = {
"Maine" => "New England",
"New Hampshire" => "New England",
etc
}
Which you then use like
region["Maine"]
Or if you want to set it up more compactly, like this:
regions = {
"Southwest" => ["Texas", "Oklahoma", "Colorado", "New Mexico", "Utah", "Arizona", "Nevada"],
"New England" => ["Maine", "New Hampshire", "Vermont", "Massachusetts","Rhode Island", "Connecticut"],
etc
}
region = {}
regions.each do |r,states|
states.each do |state|
region[state] = r
end
end

Merge Ruby arrays

I have a few arrays of Ruby objects of class UserInfo:
class UserInfo
attr_accessor :name, :title, :age
end
How can I merge these arrays into one array? A user is identified by its name, so I want no duplicate names. If name, title, age, etc. are equal I'd like to have 1 entry in the new array. If names are the same, but any of the other details differ I probably want those 2 users in a different array to manually fix the errors.
Thanks in advance
Redefine equality comparison on your object, and you can get rid of actual duplicates quickly with Array#uniq
class UserInfo
attr_accessor :name, :title, :age
def == other
name==other.name and title==other.title and age==other.age
end
end
# assuming a and b are arrays of UserInfo objects
c = a | b
# c will only contain one of each UserInfo
Then you can sort by name and look for name-only duplicates
d = c.sort{ |p,q| p.name <=> q.name } #sort by name
name = ""
e = []
d.each do |item|
if item.name == name
e[-1] = [e[-1],item].flatten
else
e << item
end
end
A year ago I monkey patched a kind of cryptic instance_variables_compare on Object. I guess you could use that.
class Object
def instance_variables_compare(o)
Hash[*self.instance_variables.map {|v|
self.instance_variable_get(v)!=o.instance_variable_get(v) ?
[v,o.instance_variable_get(v)] : []}.flatten]
end
end
A cheesy example
require 'Date'
class Cheese
attr_accessor :name, :weight, :expire_date
def initialize(name, weight, expire_date)
#name, #weight, #expire_date = name, weight, expire_date
end
end
stilton=Cheese.new('Stilton', 250, Date.parse("2010-12-02"))
gorgonzola=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
irb is my weapon of choice
>> stilton.instance_variables_compare(gorgonzola)
=> {"#name"=>"Gorgonzola", "#expire_date"=>#<Date: 4910305/2,0,2299161>}
>> gorgonzola.instance_variables_compare(stilton)
=> {"#name"=>"Stilton", "#expire_date"=>#<Date: 4910275/2,0,2299161>}
>> stilton.expire_date=gorgonzola.expire_date
=> #<Date: 4910305/2,0,2299161>
>> stilton.instance_variables_compare(gorgonzola)
=> {"#name"=>"Gorgonzola"}
>> stilton.instance_variables_compare(stilton)
=> {}
As you can see the instance_variables_compare returns an empty Hash if the two objects has the same content.
An array of cheese
stilton2=Cheese.new('Stilton', 210, Date.parse("2010-12-02"))
gorgonzola2=Cheese.new('Gorgonzola', 250, Date.parse("2010-12-17"))
arr=[]<<stilton<<stilton2<<gorgonzola<<gorgonzola2
One hash without problems and one with
h={}
problems=Hash.new([])
arr.each {|c|
if h.has_key?(c.name)
if problems.has_key?(c.name)
problems[c.name]=problems[c.name]<<c
elsif h[c.name].instance_variables_compare(c) != {}
problems[c.name]=problems[c.name]<<c<<h[c.name]
h.delete(c.name)
end
else
h[c.name]=c
end
}
Now the Hash h contains the objects without merging problems and the problems hash contains those that has instance variables that differs.
>> h
=> {"Gorgonzola"=>#<Cheese:0xb375e8 #name="Gorgonzola", #weight=250, #expire_date=#<Date: 2010-12-17 (4911095/2,0,2299161)>>}
>> problems
=> {"Stilton"=>[#<Cheese:0xf54c30 #name="Stilton", #weight=210, #expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>, #<Cheese:0xfdeca8 #name="Stilton", #weight=250,#expire_date=#<Date: 2010-12-02 (4911065/2,0,2299161)>>]}
As far as I can see you will not have to modify this code at all to support an array of UserInfo objects.
It would most probably be much faster to compare the properties directly or with a override of ==. This is how you override ==
def ==(other)
return self.weight == other.weight && self.expire_date == other.expire_date
end
and the loop changes into this
arr.each {|c|
if h.has_key?(c.name)
if problems.has_key?(c.name)
problems[c.name]=problems[c.name]<<c
elsif h[c.name] != c
problems[c.name]=problems[c.name]<<c<<h[c.name]
h.delete(c.name)
end
else
h[c.name]=c
end
}
Finally you might want to convert the Hash back to an Array
result = h.values
Here's another potential way. If you have a way of identifying each UserInfo, say a to_str method that prints out the values:
def to_str()
return "#{#name}:#{#title}:#{#age}"
end
You can use inject and a hash
all_users = a + b # collection of users to "merge"
res = all_users.inject({})do |h,v|
h[v.to_str] = v #save the value indexed on the string output
h # return h for the next iteration
end
merged = res.values #the unique users

Ruby 1.9: turn these 4 arrays into hash of key/value pairs

I have four arrays that are coming in from the client. Let's say that there is an array of names, birth dates, favorite color and location. The idea is I want a hash later where each name will have a hash with respective attributes:
Example date coming from the client:
[name0, name1, name2, name3]
[loc0, loc1]
[favcololor0, favcolor1]
[bd0, bd1, bd2, bd3, bd4, bd5]
Output I'd like to achieve:
name0 => { location => loc0, favcolor => favcolor0, bd => bd0 }
name1 => { location => loc1, favcolor => favcolor1, bd => bd1 }
name2 => { location => nil, favcolor => nil, bd => bd2 }
name3 => { location => nil, favcolor => nil, bd => bd3 }
I want to have an array at the end of the day where I can iterate and work on each particular person hash.
There need not be an equivalent number of values in each array. Meaning, names are required.. and I might receive 5 of them, but I only might receive 3 birth dates, 2 favorite colors and 1 location. Every missing value will result in a nil.
How does one make that kind of data structure with Ruby 1.9?
I would probably do it like this
# assuming names, fav_colors, birth_dates, and locations are your arrays
name_collection = {}
names.zip(birth_dates, fav_colors, locations) do |name, birth_date, fav_color, location|
name_collection[name] = { :birth_date => birth_date,
:fav_color => fav_color,
:location => location }
end
# usage
puts name_collection['jack'][:fav_color] # => 'blue'
A small class to represent a person
class Person
attr_accessor :name, :color, :loc, :bd
def initialize(args = {})
#name = args[:name]
#color = args[:color]
#loc = args[:loc]
#bd = args[:bd]
end
def pp()
puts "*********"
puts "Name: #{#name}"
puts "Location: #{#loc}"
puts "Birthday: #{#bd}"
puts "Fav. Color: #{#color}"
puts "*********"
end
end
another to represent people, which is mainly just a listing of Persons.
class People
attr_accessor :list_of_people
def initialize()
#list_of_people = {}
end
def load_people(names, locations, favcolors, birthdates)
names.each_with_index do |name, index|
#list_of_people[name] = Person.new(:name => name, :color => favcolors[index], :loc => locations[index], :bd => birthdates[index])
end
end
def pp()
#list_of_people.each_pair do |key, value|
value.pp()
end
end
end
I threw in a pretty print function for each so you can see their data. With a starting point like this it will be really easy to modify and add methods that do all sorts of useful things.
if __FILE__ == $0
names = ["name0", "name1", "name2", "name3"]
locs = ["loc0","loc1"]
favcolors = ["favcolor0", "favcolor1"]
bds = ["bd0","bd1","bd2","bd3","bd4"]
a = People.new()
a.load_people(names,locs,favcolors,bds)
a.pp()
end
I think the kind of data structure you're looking for is -ahem- a Struct.
# setup data
names = %w(name0 name1 name2 name3)
locations = %w(loc0 loc1)
colors = %w(favcololor0 favcolor1)
bd = %w(bd0 bd1 bd2 bd3 bd4 bd5)
# let's go
Person = Struct.new( :name, :location, :fav_color, :bd )
all_persons = names.zip( locations, colors, bd ).map{|p| Person.new( *p)}
# done
puts all_persons
someone= all_persons.find{|p| p.name == "name1"}
puts someone.location unless someone.nil?

Resources