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
|