11. References and Records

Introduction

Taking References to Arrays

Making Hashes of Arrays

# In Ruby, everything is an object (i.e. reference), including arrays.
# Arrays are, thus, directly supported as values of a hash.
#-----------------------------
# Assumes an array was already put into hash["KEYNAME"], e.g.:
# hash = {}
# hash["KEYNAME"] = [5, 10, 15]
hash["KEYNAME"] << "new value"
#-----------------------------
# Join is used to separate elements of the array with commas.
hash.each { |key, value|
    puts "#{key}: #{value.join(', ')}"
}
#-----------------------------
hash["a key"] = [3, 4, 5]
#-----------------------------
values = hash["a key"]
#-----------------------------
hash["a key"] << value
#-----------------------------
# Example hash:
# phone2name = { 123555888 => ["Joe", "Jenny"], 888555444 => ["Anne", "Mark"] }
residents = phone2name[number]
#-----------------------------
residents = phone2name.has_key?(number) ? phone2name[number] : []
#-----------------------------

Taking References to Hashes

Taking References to Functions

Taking References to Scalars

Creating Arrays of Scalar References

Using Closures Instead of Objects

def mkcounter(count)
    start  = count 
    bundle = { 
        "NEXT"   => proc { count += 1 },
        "PREV"   => proc { count -= 1 },
        "RESET"  => proc { count = start }
    }
    bundle["LAST"] = bundle["PREV"]
    return bundle
end

c1 = mkcounter(20)
c2 = mkcounter(77)

puts "next c1: #{c1["NEXT"].call}"  # 21 
puts "next c2: #{c2["NEXT"].call}"  # 78 
puts "next c1: #{c1["NEXT"].call}"  # 22 
puts "last c1: #{c1["PREV"].call}"  # 21 
puts "last c1: #{c1["LAST"].call}"  # 20 
puts "old  c2: #{c2["RESET"].call}" # 77 

Creating References to Methods

Constructing Records

#-----------------------------
record = {
    :NAME   => "Jason",
    :EMPNO  => 132,
    :TITLE  => "deputy peon",
    :AGE    => 23,
    :SALARY => 37.000,
    :PALS   => [ "Norbert", "Rhys", "Phineas"]
}

puts "I am #{record[:NAME]}, and my pals are #{record[:PALS].join(', ')}."
#-----------------------------
# store record
byname = {}
byname[record[:NAME]] = record

# later on, look up by name
rp = byname["Aron"]
puts "Aron is employee #{rp[:EMPNO]}." if rp

# give jason a new pal
byname["Jason"][:PALS] << "Theodore"
puts "Jason now has #{byname["Jason"][:PALS].length} pals"
#-----------------------------
# Go through all records
byname.each do |name, record|
    puts "#{name} is employee number #{record[:EMPNO]}"
end    
#-----------------------------
# store record
employees = {}
employees[record[:EMPNO]] = record

# lookup by id
rp = employees[132]
puts "employee number 132 is #{rp[:NAME]}" if rp
#-----------------------------
byname["Jason"][:SALARY] *= 1.035
#-----------------------------
peons = employees.values.select { |record| record[:TITLE] =~ /peon/i }
tsevens = employees.values.select { |record| record[:AGE] == 27 }
#-----------------------------
# Go through all records
byname.values.sort { |a, b| a[:AGE] <=> b[:AGE] }.each do |rp|
        puts "#{rp[:NAME]} is age #{rp[:AGE]}."
end
#-----------------------------
# A little different then Perl: we can use hash in Ruby.
# use @byage, _a hash_ of arrays of records
byage = {}
byage[record[:AGE]] = [] if byage[record[:AGE]] == nil
byage[record[:AGE]] << record
#-----------------------------
byage.each do |age, rps|
    print "Age #{age}: "
    rps.each do |rp|
        print rp[:NAME], " "
    end
    print "\n"
end
#-----------------------------
byage.each do |age, rps|
    puts "Age #{age}: #{byage[age].collect { |e| e[:NAME] }.join(', ')}"
end
#-----------------------------

Reading and Writing Hash Records to Text Files

Printing Data Structures

Copying Data Structures

Storing Data Structures to Disk

Transparently Persistent Data Structures

Program: Binary Trees

class Binary_tree
    def initialize(val)
        @value = val
        @left = nil
        @right = nil
    end
    
    # insert given value into proper point of
    # provided tree.  If no tree provided, 
    # use implicit pass by reference aspect of @_
    # to fill one in for our caller.
    def insert(val)
        if val < @value then
            if @left then
                @left.insert(val)
            else
                @left = Binary_tree.new(val)
            end
        elsif val > @value then
            if @right then
                @right.insert(val)
            else
                @right = Binary_tree.new(val)
            end
        else
            puts "double"
            # do nothing, no double values
        end
    end

    # recurse on left child, 
    # then show current value, 
    # then recurse on right child.  
    def in_order
        @left.in_order if @left
        print @value, " "
        @right.in_order if @right
    end
    
    # show current value, 
    # then recurse on left child, 
    # then recurse on right child.
    def pre_order
        print @value, " "
        @left.pre_order if @left
        @right.pre_order if @right
    end

    # recurse on left child, 
    # then recurse on right child,
    # then show current value.
    def post_order
        @left.post_order if @left
        @right.post_order if @right
        print @value, " "
    end

    # find out whether provided value is in the tree.
    # if so, return the node at which the value was found.
    # cut down search time by only looking in the correct
    # branch, based on current value.
    def search(val)
        if val == @value then
            return self
        elsif val < @value then
            return @left.search(val) if @left
            return nil
        else
            return @right.search(val) if @right
            return nil
        end
    end
end

# first generate 20 random inserts
test = Binary_tree.new(0)
for a in 0..20
    test.insert(rand(1000)) 
end

# now dump out the tree all three ways
print "Pre order:  ";  test.pre_order;  puts ""
print "In order:  ";  test.in_order;  puts ""
print "Post order:  ";  test.post_order;  puts ""

print "search?"
while gets
    print test.search($_.to_i)
    print "\nsearch?"
end