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
Related
Is there an alternative to iteration or a condition I can make that will allow me to interpolate the values of just one hash? I've inspected the results of my input and apparently the array that i'm saving my hashes into disappears when there is only one hash. I've also tested the results and they're of Nil class.
def print_with_index(students)
students.each_with_index do |student, index|
index_plus_one = index + 1
puts "#{index_plus_one}. #{students[:name]} (#{students[:cohort]} cohort)"
end
end
How do i solve my problem and also why do hashes behave this way?
Full code:
def print_header
puts "The students of Villains Academy"
puts "--------------"
end
def print_footer(names)
puts "Overall, we have #{names.count} great students"
end
def input_students
puts "Please enter the names and then the cohort of the students"
puts "To finish, just hit return twice"
#created an empty array
students = []
#getting the first name
name = gets.chomp
cohort = gets.chomp.to_sym
if cohort.empty?
cohort = :november
end
if cohort !~ /january|february|march|april|may|june|july|august|september|october|november|december/
puts "Please enter a valid month"
puts "Warning months are case sensitive. Please enter in lowercase characters."
cohort = gets.chomp.to_sym
end
while !name.empty? do
# add the student hash to the array called students
students << {name: name, cohort: cohort}
if students.count > 1
puts "Now we have #{students.count} students"
else students.count == 1
puts "Now we have #{students.count} student"
end
#getting another name from the user
name = gets.chomp
cohort = gets.chomp.to_sym
if cohort.empty?
cohort = :november
end
if cohort !~ /january|february|march|april|may|june|july|august|september|october|november|december/
puts "Please enter a valid month"
puts "Warning months are case sensitive. Please enter in lowercase characters."
cohort = gets.chomp.to_sym
end
end
bycohort = students.sort_by { |v| v[:cohort] }
filter = students.select! { |student| student[:cohort] == :november }
puts bycohort #This allows me to see all of my hashes before they are filtered
puts ""
bycohort
filter
end
def print_with_index(students)
students.each_with_index do |students, index|
index_plus_one = index + 1
puts "#{index_plus_one}. #{students[:name]} (#{students[:cohort]} cohort)"
end
end
### body ###
students = input_students
print_header
print_with_index(students)
print_footer(students)
this works for me though, i think with each_with_index enum, you have to pass in an array of hashes i.e. [{...}, {...}, {...}], not a single hash with multiple keys-values
def print_with_index(students)
students.each_with_index do |students, index|
index_plus_one = index + 1
puts "#{index_plus_one}. #{students[:name]} (#{students[:cohort]} cohort)"
end
end
print_with_index([{name:"b", cohort: "c"}])
# 1. b (c cohort)
You should use student instead of students as the block parameter. You can check if the student object is nil.
def print_with_index(students)
students.each_with_index do |student, index|
if !student.nil?
index_plus_one = index + 1
puts "#{index_plus_one}. #{student[:name]} (#{student[:cohort]} cohort)"
end
end
end
I am trying to get a default value whilst using hashes in ruby. Looking up the documentation you use a fetch method. So if a hash is not entered then it defaults to a value. This is my code.
def input_students
puts "Please enter the names and hobbies of the students plus country of birth"
puts "To finish, just hit return three times"
#create the empty array
students = []
hobbies = []
country = []
cohort = []
# Get the first name
name = gets.chomp
hobbies = gets.chomp
country = gets.chomp
cohort = gets.chomp
while !name.empty? && !hobbies.empty? && !country.empty? && cohort.fetch(:cohort, january) do #This is to do with entering twice
students << {name: name, hobbies: hobbies, country: country, cohort: cohort} #import part of the code.
puts "Now we have #{students.count} students"
# get another name from the user
name = gets.chomp
hobbies = gets.chomp
country = gets.chomp
cohort = gets.chomp
end
students
end
You just need to give fetch a default it can handle. It doesn't know what to do with january as you haven't declared any variable with that name. If you want to set the default value to the string "january", then you just need to quote it like this:
cohort.fetch(:cohort, "january")
There are some decent examples in the documentation for fetch.
Also, cohort isn't a Hash, it's a String since gets.chomp returns a String. fetch is for "fetching" values from a Hash. The way you're using it should be throwing an error similar to: undefined method 'fetch' for "whatever text you entered":String.
Finally, since you're using it in a conditional, the result of your call to fetch is being evaluated for its truthiness. If you're setting a default, it will always be evaluated as true.
If you just want to set a default for cohort if it's empty, you can just do something like this:
cohort = gets.chomp
cohort = "january" if cohort.empty?
while !name.empty? && !hobbies.empty? && !country.empty?
students << {
name: name,
hobbies: hobbies,
country: country,
cohort: cohort
}
... # do more stuff
Hope that's helpful.
You have several options. #dinjas mentions one, likely the one you want to use. Suppose your hash is
h = { :a=>1 }
Then
h[:a] #=> 1
h[:b] #=> nil
Let's say the default is 4. Then as dinjas suggests, you can write
h.fetch(:a, 4) #=> 1
h.fetch(:b, 4) #=> 4
But other options are
h.fetch(:a) rescue 4 #=> 1
h.fetch(:b) rescue 4 #=> 4
or
h[:a] || 4 #=> 1
h[:b] || 4 #=> 4
You could also build the default into the hash itself, by using Hash#default=:
h.default = 4
h[:a] #=> 1
h[:b] #=> 4
or by defining the hash like so:
g = Hash.new(4).merge(h)
g[:a] #=> 1
g[:b] #=> 4
See Hash::new.
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}
I've been looking for an answer to this question for a while, but I haven't been able to find one that I was able to understand and apply.
I have a class that contains three instance variables: #brand, #setup, and #year. I have a module, included in that class, that has three methods: print_brand(), print_setup(), and print_year() that simply print the value assigned to the associated variable.
I'd like to get two strings from a user and use the first as an object name and the second as a method name. Here's what I have right now:
class Bike
include(Printers)
def initialize(name, options = {})
#name = name
#brand = options[:brand]
#setup = options[:setup]
#year = options[:year]
end
end
trance = Bike.new("trance x3", {
:brand => "giant",
:setup => "full sus",
:year => 2011
}
)
giro = Bike.new("giro", {
:brand => "bianchi",
:setup => "road",
:year => 2006
}
)
b2 = Bike.new("b2", {
:brand => "felt",
:setup => "tri",
:year => 2009
}
)
puts "Which bike do you want information on?"
b = gets()
b.chomp!
puts "What information are you looking for?"
i = gets()
i.chomp!
b.send(i)
I'm missing some functionality that converts b from a string to an object name. For example, I want the user to be able to type in "trance" and then "print_year" and have "2011" printed on the screen. I tried to use constantize on b, but that doesn't seem to work. I get the error:
in 'const_defined?': wrong constant name trance (NameError)
Any other ideas?
You should store the object in a hashmap with key = name and value = object, then use b (name) to retrieve the right object from the hashmap. I'm still not sure what do you want to do with the second input, by my guess is that this answer covers that as well.
h = Hash.new()
h["trance x3"] = trance
h["giro"] = giro
...
puts "Which bike do you want information on?"
b = gets()
b.chomp!
user_bike = h[b]
puts "What information are you looking for?"
i = gets()
i.chomp!
user_bike.send(i)
I would use eval:
eval "#{ b }.#{ i }"
I suppose you have to add accessors:
attr_accessor :brand, :setup, :year
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