11. References and Records

Introduction

//----------------------------------------------------------------------------------
// In Groovy, most usages of names are references (there are some special
// rules for the map shorthand notation and builders).
// Objects are inherently anonymous, they don't know what names refer to them.
ref = 3       // points ref to an Integer object with value 3.
println ref   // prints the value that the name ref refers to.

myList = [3, 4, 5]       // myList is a name for this list
anotherRef = myList
myMap = ["How": "Now", "Brown": "Cow"] // myMap is a name for this map

anArray = [1, 2, 3] as int[] // creates an array of three references to Integer objects

list = [[]]  // a list containing an empty list
list[2] = 'Cat'
println list // => [[], null, "Cat"]
list[0][2] = 'Dog'
println list // => [[null, null, "Dog"], null, "Cat"]

a = [2, 1]
b = a  // b is a reference to the same thing as a
a.sort()
println b // => [1, 2]

nat = [ Name: "Leonhard Euler",
        Address: "1729 Ramanujan Lane\nMathworld, PI 31416",
        Birthday: 0x5bb5580
]
println nat
// =>["Address":"1729 Ramanujan Lane\nMathworld, PI 31416", "Name":"Leonhard Euler", "Birthday":96163200]
//----------------------------------------------------------------------------------

Taking References to Arrays

//----------------------------------------------------------------------------------
aref = myList
anonList = [1, 3, 5, 7, 9]
anonCopy = anonList
implicitCreation = [2, 4, 6, 8, 10]

anonList += 11
println anonList  // => [1, 3, 5, 7, 9, 11]

two = implicitCreation[0]
assert two == 2

//  To get the last index of a list, you can use size()
// but you never would
lastIdx = aref.size() - 1

// Normally, though, you'd use an index of -1 for the last
// element, -2 for the second last, etc.
println implicitCreation[-1]
//=> 10

// And if you were looping through (and not using a list closure operator)
(0..<aref.size()).each{ /* do something */ }

numItems = aref.size()

assert anArray instanceof int[]
assert anArray.class.isArray()
println anArray

myList.sort() // sort is in place.
myList += "an item" // append item

def createList() { return [] }
aref1 = createList()
aref2 = createList()
// aref1 and aref2 point to different lists.

println anonList[4] // refers to the 4th item in the list_ref list.

// The following two statements are equivalent and return up to 3 elements
// at indices 3, 4, and 5 (if they exist).
x = anonList[3..5]
x = anonList[(3..5).step(1)]

//   This will insert 3 elements, overwriting elements at indices 3,4, or 5 - if they exist.
anonList[3..5] = ["blackberry", "blueberry", "pumpkin"]

// non index-based looping
for (item in anonList) println item
anonList.each{ println it }

// index-based looping
(0..<anonList.size()).each{ idx -> println anonList[idx] }
for (idx in 0..<anonList.size()) println anonList[idx]
//----------------------------------------------------------------------------------

Making Hashes of Arrays

//----------------------------------------------------------------------------------
// Making Hashes of Arrays
hash = [:] // empty map
hash["KEYNAME"] = "new value"

hash.each{ key, value -> println key + ' ' + value }

hash["a key"] = [3, 4, 5]
values = hash["a key"]

hash["a key"] += 6
println hash
// => ["KEYNAME":"new value", "a key":[3, 4, 5, 6]]

// attempting to access a value for a key not in the map yields null
assert hash['unknown key'] == null
assert hash.get('unknown key', 45) == 45
println hash
// => ["unknown key":45, "KEYNAME":"new value", "a key":[3, 4, 5, 6]]
//----------------------------------------------------------------------------------

Taking References to Hashes

//----------------------------------------------------------------------------------
// Hashes are no different to other objects
myHash = [ key1:100, key2:200 ]
myHashCopy = myHash.clone()

value = myHash['key1']
value = myHash.'key1'
slice = myHash[1..3]
keys = myHash.keySet()

assert myHash instanceof Map

[myHash, hash].each{ m ->
    m.each{ k, v -> println "$k => $v"}
}
// =>
// key1 => 100
// key2 => 200
// unknown key => 45
// KEYNAME => new value
// a key => [3, 4, 5, 6]

