Concepts / Linear Search in Arrays

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.

  • Programming

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?

  • The number 0

  • An empty string

  • null

  • An error that stops the program

Reveal answer
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

Definition

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.

DesignWhat the array holdsWhat must be kept in step
Single pair objectOne kind of element, each holding a name and a numberNothing extra; the pair stays together
Two parallel arraysNames in one array, numbers in anotherEvery 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.

Worked example

State of a new directory

What are the array length and dataCount immediately after a new PhoneDirectory is constructed?

  1. 1
    Constructor allocates the array

    The constructor creates an array with one element, so the length is 1.

  2. 2
    Constructor sets the count

    The constructor sets dataCount to zero, because no pairs have been stored.

  3. 3
    Interpret the two values

    The directory has room for one pair but holds none. The one slot is free.

Result

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.

index 0Ana, 555-0101index 1Ben, 555-0102index 2unusedcompare nextscan stops at dataCount
Which index does the scan reach first, and what does each filled position hold before the scan passes the last filled slot?

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.

java
Notes on the code
  • 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.

index 0Ana, 555-0101index 0Ana, 555-0199index 1Ben, 555-0102index 1Ben, 555-0102
After putNumber("Ana", "555-0199"), which slot changed, and did dataCount change?

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.

Worked example

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?

  1. 1
    find returns -1

    Cy is not among the two filled positions, so the scan finishes without a match.

  2. 2
    Check for fullness

    dataCount is 2 and the length is 2, so the array is full.

  3. 3
    Double and copy

    A new array of length 4 is created, and Arrays.copyOf copies the two existing pairs into it.

  4. 4
    Store and count

    The new pair goes at index 2, the position indicated by dataCount, and dataCount becomes 3.

Result

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.

Callerprogram codePhoneDirectorygetNumberfindprivate searchgetNumber(Cy)find(Cy)-1null
In the call getNumber("Cy"), which component returns the -1 signal, and which one turns it into the final result?

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?

  • 555-0101

  • 555-0199

  • null

  • 555-0102

Reveal answer
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 like

    A directory with length 4 is assumed to hold four names, even though dataCount is 2.

    Why it fails

    Length is capacity. Only the positions up to dataCount hold real pairs.

    Do this instead

    Use 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 like

    After replacing Ana's number, the learner expects dataCount to rise from 2 to 3.

    Why it fails

    find returned a non-negative index, so no new pair was created and dataCount does not change.

    Do this instead

    Check 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 like

    getNumber("Cy") is expected to return -1 because find returned -1.

    Why it fails

    -1 is an internal signal. getNumber converts it into a null return value.

    Do this instead

    A missing name produces null from the public method.

  • Common mistake

    Comparing names with == instead of equals

    What it looks like

    Two identical name strings built separately are expected to fail to match.

    Why it fails

    The class compares names with equals, so two names match when their characters are equal, not merely when they are the same object.

    Do this instead

    Reason about matches by character content, as the source's equals comparison does.

Practice Tracing the Directory

MEDIUM

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 dataCount of 0, as the constructor sets them.
  • A put for a name that find locates replaces in place and leaves dataCount alone.
  • A get for a name that find cannot locate returns null.
HARD

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.
  • getNumber converts a -1 from find into null, while putNumber replaces the number in place when the name exists and otherwise appends a new pair at dataCount.
  • The array is doubled with Arrays.copyOf when 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.