help sorting objects by their instance field

A

Aaron Haas

I'm trying to figure out how to sort objects in an array by one of their
instance
variables, in this case last name. The person class has a first name,
last name, and an address object. The address book class is made up of
an array of persons. How do I sort the array of persons in
alphabetically by the persons last name instance fields. I added a
getLastname method to the persons class, but I'm not sure on the logic.

Code:
class Person
attr_accessor :fname, :lname, :address

# initializer
def initialize
@fname = @lname = ""
# since address is an object
@address = Address.new
end

# assign given values to Person object
def crPerson (aFname, aLname, aAddress)
@fname = aFname
@lname = aLname
@address = aAddress
end

def get_lname
return @lname
end

# string representation of Person
# note the syntax: address.to_s (to call the to_s instance method
of Address class)
def to_s
@fname + " " + @lname + "\n" + "\n" + address.to_s
end

end

Code:
class AddressBook

# class variable. keeps track of number of address book entries
@@instances = 0

# no attr_accessor
# don't want the user to have direct access to @persons
def initialize
@persons = [] # starts with empty array
end

# add person to address book
def add(person)
@persons += [person]
@@instances += 1
end

# delete person from address book
def remove(person)
@persons.delete(person)
@@instances -= 1
end
#  totally lost on this part
# print sorted by last name method using person's get_lname method
def print_addresses
@persons.each { |p| persons[p].get_lname.sort  }
end # close while loop
# print sorted array
@persons.each { |p| yield p }
end

end
 
N

Nathan Clark

Check the docs for class Array
http://ruby-doc.org/core/classes/Array.html#M002185

You can pass a block to array.sort, that tells it what to sort by (in
your case, person.lname).

I'm trying to figure out how to sort objects in an array by one of their
instance
variables, in this case last name. The person class has a first name,
last name, and an address object. The address book class is made up of
an array of persons. How do I sort the array of persons in
alphabetically by the persons last name instance fields. I added a
getLastname method to the persons class, but I'm not sure on the logic.

Code:
class Person
=C2=A0 =C2=A0attr_accessor :fname, :lname, :address

=C2=A0 =C2=A0# initializer
=C2=A0 =C2=A0def initialize
=C2=A0 =C2=A0 =C2=A0 =C2=A0@fname =3D @lname =3D ""
=C2=A0 =C2=A0 =C2=A0 =C2=A0# since address is an object
=C2=A0 =C2=A0 =C2=A0 =C2=A0@address =3D Address.new
=C2=A0 =C2=A0end

=C2=A0 =C2=A0# assign given values to Person object
=C2=A0 =C2=A0def crPerson (aFname, aLname, aAddress)
=C2=A0 =C2=A0 =C2=A0 =C2=A0@fname =3D aFname
=C2=A0 =C2=A0 =C2=A0 =C2=A0@lname =3D aLname
=C2=A0 =C2=A0 =C2=A0 =C2=A0@address =3D aAddress
=C2=A0 =C2=A0end

=C2=A0 =C2=A0def get_lname
=C2=A0 =C2=A0 =C2=A0 =C2=A0return @lname
=C2=A0 =C2=A0end

=C2=A0 =C2=A0 # string representation of Person
=C2=A0 =C2=A0 # note the syntax: address.to_s (to call the to_s instance = method
of Address class)
=C2=A0 =C2=A0def to_s
=C2=A0 =C2=A0 =C2=A0 =C2=A0@fname + " " + @lname + "\n" + "\n" + address.= to_s
=C2=A0 =C2=A0end

end

Code:
class AddressBook

=C2=A0 =C2=A0# class variable. keeps track of number of address book entr= ies
=C2=A0 =C2=A0@@instances =3D 0

=C2=A0 =C2=A0# no attr_accessor
=C2=A0 =C2=A0# don't want the user to have direct access to @persons
=C2=A0 =C2=A0def initialize
=C2=A0 =C2=A0 =C2=A0 =C2=A0@persons =3D [] # starts with empty array
=C2=A0 =C2=A0end

=C2=A0 =C2=A0# add person to address book
=C2=A0 =C2=A0def add(person)
=C2=A0 =C2=A0 =C2=A0 =C2=A0@persons +=3D [person]
=C2=A0 =C2=A0 =C2=A0 =C2=A0@@instances +=3D 1
=C2=A0 =C2=A0end