values = ['key1','key2'].collect{ myHash[it] }
println values  // => [100, 200]

for (key in ["key1", "key2"]) {
    myHash[key] += 7
}
println myHash  // => ["key1":107, "key2":207]
//----------------------------------------------------------------------------------

Taking References to Functions

//----------------------------------------------------------------------------------
// you can use closures or the &method notation
def joy() { println 'joy' }
def sullen() { println 'sullen' }
angry = { println 'angry' }
commands = [happy: this.&joy,
            sad:   this.&sullen,
            done:  { System.exit(0) },
            mad:   angry
]

print "How are you?"
cmd = System.in.readLine()
if (cmd in commands.keySet()) commands[cmd]()
else println "No such command: $cmd"


// a counter of the type referred to in the original cookbook
// would be implemented using a class
def counterMaker(){
    def start = 0
    return { -> start++; start-1 }
}

counter = counterMaker()
5.times{ print "${counter()} " }; println()

counter1 = counterMaker()
counter2 = counterMaker()

5.times{ println "${counter1()} " }
println "${counter1()} ${counter2()}"
//=> 0
//=> 1
//=> 2
//=> 3
//=> 4
//=> 5 0


def timestamp() {
    def start = System.currentTimeMillis()
    return { (System.currentTimeMillis() - start).intdiv(1000) }
}
early = timestamp()
//sleep(10000)
later = timestamp()
sleep(2000)
println "It's been ${early()} seconds since early."
println "It's been ${later()} seconds since later."
//=> It's been 12 seconds since early.
//=> It's been 2 seconds since later.
//----------------------------------------------------------------------------------

Taking References to Scalars

//----------------------------------------------------------------------------------
// All variables in Groovy are objects including primitives. Some objects
// are immutable. Some operations on objects change mutable objects.
// Some operations produce new objects.

// 15 is an Integer which is an immutable object.
// passing 15 to a method passes a reference to the Integer object.
def print(n) { println "${n.toString()}" }
print(15)  // no need to create any kind of explicit reference

// even though Integers are immutable, references to them are not
x = 1
y = x
println "$x $y"  // => 1 1
x += 1    // "x" now refers to a different object than y
println "$x $y"  // => 2 1
y = 4     // "y" now refers to a different object than it did before
println "$x $y"  // => 2 4

// Some objects (including ints and strings) are immutable, however, which
// can give the illusion of a by-value/by-reference distinction:
list = [[1], 1, 's']
list.each{ it += 1 } // plus operator doesn't operate inplace
print list //=> [[1] 1 s]
list = list.collect{ it + 1 }
print list //=> [[1, 1], 2, s1]

list = [['Z', 'Y', 'X'], ['C', 'B', 'A'], [5, 3, 1]]
list.each{ it.sort() } // sort operation operates inline
println list // => [["X", "Y", "Z"], ["A", "B", "C"], [1, 3, 5]]
//----------------------------------------------------------------------------------

Creating Arrays of Scalar References

//----------------------------------------------------------------------------------
// As indicated by the previous section, everything is referenced, so
// just create a list as normal, and beware that augmented assignment
// works differently with immutable objects to mutable ones and depends
// on the semantics of the particular operation invoked:
mylist = [1, "s", [1]]
print mylist
//=> [1, s, [1]]

mylist.each{ it *= 2 }
print mylist
//=> [1, s, [1,1]]

mylist[0] *= 2
mylist[-1] *= 2
print mylist
//=> [2, s, [1, 1]]

// If you need to modify every value in a list, you use collect
// which does NOT modify inplace but rather returns a new collection:
mylist = 1..4
println mylist.collect{ it**3 * 4/3 * Math.PI }
// => [4.188790204681671, 33.510321638395844, 113.09733552923255, 268.0825731062243]
//----------------------------------------------------------------------------------

Using Closures Instead of Objects

//----------------------------------------------------------------------------------
def mkcounter(count) {
    def start = count
    def bundle = [:]
    bundle.'NEXT' = { count += 1 }
    bundle.'PREV' = { count -= 1 }
    bundle.'RESET' = { count = start }
    bundle["LAST"] = bundle["PREV"]
    return bundle
}

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

