The magazine of the Melbourne PC User Group

Real Programming with JavaScript —Associative Arrays
Trevor Gosbell
 


Trevor Gosbell shows how the power and utility of an array can be increased when it is indexed by words instead of numbers

In our previous article we looked at creating, manipulating and extracting values from JavaScript arrays. Arrays enable us to store lists of related data using a numbered index. However, in JavaScript, the index of an array need not be a number, it can also be a word or key as Listing 1 demonstrates.
 

Listing 1.

var primeMinister = new Array();

primeMinister["barton"] = "Sir Edmund BARTON";
primeMinister["bruce"] = "Stanley Melbourne BRUCE";
primeMinister["chifley"] = "Joseph Benedict CHIFLEY";
primeMinister["cook"] = "Joseph COOK";
primeMinister["curtin"] = "John Joseph CURTIN";
primeMinister["deakin"] = "Alfred DEAKIN";
primeMinister["fadden"] = "Arthur William FADDEN";
primeMinister["fisher"] = "Andrew FISHER";
primeMinister["forde"] = "Francis Michael FORDE";
primeMinister["fraser"] = "John Malcolm FRASER";
primeMinister["gorton"] = "John Grey GORTON";
primeMinister["hawke"] = "Robert James Lee HAWKE";
primeMinister["holt"] = "Harold Edward HOLT";
primeMinister["howard"] = "John Winston HOWARD";
primeMinister["hughes"] = "William Morris HUGHES";
primeMinister["keating"] = "Paul John KEATING";
primeMinister["lyons"] = "Joseph Aloysius LYONS";
primeMinister["mcewen"] = "John McEWEN";
primeMinister["mcmahon"] = "William McMAHON";
primeMinister["menzies"] = "Robert Gordon MENZIES";
primeMinister["page"] = "Earle Christmas Grafton PAGE";
primeMinister["reid"] = "George Houstoun REID";
primeMinister["scullin"] = "James Henry SCULLIN";
primeMinister["watson"] = "John Christian WATSON";
primeMinister["whitlam"] = "Edward Gough WHITLAM";

var selectedPM = prompt( "Which PM?","" );

var outputPM = primeMinister[ selectedPM ];

alert( outputPM );

This program uses the surnames of prime ministers as the array index. Firstly, define a perfectly ordinary array

    var primeMinister = new Array();

and add the twenty-five prime ministers using surname (not a number) as the key and the full name as the value:
    primeMinister["barton"] = "Sir Edmund BARTON";
    ...
    primeMinister["whitlam"] = "Edward Gough WHITLAM";

In the line

    var selectedPM = prompt( "Which PM?","" );

the user's response to the question "Which PM?" (that is, a prime minister's surname - see Figure 1) is stored in the variable
selectedPM. Then the corresponding value is retrieved from the array and saved in outputPM.

    var outputPM = primeMinister[ selectedPM ];

And finally this value is printed to the screen in an alert box (see Figure 2)

    alert( outputPM );
 



Figure 1. Request your PM by surname.



Figure 2. Billy Hughes name in full

Key Value