=C2=A0 =C2=A0# delete person from address book
=C2=A0 =C2=A0def remove(person)
=C2=A0 =C2=A0 =C2=A0 [email protected](person)
=C2=A0 =C2=A0 =C2=A0 =C2=A0@@instances -=3D 1
=C2=A0 =C2=A0end
=C2=A0 =C2=A0 # =C2=A0totally lost on this part
=C2=A0 =C2=A0# print sorted by last name method using person's get_lname = method
=C2=A0 =C2=A0def print_addresses
=C2=A0 =C2=A0 [email protected] { |p| persons[p].get_lname.sort =C2=A0}
=C2=A0 =C2=A0 =C2=A0end # close while loop
=C2=A0 =C2=A0 =C2=A0# print sorted array
=C2=A0 =C2=A0 [email protected] { |p| yield p }
=C2=A0 =C2=A0end

end
 
J

Jeremy Bopp

I'm trying to figure out how to sort objects in an array by one of their
instance
variables, in this case last name. The person class has a first name,
last name, and an address object. The address book class is made up of
an array of persons. How do I sort the array of persons in
alphabetically by the persons last name instance fields. I added a
getLastname method to the persons class, but I'm not sure on the logic.

Code:
class Person
attr_accessor :fname, :lname, :address

# initializer
def initialize
@fname = @lname = ""
# since address is an object
@address = Address.new
end

# assign given values to Person object
def crPerson (aFname, aLname, aAddress)
@fname = aFname
@lname = aLname
@address = aAddress
end

def get_lname
return @lname
end[/QUOTE]

First of all, you don't need the get_lname method.  Use Person#lname
instead which was created when you called attr_accessor above.  It's
equivalent.
[QUOTE]
# string representation of Person
# note the syntax: address.to_s (to call the to_s instance method
of Address class)
def to_s
@fname + " " + @lname + "\n" + "\n" + address.to_s
end

end

Code:
class AddressBook

# class variable. keeps track of number of address book entries
@@instances = 0[/QUOTE]

FYI, you don't need this.  The @persons array knows how many entries it
has automatically.  Just call @persons.size when you want to know how
many entries there are. :-)

You should usually avoid class variables in Ruby in my experience in any
case.  There are times they can make sense, but what would happen here
if you had more than a single instance of AddressBook in your program?
[QUOTE]
# no attr_accessor
# don't want the user to have direct access to @persons
def initialize
@persons = [] # starts with empty array
end

# add person to address book
def add(person)
@persons += [person]
@@instances += 1
end

# delete person from address book
def remove(person)
@persons.delete(person)
@@instances -= 1
end
#  totally lost on this part
# print sorted by last name method using person's get_lname method
def print_addresses
@persons.each { |p| persons[p].get_lname.sort  }
end # close while loop
# print sorted array
@persons.each { |p| yield p }
end

end

You need to call the sort method on the @persons array rather than the
string you get from Person#get_lname. There is good documentation for
Array#sort and for the closely related sort_by method mixed in from the
Enumerable module:

http://rdoc.info/docs/ruby-core/1.9.2/Array#sort-instance_method
http://rdoc.info/docs/ruby-core/1.9.2/Enumerable#sort_by-instance_method

For cases such as learning details of sorting, I have usually found it
much better to make a quick script or irb session that pointedly tries
to do what I want to do. In this case, I would create a very basic
array of strings first and figure out how to sort that. Then I would
apply it to your larger program.

-Jeremy
 
A

Ammar Ali

I'm trying to figure out how to sort objects in an array by one of their
instance
variables, in this case last name. The person class has a first name,
last name, and an address object. The address book class is made up of
an array of persons. How do I sort the array of persons in
alphabetically by the persons last name instance fields. I added a
getLastname method to the persons class, but I'm not sure on the logic.

Code:
class Person
=C2=A0 =C2=A0attr_accessor :fname, :lname, :address

=C2=A0 =C2=A0# initializer
=C2=A0 =C2=A0def initialize
=C2=A0 =C2=A0 =C2=A0 =C2=A0@fname =3D @lname =3D ""
=C2=A0 =C2=A0 =C2=A0 =C2=A0# since address is an object
=C2=A0 =C2=A0 =C2=A0 =C2=A0@address =3D Address.new
=C2=A0 =C2=A0end