println "next c1: ${c1["NEXT"]()}"  // 21
println "next c2: ${c2["NEXT"]()}"  // 78
println "next c1: ${c1["NEXT"]()}"  // 22
println "last c1: ${c1["PREV"]()}"  // 21
println "last c1: ${c1["LAST"]()}"  // 20
println "old  c2: ${c2["RESET"]()}" // 77
//----------------------------------------------------------------------------------

Creating References to Methods

//----------------------------------------------------------------------------------
def addAndMultiply(a, b) {
    println "${a+b} ${a*b}"
}
methRef = this.&addAndMultiply
// or use direct closure
multiplyAndAdd = { a,b -> println "${a*b} ${a+b}" }
// later ...
methRef(2,3)                   // => 5 6
multiplyAndAdd(2,3)            // => 6 5
//----------------------------------------------------------------------------------

Constructing Records

//----------------------------------------------------------------------------------
record = [
    "name": "Jason",
    "empno": 132,
    "title": "deputy peon",
    "age": 23,
    "salary": 37000,
    "pals": ["Norbert", "Rhys", "Phineas"],
]
println "I am ${record.'name'}, and my pals are ${record.'pals'.join(', ')}."
// => I am Jason, and my pals are Norbert, Rhys, Phineas.

byname = [:]
byname[record["name"]] = record

rp = byname.get("Aron")
if (rp) println "Aron is employee ${rp["empno"]}."

byname["Jason"]["pals"] += "Theodore"
println "Jason now has ${byname['Jason']['pals'].size()} pals."

byname.each{ name, record ->
    println "$name is employee number ${record['empno']}."
}

employees = [:]
employees[record["empno"]] = record

// lookup by id
rp = employees[132]
if (rp) println "Employee number 132 is ${rp.'name'}."

byname["Jason"]["salary"] *= 1.035
println record
// => ["pals":["Norbert", "Rhys", "Phineas", "Theodore"], "age":23,
//      "title":"deputy peon", "name":"Jason", "salary":38295.000, "empno":132]

peons = employees.findAll{ k, v -> v.'title' =~ /(?i)peon/ }
assert peons.size() == 1
tsevens = employees.findAll{ k, v -> v.'age' == 27 }
assert tsevens.size() == 0

// Go through all records
println 'Names are: ' + employees.values().collect{r->r.'name'}.join(', ')

byAge = {a,b-> a.value().'age' <=> b.value().'age'}
employees.values().sort{byAge}.each{ r->
    println "${r.'name'} is ${r.'age'}"
}

// byage, a hash: age => list of records
byage = [:]
byage[record["age"]] = byage.get(record["age"], []) + [record]

byage.each{ age, list ->
    println "Age $age: ${list.collect{it.'name'}.join(', ')}"
}
//----------------------------------------------------------------------------------

Reading and Writing Hash Records to Text Files

//----------------------------------------------------------------------------------
// if you are using a Properties (see 8.16) then just use load
// and store (or storeToXML)
// variation to original cookbook as Groovy can use Java's object serialization
map = [1:'Jan', 2:'Feb', 3:'Mar']
// write
new File('months.dat').withObjectOutputStream{ oos ->
    oos.writeObject(map)
}
// reset
map = null
// read
new File('months.dat').withObjectInputStream{ ois ->
    map = ois.readObject()
}
println map // => [1:"Jan", 2:"Feb", 3:"Mar"]
//----------------------------------------------------------------------------------

Printing Data Structures

//----------------------------------------------------------------------------------
// Groovy automatically does pretty printing for some of the key types, e.g.
mylist = [[1,2,3], [4, [5,6,7], 8,9, [0,3,5]], 7, 8]
println mylist
// => [[1, 2, 3], [4, [5, 6, 7], 8, 9, [0, 3, 5]], 7, 8]

mydict = ["abc": "def", "ghi":[1,2,3]]
println mydict
// => ["abc":"def", "ghi":[1, 2, 3]]

// if you have another type of object you can use the built-in dump() method
class PetLover {
    def name
    def age
    def pets
}
p = new PetLover(name:'Jason', age:23, pets:[dog:'Rover',cat:'Garfield'])
println p
// => PetLover@b957ea
println p.dump()
// => <PetLover@b957ea name=Jason age=23 pets=["cat":"Garfield", "dog":"Rover"]>

