Find max value in a class in ruby - 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}

Related

Can I improve this code using advance ruby concepts and I also wanted to print the list with item values?

Write a simple DSL for creating a shopping list. We should be able to specify the item name and quantity..
Something like.
My code works for pre-defined hash map, but once I take user input for creating a hash map it fails and also I want to improve my code by using more advanced concepts to achieve this result. Any suggestions ?
class Store
def initialize(item, quantity)
#products = { "item" => quantity }
#cart = []
end
def add_to_cart( item )
#cart << item
end
def add_product( item, price )
#products[item] = price
end
def cart_total
#cart.inject(0){|sum, item| sum + #products[item]}
end
def items
#products.join(', ')
end
end
puts "Please provide item name"
item = gets.chomp
puts "Please provide quantity associated with item"
quantity = gets.chomp.to_i
store = Store.new(item, quantity)
store.add_to_cart(item)
puts store.cart
printf "$%6.2f", store.cart_total
Expected Result:
s1.list #Should print complete list of the item and values added
sl.total # Should list the total price value for shopping done.
It's a quite broad question and it shouldn't, but I'd like to give my view on this anyway.
Initialize empty Hash with default values (Hash#new)
Don't forget attr_reader for accessing instance variable (Module#attr_reader)
Check the docs for the method used here (Hash, Array, Enumerable)
Here is a possible refactor:
class Store
attr_reader :cart, :products # you need to access the variables
def initialize
#cart = Hash.new(0) # initilize the Hash with default value
#products = Hash.new
end
def add_to_cart(item, quantity)
# increase by quantity, thanks to default value of the Hash,
# only if the product is available
if products.has_key? item # products thanks to the attr_reader, then see Hash docs
cart[item] += quantity
true
else
false
end
end
def add_product(item, price)
products[item] = price
end
def cart_total
cart.sum { |k, v| products[k] * v }
end
def products_list
products.keys
end
end
So, you can use this class as follows:
store = Store.new
store.add_product("Apple", 10.0 )
store.add_product("Lemon", 12.0 )
store.add_product("Melon", 20.0 )
store.products_list
#=> ["Apple", "Lemon", "Melon"]
store.products
#=> {"Apple"=>10.0, "Lemon"=>12.0, "Melon"=>20.0}
store.add_to_cart('Apple', 4) #=> true
store.add_to_cart('Melon', 5) #=> true
store.add_to_cart('Carrot', 1) #=> false # not available, not added
store.cart # thanks to attr_reader
#=> {"Apple"=>4, "Melon"=>2}
store.cart_total
#=> 140.0
You could think also to define a Cart class...

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

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

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

reset a count in ruby when putting to screen from parallel arrays

This is home work so I would prefer not to put up my code. I have 2 parallel arrays, 1.names 2. ages. The idea is to puts all ages less than 21. I can do this. The problem is that when I puts "#{count}. #{names[count]}, #{ages[count]}" <---The beginning count prints out the index number or position of element in array. Obviously what I want is for it to start at 1. if there are three names...
name, age
name, age
name, age
NOT
5, name, age
6, name, age
I am using a while loop with an if statement. I don't need code, just would like some feedback to trigger more ideas. Thanks for your time, much appreciated.
names[name1, name2, name3]
ages[age1, age2, age3]
#view all younger than 21
count = 0
while count < names.length
if ages[count] < 21
puts "#{count}. #{names[count]}, #{ages[count]}" #works
end
count += 1
end
pause
You shouldn't have "parallel arrays" in the first place! Data that belongs together should be manipulated together, not separately.
Instead of something like
names = %w[john bob alice liz]
ages = [16, 22, 18, 23 ]
You could, for example, have a map (called Hash in Ruby):
people = { 'john' => 16, 'bob' => 22, 'alice' => 18, 'liz' => 23 }
Then you would have something like:
puts people.select {|_name, age| age > 21 }.map.
with_index(1) {|(name, age), i| "#{i}. #{name}, #{age}" }
# 1. bob, 22
# 2. liz, 23
If you have no control over the creation of those parallel arrays, then it is still better to convert them to a sensible data structure first, and avoid the pain of having to juggle them in your algorithm:
people = Hash[names.zip(ages)]
Even better yet: you should have Person objects. After all, Ruby is object-oriented, not array-oriented or hash-oriented:
class Person < Struct.new(:name, :age)
def to_s
"#{name}, #{age}"
end
def adult?
age > 21
end
end
people = [
Person.new('john', 16),
Person.new('bob', 22),
Person.new('alice', 18),
Person.new('liz', 23)]
puts people.select(&:adult?).map.with_index(1) {|p, i| "#{i}. #{p}" }
Again, if you don't have control of the creation of those two parallel arrays, you can still convert them first:
people = names.zip(ages).map {|name, age| Person.new(name, age) }

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

Resources