=C2=A0 =C2=A0# assign given values to Person object
=C2=A0 =C2=A0def crPerson (aFname, aLname, aAddress)
=C2=A0 =C2=A0 =C2=A0 =C2=A0@fname =3D aFname
=C2=A0 =C2=A0 =C2=A0 =C2=A0@lname =3D aLname
=C2=A0 =C2=A0 =C2=A0 =C2=A0@address =3D aAddress
=C2=A0 =C2=A0end[/QUOTE]

To answer your main question first:

@persons.sort_by {|person| person.aLname}

Some notes:

Do you need to create blank Person objects? If not, you do the
initialization work of crPerson inside the initialize method.

def initialize(first_name, last_name, address)
@fname =3D aFname
@lname =3D aLname
@address =3D aAddress
end
[QUOTE]
=C2=A0 =C2=A0def get_lname
=C2=A0 =C2=A0 =C2=A0 =C2=A0return @lname
=C2=A0 =C2=A0end[/QUOTE]

If you use the attr_reader, attr_writer, and attr_accessor keywords,
you can do away with getter/setter methods. Example

class Person
attr_accessor :first_name, :last_name
end

Now you can just write:

p =3D Person.new('Joe', 'Schmoe', '123 Main Street')
p.last_name # gets last name
p.first_name # gets first name

THe same applies for setting values:
p.last_name =3D 'Bloe'


[QUOTE]
=C2=A0 =C2=A0# class variable. keeps track of number of address book entr= ies
=C2=A0 =C2=A0@@instances =3D 0[/QUOTE]

You can accomplish this with:

@persons.length

Class variables in ruby are rarely used, or what one thinks they need.

[QUOTE]
=C2=A0 =C2=A0 # =C2=A0totally lost on this part
=C2=A0 =C2=A0# print sorted by last name method using person's get_lname = method
=C2=A0 =C2=A0def print_addresses
=C2=A0 =C2=A0 [email protected] { |p| persons[p].get_lname.sort =C2=A0}[/QUOTE]

First the sort is being applied to the last name. I doubt that's what
you want. Also, you don't need to index into the persons array here,
the p is the person you want already. This is the same:

@persons.each { |p| persons.last_name =C2=A0}

I already addressed the sorting at the top of the post.
[QUOTE]
=C2=A0 =C2=A0 =C2=A0end # close while loop
=C2=A0 =C2=A0 =C2=A0# print sorted array
=C2=A0 =C2=A0 [email protected] { |p| yield p }
=C2=A0 =C2=A0end[/QUOTE]

This doesn't print the persons, it just yields them. This does:

@persons.each {|p| puts p}

If you meant to use yield, then the printing is done in a block passed
to AddressBook class print_addresses method, so you need to add a
block argument and yield to it.

def print_addresses(&block)
@persons.sort_by { |p| persons.last_name  }
@persons.each { |p| yield p }
end

Then you use it like:

address_book.print_addresses {|p| puts p}

Note that I have used the common ruby naming convention for variables
and methos, last_name, instead of aLastName.

HTH,
Ammar
 
A

Aaron Haas

Thank you guys so much for your helpful posts. I just started learning
ruby this week and it has been slow going.

I read the api on array's sort methods and I did not understand them.

Does this line sort the array of persons?

@persons.sort_by {|person| person.aLname}

do I need to save it to a new array variable?

order_array = @persons.sort_by {|person| person.aLname}
 
A

Ammar Ali

Thank you guys so much for your helpful posts. I just started learning
ruby this week and it has been slow going.

=C2=A0I read the api on array's sort methods and I did not understand the= m.

Does this line sort the array of persons?

@persons.sort_by {|person| person.aLname}

do I need to save it to a new array variable?

order_array =3D @persons.sort_by {|person| person.aLname}

It sorts the array itself, no need to make a copy.

The documentation for sort_by is in the Enumerable mixin (which Array
includes) It's at:

http://ruby-doc.org/core/classes/Enumerable.html#M003120

Regards,
Ammar
 
J

Jeremy Bopp

Thank you guys so much for your helpful posts. I just started learning
ruby this week and it has been slow going.

I read the api on array's sort methods and I did not understand them.

Well, just keep in mind that the sort methods belong to Array, so you
have to call them on an array rather than an element of an array. :)
Does this line sort the array of persons?

@persons.sort_by {|person| person.aLname}

It should sort by the aLname property of each array element, but give it
a try and see for yourself. You should get familiar with irb for simple
things (like trying out array sorting), and don't fear running your code
to see what it does.
do I need to save it to a new array variable?

order_array = @persons.sort_by {|person| person.aLname}