// If that isn't good enough, you can use Boost (http://tara-indigo.org/daisy/geekscape/g2/128)
// or Jakarta Commons Lang *ToStringBuilders (jakarta.apache.org/commons)
// Here's an example of Boost, just extend the supplied Primordial class
import au.net.netstorm.boost.primordial.Primordial
class PetLover2 extends Primordial { def name, age, pets }
println new PetLover2(name:'Jason', age:23, pets:[dog:'Rover',cat:'Garfield'])
// =>
// PetLover2[
//     name=Jason
//     age=23
//     pets={cat=Garfield, dog=Rover}
//     metaClass=groovy.lang.MetaClassImpl@1d8d39f[class PetLover2]
// ]

// using Commons Lang ReflectionToStringBuilder (equivalent to dump())
import org.apache.commons.lang.builder.*
class PetLover3 {
    def name, age, pets
    String toString() {
        ReflectionToStringBuilder.toString(this)
    }
}
println new PetLover3(name:'Jason', age:23, pets:[dog:'Rover',cat:'Garfield'])
// => PetLover3@196e136[name=Jason,age=23,pets={cat=Garfield, dog=Rover}]

// using Commons Lang ToStringBuilder if you want a custom format
class PetLover4 {
    def name, dob, pets
    String toString() {
        def d1 = dob.time; def d2 = (new Date()).time
        int age = (d2 - d1)/1000/60/60/24/365 // close approx good enough here
        return new ToStringBuilder(this).
            append("Pet Lover's name", name).
            append('Pets', pets).
            append('Age', age)
    }
}
println new PetLover4(name:'Jason', dob:new Date(83,03,04), pets:[dog:'Rover',cat:'Garfield'])
// => PetLover4@fdfc58[Pet Lover's name=Jason,Pets={cat=Garfield, dog=Rover},Age=23]
//----------------------------------------------------------------------------------

Copying Data Structures

//----------------------------------------------------------------------------------
oldlist = [1, 2, 3]
newlist = new ArrayList(oldlist) // shallow copy
newlist = oldlist.clone() // shallow copy

oldmap = [a:1, b:2, c:3]
newmap = new HashMap(oldmap) // shallow copy
newmap = oldmap.clone() // shallow copy

oldarray = [1, 2, 3] as int[]
newarray = oldarray.clone()

// shallow copies copy a data structure, but don't copy the items in those
// data structures so if there are nested data structures, both copy and
// original will refer to the same object
mylist = ["1", "2", "3"]
newlist = mylist.clone()
mylist[0] = "0"
println "$mylist $newlist"
//=> ["0", "2", "3"] ["1", "2", "3"]

mylist = [["1", "2", "3"], 4]
newlist = mylist.clone()
mylist[0][0] = "0"
println "$mylist $newlist"
//=> [["0", "2", "3"], 4] [["0", "2", "3"], 4]

// standard deep copy implementation
def deepcopy(orig) {
     bos = new ByteArrayOutputStream()
     oos = new ObjectOutputStream(bos)
     oos.writeObject(orig); oos.flush()
     bin = new ByteArrayInputStream(bos.toByteArray())
     ois = new ObjectInputStream(bin)
     return ois.readObject()
}

newlist = deepcopy(oldlist) // deep copy
newmap  = deepcopy(oldmap)  // deep copy

mylist = [["1", "2", "3"], 4]
newlist = deepcopy(mylist)
mylist[0][0] = "0"
println "$mylist $newlist"
//=> [["0", "2", "3"], 4] [["1", "2", "3"], 4]

// See also:
// http://javatechniques.com/public/java/docs/basics/low-memory-deep-copy.html
// http://javatechniques.com/public/java/docs/basics/faster-deep-copy.html
//----------------------------------------------------------------------------------

Storing Data Structures to Disk

//----------------------------------------------------------------------------------
// use Java's serialization capabilities as per 11.10
//----------------------------------------------------------------------------------

Transparently Persistent Data Structures

//----------------------------------------------------------------------------------
// There are numerous mechanisms for persisting objects to disk
// using Groovy and Java mechanisms. Some are completely transparent,
// some require some initialization only, others make the persistence
// mechanisms visible. Here is a site that lists over 20 options:
// http://www.java-source.net/open-source/persistence
// (This list doesn't include EJB offerings which typically
// require an application server or XML-based options)