This type of array is called an associative array (or a hash table if you're coming from the general direction of Perl). Presumably associative arrays get the name because rather than being numerically indexed, values are associated with a specific key. This leads us to think of associative arrays as conceptually different structures: whereas a normal array is a list of values ordered by index, an associative array is a bunch of key-value pairs. There is no inherent order in the keys of an associative array.

Traps 'n Tricks

There are always a few traps and tricks, so let's look at the common ones with associative arrays.

Firstly, each key must be unique but keys are case sensitive. So the words "hughes", "Hughes", "HUGHES", and "HuGhEs" could all be unique keys -that's potentially very confusing! In Listing 1 you can see that I've made all keys lower case to be consistent.

But there's a further complication - of course! Because keys are case sensitive a value will only be retrieved if a perfect match is found. For example, if a user that wants to retrieve Billy Hughes's record they might quite reasonably enter "Hughes" in response to the question "Which PM?" (Figure 3). Unfortunately the program will not find the record because the input word "Hughes" is not a match for the key "hughes" or any other key in the array (Figure 4).
 



Figure 3. A reasonable request ...



Figure 4. ... rejected!

This is similar to the problem of zero-indexed arrays we had in the previous article - either we can instruct the users to enter their requests only in lower case or we can do some processing of the input to force input into lower case. Inevitably, the most reliable method is to give the user a free hand and make the program do the work. This solution is remarkably easy - change the line

    var outputPM = primeMinister[ selectedPM ];

to read

    var outputPM = primeMinister[ selectedPM.toLowerCase() ];

Applying the
toLowerCase() function converts the value in selectedPM to lower case. In the example above, the user's input "Hughes" is converted to "hughes" by toLowerCase(), making a match possible and Billy Hughes' record is found.

More Information Please
This program is OK as far as it goes, but it would be good if we could include some other information about each prime minister - their terms in the top job for instance. Listing 2 has a possible approach.
 

Listing 2.

primeMinister = new Array();

primeMinister["barton"] = ["Sir Edmund BARTON","1901-03"];
primeMinister["bruce"] = ["Stanley Melbourne BRUCE","1923-29"];
primeMinister["chifley"] = ["Joseph Benedict CHIFLEY","1945-49"];
primeMinister["cook"] = ["Joseph COOK","1913-14"];
primeMinister["curtin"] = ["John Joseph CURTIN","1941-45"];
primeMinister["deakin"] = ["Alfred DEAKIN","1903-04","1905-08","1909-10"];
primeMinister["fadden"] = ["Arthur William FADDEN","1941-41"];
primeMinister["fisher"] = ["Andrew FISHER","1908-09","1910-13","1914-15"];
primeMinister["forde"] = ["Francis Michael FORDE","1945-45"];
primeMinister["fraser"] = ["John Malcolm FRASER","1975-83"];
primeMinister["gorton"] = ["John Grey GORTON","1968-71"];
primeMinister["hawke"] = ["Robert James Lee HAWKE","1983-91"];
primeMinister["holt"] = ["Harold Edward HOLT","1966-67"];
primeMinister["howard"] = ["John Winston HOWARD","1996-"];
primeMinister["hughes"] = ["William Morris HUGHES","1915-23"];
primeMinister["keating"] = ["Paul John KEATING","1991-96"];
primeMinister["lyons"] = ["Joseph Aloysius LYONS","1932-39"];
primeMinister["mcewen"] = ["John McEWEN","1967-68"];
primeMinister["mcmahon"] = ["William McMAHON","1971-72"];
primeMinister["menzies"] = ["Robert Gordon MENZIES","1939-41","1949-66"];
primeMinister["page"] = ["Earle Christmas Grafton PAGE","1939-39"];
primeMinister["reid"] = ["George Houstoun REID","1904-05"];
primeMinister["scullin"] = ["James Henry SCULLIN","1929-32"];
primeMinister["watson"] = ["John Christian WATSON","1904-04"];
primeMinister["whitlam"] = ["Edward Gough WHITLAM","1972-75"];

var selectedPM = prompt( "Which PM?","" );

var outputPM = primeMinister[ selectedPM.toLowerCase() ];

alert( outputPM.join("\n") );

The key difference here is that the values in the associative array are not plain strings - they are actually array literals. So what we have here is an associative array with values that are arrays, or an array of arrays!

But Why?

Why an array of arrays? Why didn't I just pile everything up into a string? The first reason is given by the last line

    alert( outputPM.join("\n") );

Remember each value in the
primeMinister associative array is itself an array - so all of the handy hidden extras are available. By using the join() function on outputPM (that is, one of the arrays selected from primeMinister) I can alter the presentation of the information. In this case I have joined each element from the prime minister's array with the \n new line character, giving the output a columnar feel (Figure 5).
But beyond that, structuring the data in this way provides me with a very simple database that can be queried.
 



Figure 5. Billy Hughes and his time as PM



Figure 6. How many PMs have had three separate
goes at the job?

Enquiries

Suppose you were more interested in finding out what prime ministers have had more than one separate term in the top job (ie. were voted out then voted in again). Oh sure, you could just run your eye down the list but it's more fun to alter the program to get the answer for you. See Listing 3.
 

Listing 3.

primeMinister = new Array();

primeMinister["barton"] = ["Sir Edmund BARTON","1901-03"];
... as in listing 2 ...
primeMinister["whitlam"] = ["Edward Gough WHITLAM","1972-75"];

var numberTerms = numberPrompt( "How many terms served?", 1 );

var recordsFound = "";

for ( testKey in primeMinister )
{
  if ( primeMinister[ testKey ].length > numberTerms )
  {
  recordsFound += primeMinister[ testKey ][0]
  + "\nTerm "
  + numberTerms
  + ": "
  + primeMinister[ testKey ][ numberTerms ]
  + "\n";
  }
}

if ( recordsFound == "" )
{
  alert( "No prime ministers have served for "
    + numberTerms
    + " separate terms." );
}
else
{
  alert( recordsFound );
}

This time instead of asking for a prime ministers name, the user is asked to nominate how many terms in office (Figure 6 above):

   var numberTerms = numberPrompt( "How many terms served?", 1 );

Note that this uses my
numberPrompt() function (not included in the listing - see PC Update June 2004) but the program works fine with an ordinary prompt() provided the user enters digits only.

Things start getting interesting in the line

   for ( testKey in primeMinister )

This is a looping command which in English means: "for each key in the
primeMinister associative array, assign that value to the variable testKey and use it in the following block of code".

Inside the loop, the line

if ( primeMinister[ testKey ].length > numberTerms )

we use
testKey to retrieve the value (an array) from primeMinister and test its length against the number of terms served. How does this work? Each PM will have at least two elements in their array: element 0 (their name) and element 1 (their first term in office). So if the user is foolish enough to request all prime ministers who have served one term (by definition that's all of them), this test will always succeed because the length of primeMinister[testKey] is 2, which is greater than 1 - the number of terms served. To extrapolate, all prime ministers who have served two terms will have three elements: element 0 (name), element 1 (first term), and element 2 (second term), and an array of length 3 is greater than numberTerms of 2. Hopefully it's clear that the length of each prime minister's array will always be longer than the number of terms served if that PM has served at least that many terms.

The next line

   recordsFound += primeMinister[ testKey ][0]
   + "\nTerm "
   + numberTerms
   + ": "
   + primeMinister[ testKey ][ numberTerms ]
   + "\n";


builds formatted output for the record including the prime minister's name, which is extracted from the array using
primeMinister[ testKey][0]. Note the double use of array index format: first primeMinister[testKey] is used to retrieve the array for the appropriate prime minister, then the [0] element (which happens to be the PM's name) is retrieved from the array that was just retrieved.
 
This is repeated again at
primeMinister[testKey][numberTerms] where details of the prime minister's term in office is retrieved. Note that this part will be different depending on what number is held in numberTerms.

And the rest of the line is simply concatenation - remembering that \n is the special character for a new line. The resulting string is appended to
recordsFound - notice the use of the += operator, which means "add this onto the end of what's already in recordsFound".

Once out of the loop, we check the contents of
recordsFound

if ( recordsFound == "" )


and if this test returns true (that is,
recordsFound is empty) this means no prime ministers matched the question so we print an informative message to that effect (Figure 7). Otherwise at least one prime minister has been found by the query, so just print our prepared output in recordsFound (Figure 8).
 



Figure 7. No one has had the longevity to do it five times.



Figure 8. Politicians had real staying power back then.

Wrap Up

In this episode we covered the following:

  • an associative array doesn't use a numeric index, it uses a word or key.
  • values held in an array can be any legal value - including other arrays
  • use of the for (... in ...) construct to retrieve all of the keys in an associative array
I've used and abused prompt() and alert() well beyond their intended usage and abusage, so next time we'll look at some other output options.

Information about the Prime Ministers was obtained from
the excellent site "Australia's Prime Ministers" http://primeministers.naa.gov.au/ published by the National Archives of Australia.

Code examples from this article are available from the author's Web page: http://member.melbpc.org.au/~tgosbell/.

Reprinted from the August 2004 issue of PC Update, the magazine of Melbourne PC User Group, Australia

[ About Melbourne PC User Group ]