It depends on how you want to use it. If all you want to do is print
the sorted list, you could just skip the assignment and immediately call
#each on the array returned by #sort_by:

@persons.sort_by {|person| person.aLname}.each |person|
puts person.aLname
end


If you need to use the same sorted list again, it might be a good idea
to store it somewhere before using it further, but in this case it
doesn't appear necessary.

-Jeremy
 
A

Aaron Haas

Good morning all and thanks again for the help.

I re-did the print method as you suggested but it is not sorting by the
persons last name. It is just printing in the order in which they were
added to array.
Here is my print method

# sort and print addresses in alphabetical order
def print_addresses
@persons.sort_by {|person| person.lname} # sorts list by lname
@persons.each { |p| puts p } # prints address book

end

For a good Saturday morning laugh I included my query method. It takes
a name and returns any matches found. It actually works fine!!! But I'm
sure it could be written in 2 or 3 lines rather than the 13 lines it
took me. I wrote this before your help yesterday so I still made use of
my get method.

# query by name
# method is case sensitive
# prints each name that matched search parameter
def query(name)

i = 0
match_counter = 0
# iterate over the entire address book
while (i < @persons.length)
# set names to match with parameter name
first_name = @persons.get_fname
last_name = @persons.get_lname
whole_name = first_name + " " + last_name
# if name matched print it.
if (last_name.eql?(name)) || (first_name.eql?(name)) ||
(whole_name.eql?(name)) then
puts @persons.to_s
match_counter += 1
end
i += 1
end
# tell how many matches found
puts "Found #{match_counter} matches for that name."
end
 
A

Ammar Ali

Hmmm. for some reason my reply never made it.

Remember, I gave incorrect information, so the sort is not "saved" in
the array, so you need to make a copy:

def print_addresses
=C2=A0sorted_persons =3D @persons.sort_by {|person| person.lname}
=C2=A0sorted_persons.each { |p| puts p }
end

Or, you can do it all on one line:

@persons.sort_by {|person| person.lname}.each {|p| puts p}

Regards,
Ammar
 
A

Aaron Haas

Nathan Clark wrote in post #961001:
Check the docs for class Array
http://ruby-doc.org/core/classes/Array.html#M002185

You can pass a block to array.sort, that tells it what to sort by (in
your case, person.lname).

Is this what you mean? I also tried this and I'm getting the following
error

def print_addresses(&block)
@persons.sort_by { |p| persons.lname }
@persons.each { |p| yield p }
end

error...



Ruby2.rb:113:in `print_addresses': undefined local variable or method
`persons' for #<AddressBook:0x10014c750> (NameError)
from COMP205_8-1Ruby2.rb:222:in `sort_by'
from COMP205_8-1Ruby2.rb:113:in `each'
from COMP205_8-1Ruby2.rb:113:in `sort_by'
from COMP205_8-1Ruby2.rb:113:in `print_addresses'
from COMP205_8-1Ruby2.rb:219
 
J

Jesús Gabriel y Galán

Nathan Clark wrote in post #961001:

Is this what you mean? =A0I also tried this and I'm getting the following
error

def print_addresses(&block)
=A0 =A0 =A0 =A0 @persons.sort_by { |p| persons.lname =A0}
=A0 =A0 =A0 =A0 @persons.each { |p| yield p }
=A0 =A0 =A0 end

persons is undefined here, you have to use p (btw, I'd use person
instead). But I think what Nathan means is what was already said:


def print_addresses
@persons.sort_by {|person| person.lname}.each {|person| puts person}
end

He was talking about the block passed to the Array#sort_by method.

Jesus.
 
M

Michael Bostler

My attempt at refactoring the query method would leverage array#select.
I'd probably do something like:

def query(name)
matches = @persons.select do |person|
full_name = [person.first_name, person.last_name].join(' ')
full_name =~ Regexp.new("\\b#{name}\\b")
end

puts "Found #{matches.size} matches for that name."
end

Basically, if the last line of the select block returns false or nil,
that element will not be returned. In this case, the last line in
the block converts the "name" parameter into a regular expression and
encloses it in word boundary delimiters (so that searches with, for
example, "ti" will not return people named "tim").

The =~ method will return the index of the first match in the full_name
string, or nil if no match is found. In other words, =~ only returns
non-nil/non-false on matches, and the select method will pick those
matches
up
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,769
Messages
2,569,582
Members
45,071
Latest member
MetabolicSolutionsKeto

Latest Threads

Top