// We'll just consider one possibility from prevayler.sf.net.
// This package doesn't make changes to persistent data transparent;
// instead requiring an explicit call via a transaction object.
// It saves all such transaction objects in a journal file so
// that it can rollback the system any number of times (or if
// you make use of the timestamp feature) to a particular point
// in time. It can also be set up to create snapshots which
// consolidate all changes made up to a certain point. The
// journalling will begin again from that point.
import org.prevayler.*
class ImportantHash implements Serializable {
    private map = [:]
    def putAt(key, value) { map[key] = value }
    def getAt(key) { map[key] }
}
class StoreTransaction implements Transaction {
    private val
    StoreTransaction(val) { this.val = val }
    void executeOn(prevayler, Date ignored) { prevayler.putAt(val,val*2) }
}
def save(n){ store.execute(new StoreTransaction(n)) }
store = PrevaylerFactory.createPrevayler(new ImportantHash(), "pleac11")
hash = store.prevalentSystem()
for (i in 0..1000) {
    save(i)
}
println hash[750] // => 1500

store = null; hash = null // *** could shutdown here

store = PrevaylerFactory.createPrevayler(new ImportantHash(), "pleac11")
hash = store.prevalentSystem()
println hash[750] // => 1500
//----------------------------------------------------------------------------------

Program: Binary Trees

//----------------------------------------------------------------------------------
// bintree - binary tree demo program
class BinaryTree {
    def value, left, right
    BinaryTree(val) {
        value = val
        left = null
        right = null
    }

    // 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) {
            if (left) left.insert(val)
            else left = new BinaryTree(val)
        } else if (val > value) {
            if (right) right.insert(val)
            else right = new BinaryTree(val)
        } else println "double" // ignore double values
    }

    // recurse on left child,
    // then show current value,
    // then recurse on right child.
    def inOrder() {
        if (left) left.inOrder()
        print value + ' '
        if (right) right.inOrder()
    }

    // show current value,
    // then recurse on left child,
    // then recurse on right child.
    def preOrder() {
        print value + ' '
        if (left) left.preOrder()
        if (right) right.preOrder()
    }

    // show current value,
    // then recurse on left child,
    // then recurse on right child.
    def dumpOrder() {
        print this.dump() + ' '
        if (left) left.dumpOrder()
        if (right) right.dumpOrder()
    }

    // recurse on left child,
    // then recurse on right child,
    // then show current value.
    def postOrder() {
        if (left) left.postOrder()
        if (right) right.postOrder()
        print value + ' '
    }

    // 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) {
            return this.dump()
        } else if (val < value) {
            return left ? left.search(val) : null
        } else {
            return right ? right.search(val) : null
        }
    }
}

// first generate 20 random inserts
test = new BinaryTree(500)
rand = new Random()
20.times{
    test.insert(rand.nextInt(1000))
}

// now dump out the tree all three ways
print "Pre order:  "; test.preOrder();  println ""
print "In order:   "; test.inOrder();   println ""
print "Post order: "; test.postOrder(); println ""

println "\nSearch?"
while ((item = System.in.readLine()?.trim()) != null) {
    println test.search(item.toInteger())
    println "\nSearch?"
}
// Randomly produces a tree such as:
//           -------- 500 ------
//         /                     \
//       181                     847
//     /    \                    /  \
//   3       204              814   970
//    \       /  \            /
//    126  196  414        800
//             /   \       /
//          353   438   621
//                /     /  \
//             423    604   776
//                   /     /
//                 517   765
//                      /
//                    646
//                   /
//                 630
// Pre order:
// 500 181 3 126 204 196 414 353 438 423 847 814 800 621 604 517 776 765 646 630 970
// In order:
// 3 126 181 196 204 353 414 423 438 500 517 604 621 630 646 765 776 800 814 847 970
// Post order:
// 126 3 196 353 423 438 414 204 181 517 604 630 646 765 776 621 800 814 970 847 500
//
// Search?
// 125
// null
//
// Search?
// 126
// <BinaryTree@ae97c4 value=126 left=null right=null>
//----------------------------------------------------------------------------------