Help sorting objects by their instance field

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.

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
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

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).

On 11/12/2010 9:43 AM, Aaron H. wrote:

attr_accessor :fname, :lname, :address
    @fname = aFname
    @lname = aLname
    @address = aAddress
end

def get_lname
    return @lname
end

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.

[CODE]
class AddressBook

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

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. :slight_smile:

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?

end
  end # close while loop
  # print sorted array
  @persons.each { |p| yield p }
end

end
[/CODE]

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

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}

On Fri, Nov 12, 2010 at 6:54 PM, Aaron H. [email protected] wrote:

order_array = @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:

module Enumerable - RDoc Documentation

Regards,
Ammar

On Fri, Nov 12, 2010 at 5:43 PM, Aaron H. [email protected] wrote:

attr_accessor :fname, :lname, :address
@fname = aFname
@lname = aLname
@address = aAddress
end

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 = aFname
@lname = aLname
@address = aAddress
end

def get_lname
return @lname
end

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 = 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 = ‘Bloe’

class variable. keeps track of number of address book entries

@@instances = 0

You can accomplish this with:

@persons.length

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

#  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 }

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 }

I already addressed the sorting at the top of the post.

 end # close while loop
 # print sorted array
 @persons.each { |p| yield p }

end

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

On Fri, Nov 12, 2010 at 7:12 PM, Ammar A. [email protected]
wrote:

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

Sorry. I gave you wrong information. Probably need more coffee.

a = [[1,3], [3,2], [5,1]]
=> [[1, 3], [3, 2], [5, 1]]
a.sort_by {|e| e[1]}
=> [[5, 1], [3, 2], [1, 3]]
a
=> [[1, 3], [3, 2], [5, 1]]

Regards,
Ammar

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[i].get_fname
         last_name = @persons[i].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[i].to_s
match_counter += 1
end
i += 1
end
# tell how many matches found
puts “Found #{match_counter} matches for that name.”
end

On 11/12/2010 10:54 AM, Aaron H. wrote:

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. :slight_smile:

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

Nathan Clark wrote in post #961001:

Check the docs for class Array
class Array - RDoc Documentation

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

On Sat, Nov 13, 2010 at 11:18 PM, Aaron H. [email protected]
wrote:

def print_addresses(&block)
@persons.sort_by { |p| persons.lname }
@persons.each { |p| yield p }
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.

That works!!! Thanks!!!

Hmmm. for some reason my reply never made it.

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