Linear Search in Arrays
A phone directory built on an association list stores each name and number as a pair, so a lookup by key returns the associated value.
A Question a Directory Must Answer
A phone directory answers one question over and over: given a person's name, what is their number? It also has to accept new entries and changes to existing ones. This article follows a Java class called PhoneDirectory that handles both jobs using only an array and a few integer counters. Nothing in it is exotic. The interest lies in how a handful of simple decisions combine into a working data structure: where to look for a name, what to do when the name is already present, and what to do when storage runs out.
What do you think happens?
Suppose a directory has never been given the name Cy. What should getNumber("Cy") return?
Reveal answer
null
getNumber turns the not-found signal from find into a null return value. A name that was never stored produces null, not a number.
Keep the two public operations in mind as you read: one gets a number and one puts a number. Each takes a name as input. The rest of the article shows how the class carries out both jobs and why it sometimes returns null, sometimes replaces a value, and sometimes grows its storage.
Pairs Stored as One Unit
An association list stores each key together with its associated value, so a lookup by key returns the value paired with it. In a phone directory, the key is a name and the value is a phone number.
The directory stores each association as a single object that holds both halves of the pair. The class has a nested type with a name field and a number field, so each stored element carries its own key and its own value together. Because the pair travels as one unit, the array only needs to hold one kind of element. The alternative would be two parallel arrays, one of names and one of numbers, which must be kept in step with each other on every insertion and replacement.
| Design | What the array holds | What must be kept in step |
|---|---|---|
| Single pair object | One kind of element, each holding a name and a number | Nothing extra; the pair stays together |
| Two parallel arrays | Names in one array, numbers in another | Every name slot must match the number slot at the same index |
Storing each association as one pair removes the need to synchronize two arrays.
The name is the only thing a caller needs in order to retrieve the number. The structure only has to preserve which number belongs with which name. Its meaning does not depend on any ordering of the names.
Two Pieces of State
The class keeps two pieces of state. The first is an array of pairs. The second is an integer, dataCount, that counts how many pairs are actually stored. These are different quantities. The array has a length, which is its capacity, and dataCount records how many of those positions are filled. A constructor creates an initially empty directory by allocating a one-element array and setting the count to zero.
State of a new directory
What are the array length and dataCount immediately after a new PhoneDirectory is constructed?
- 1Constructor allocates the array
The constructor creates an array with one element, so the length is 1.
- 2Constructor sets the count
The constructor sets
dataCountto zero, because no pairs have been stored. - 3Interpret the two values
The directory has room for one pair but holds none. The one slot is free.
Length 1, dataCount 0.
How find Scans the Array
The find method is the heart of the class, because both getNumber and putNumber depend on it. Its job is to search the array for a name and report where that name sits. It walks through the filled positions one at a time, starting from index zero, and compares each stored name with the requested name. This is a linear search: it visits positions in order until it finds a match or runs out of filled positions.
In the diagram, the directory holds two pairs, so dataCount is 2. The scan compares Cy with Ana at index zero, then with Ben at index one. Neither matches. Index two is outside the filled positions, so the scan does not look at it. The search is restricted to the filled positions and does not examine unused slots.
- The loop stops at
dataCount, so it searches only filled positions. - equals compares characters, not object identity.
- A match returns the index immediately.
What find Returns When a Name Is Missing
find has two possible outcomes. If a stored name matches, it returns the index of that pair as soon as the match is found. If the loop finishes without a match, find returns -1. That value is a signal, not a position. No valid index is negative, so -1 can never be confused with a real location.
The two public methods interpret the signal differently. getNumber turns -1 into a null return value. putNumber uses -1 to decide that a new pair must be added. The same internal result therefore leads to two different behaviors depending on the caller's purpose.
Replacing a Number or Appending a Pair
putNumber begins with a guard. If either argument is null, it throws an IllegalArgumentException saying that the name and number cannot be null. After the guard, it calls find. If find returns a non-negative index, the name already exists, and the method replaces the old number at that position with the new one. The count does not change, because no new pair was created. If find returns -1, the method adds a new pair at the position indicated by dataCount and then increases dataCount by one.
In the before and after views, only index zero changed. The number was overwritten in place, and dataCount stayed at 2. Replacing a value and adding a value leave different traces in the state, which is why counting carefully matters when you debug.
Growing the Array When It Is Full
Appending a new pair needs a free slot. When dataCount equals the array length, every position is occupied, and the method cannot store the new pair. The source's approach is to create a larger array with twice the length, copy the existing pairs into it, and then continue. The copy is performed by the Arrays.copyOf library method, which takes the old array and the new length. Because the array can keep doubling, the directory has no fixed size limit.
Adding a third name to a full directory
Starting from a directory with length 2 and dataCount 2, what happens when putNumber("Cy", "555-0303") is called and Cy is not already present?
- 1find returns -1
Cy is not among the two filled positions, so the scan finishes without a match.
- 2Check for fullness
dataCountis 2 and the length is 2, so the array is full. - 3Double and copy
A new array of length 4 is created, and
Arrays.copyOfcopies the two existing pairs into it. - 4Store and count
The new pair goes at index 2, the position indicated by
dataCount, anddataCountbecomes 3.
Length 4, dataCount 3. Three pairs are stored and index 3 is still free.
Tracing a Sequence of Calls
A program using the directory may return something unexpected. The useful question is where in the sequence of calls the actual result departed from the expected one. Work through the operations in order and check the state after each one. For a getNumber call, ask whether find returned the index you expected. For a putNumber call, ask whether find returned -1 or a non-negative index, since that decides whether the count grows.
What do you think happens?
Starting from an empty directory, the calls are putNumber(Ana, 555-0101), putNumber(Ben, 555-0102), putNumber(Ana, 555-0199), getNumber(Ana). What does the final getNumber return?
Reveal answer
555-0199
The third call finds Ana at index 0 and replaces the number there, so the later getNumber(Ana) finds index 0 and returns the replaced value.
Divergence usually shows up first in the count or in a single stored number. Tracing forward from the last point where expected and actual results still agreed identifies the step responsible. If the count is higher than you predicted, check whether a put call treated an existing name as new. If a lookup returns null for a name you believed was stored, check whether the name was spelled differently, since find compares characters with equals.
Mistakes Learners Make with Directory Operations
Common mistake Treating array length as the number of entries
What it looks likeA directory with length 4 is assumed to hold four names, even though dataCount is 2.
Why it failsLength is capacity. Only the positions up to
dataCounthold real pairs.Do this insteadUse dataCount to decide how many entries are stored and where the next pair goes.
Common mistake Expecting putNumber on an existing name to increase the count
What it looks likeAfter replacing Ana's number, the learner expects dataCount to rise from 2 to 3.
Why it failsfind returned a non-negative index, so no new pair was created and
dataCountdoes not change.Do this insteadCheck find's result first. Only a -1 result leads to a new pair.
Common mistake Expecting getNumber to return -1 for a missing name
What it looks likegetNumber("Cy") is expected to return -1 because find returned -1.
Why it fails-1 is an internal signal.
getNumberconverts it into a null return value.Do this insteadA missing name produces null from the public method.
Common mistake Comparing names with == instead of equals
What it looks likeTwo identical name strings built separately are expected to fail to match.
Why it failsThe class compares names with equals, so two names match when their characters are equal, not merely when they are the same object.
Do this insteadReason about matches by character content, as the source's equals comparison does.
Practice Tracing the Directory
Start with an empty directory. Apply these calls in order: putNumber(Dee, 555-0401), putNumber(Eli, 555-0402), putNumber(Dee, 555-0409), getNumber(Eli), getNumber(Fay). For each call, state the value of dataCount after the call, whether the array grew, and what each getNumber returned.
Hints
- Begin with a length of 1 and
dataCountof 0, as the constructor sets them. - A put for a name that find locates replaces in place and leaves
dataCountalone. - A get for a name that find cannot locate returns null.
A teammate reports that after several calls, getNumber returns null for a name they are sure they stored, and dataCount is one lower than they expected. Describe the first call you would check and what result of find would explain the symptom.
Hints
- Consider whether a name might have been stored under a slightly different spelling.
- Think about what find returns when the comparison fails for every filled position.
Key Takeaways
- An association list stores each key with its value as a single pair. In this directory, a name is the key and a phone number is the value.
- find scans only the filled positions, compares names with equals, and returns the matching index or -1 when no name matches.
getNumberconverts a -1 from find into null, whileputNumberreplaces the number in place when the name exists and otherwise appends a new pair atdataCount.- The array is doubled with
Arrays.copyOfwhen a new pair must be added and the array is full, so the directory has no fixed size limit. - When results go wrong, trace the calls in order and find the first step where the count or a stored number departs from what you expected.