Grade by Grade
A. Programming Concepts and Skills · Arrays and Subprograms

Highest, Lowest and Linear Search

A2.3 A1.6 A4.5About 70 minutes
Learning goals
  • I can find the highest or lowest value in a list, and its index, without using max() or min().
  • I can explain why the tracker should start at the first element, not at 0.
  • I can write a linear search that returns the index of a target, or −1 if it is not there.
  • I can choose test cases (first, last, middle, missing, repeated, empty) that check a search fully.

Words to know

maximum / minimum
The highest / lowest value in a set of data.
tracker
A variable that remembers the best value (or its index) seen so far while a loop runs.
linear search
Checking the elements of a list one at a time, in order, until the target is found or the list ends.
target (search key)
The value a search is looking for.
sentinel value
A special value that means "nothing" or "stop", such as −1 for "not found" (−1 can never be a real index in a fixed-size array).
test case
One set of inputs plus the output you expect, used to check that a program works.

The warmest day of a cold week

It is February in Thunder Bay and every reading this week is below zero: −14, −9, −22, −6 and −11 °C. Which day was the warmest? You can see it is −6. A program has to find it by looking at every element and remembering the best one so far. The same idea finds the fastest skater, the cheapest flight or the highest mark. In this lesson you will write the maximum/minimum algorithm and the linear search algorithm, the last two array algorithms the curriculum names for this course.

Finding the highest value

  1. Make a tracker highest and give it the first element: highest = a[0].
  2. Visit every element. If the element is greater than highest, it becomes the new highest.
  3. After the loop, highest holds the maximum.

For the lowest value, use the same pattern with <.

temps = [-14, -9, -22, -6, -11]
highest = temps[0]
for t in temps:
if t > highest:
highest = t
print("Warmest:", highest)

Output: Warmest: -6.

temps-140-91-222-63-114highestchanged?-14start-9yes-9no-6yes-6noStarting at 0 would be wrong: 0 is higher than every temperature here.
Tracing the maximum algorithm. The tracker starts at the first element, −14, and changes only when a bigger value appears: −9, then −6. Shaded tracker boxes mark the start and each change. The answer, −6, is shaded in the list.

A classic logic error

Many students start the tracker at 0. With marks or heights that seems to work, because all the values are positive. With Canadian winter temperatures it fails:

temps = [-14, -9, -22, -6, -11]
highest = 0 # BUG: 0 is not one of the temperatures
for t in temps:
if t > highest:
highest = t
print("Warmest:", highest)

Output: Warmest: 0, which is wrong. No element is greater than 0, so the tracker never changes, and the program reports a temperature that is not even in the list. Start at temps[0] and the bug disappears for every possible list.

Where is the best value? Track the index

Often you need to know which element won, not just its value: whose time was fastest, which day was coldest. Track the index instead of the value. Because you start with index 0, the loop can begin at index 1.

names = ["Ana", "Raj", "Kim", "Lou"]
times = [52.4, 49.8, 55.1, 49.8] # seconds for 400 m skate
best_i = 0
for i in range(1, len(times)):
if times[i] < times[best_i]:
best_i = i
print(names[best_i], "was fastest:", times[best_i])

Output: Raj was fastest: 49.8. The list names and the list times are parallel lists: index 1 in both refers to the same skater. Lou also skated 49.8, but < only replaces the best when a time is strictly smaller, so the first one found (Raj) stays.

Built-in shortcuts

Python has max(a) and min(a), and they work well. You will use them in Using Built-in Functions and Modules. Writing the algorithm yourself is still required in this course: it shows you understand it, it works in every language, and it is easy to adapt (for example, to find the index of the maximum, or the longest name).

Linear search

To find a target in a list, a linear search checks the elements one at a time, starting at index 0:

  • If an element equals the target, the search stops and reports that index.
  • If the loop reaches the end without a match, the target is not in the list. By convention we report −1, a sentinel value, because −1 is never a real position in an array.

The break statement leaves the loop immediately, so no time is wasted after the target is found.

jerseys = [91, 16, 34, 44, 8]
target = int(input("Jersey number? "))
found_at = -1
for i in range(len(jerseys)):
if jerseys[i] == target:
found_at = i
break
if found_at == -1:
print(target, "is not on the team")
else:
print(target, "is at index", found_at)

Two sample runs. Entering 34 prints 34 is at index 2. Entering 50 prints 50 is not on the team.

target 3491016134244384check 1≠ 34check 2≠ 34check 3= 34 stop!not checkednot checkedFound at index 2 after 3 comparisons. Searching for 50 would take all 5 and return -1.
A linear search for 34. The first two elements (blue) do not match; the third (green) does, so the search stops. Elements after it are never checked. In the worst case (target last, or missing), a list of n elements needs n comparisons.

Python also has the operator in (34 in jerseys gives True or False) and the method jerseys.index(34), which gives the index but raises a ValueError if the target is missing. Both do a linear search for you behind the scenes.

Worked example

Example 1: Find every match

A die was rolled 6 times: rolls = [3, 6, 1, 6, 2, 6]. Write a program that lists every index where a 6 was rolled, and how many 6s there were.

  1. We want all matches, so do not break: the loop must visit every element.
  2. Collect the matching indexes in a new list with append. The count is the length of that list.
  3. Code: rolls = [3, 6, 1, 6, 2, 6] spots = [] for i in range(len(rolls)):     if rolls[i] == 6:         spots.append(i) print("6 was rolled at indexes", spots) print("Count:", len(spots))

6 was rolled at indexes [1, 3, 5] and Count: 3

Testing a search: a full range of test cases

A program that works for one input is not proven correct. Choose test cases that cover every kind of situation, write down the expected output first, then run the program and record the actual output.

Test caseInput (target)Why it mattersExpected output
Target in the middle34the normal case34 is at index 2
Target first91lower bound (index 0)91 is at index 0
Target last8upper bound (index 4)8 is at index 4
Target missing50the not-found path50 is not on the team
Repeated targetlist [7, 7], target 7which match is reported?index 0 (the first)
Empty listlist [], any targetloop runs zero timesnot found, no crash
A test plan for the jersey search. Each row checks a different path through the code.
Worked example

Example 2: Spot the bug with a test case

This search "works" for 34 but gives the wrong answer for 91. Find the bug using the test plan.

found_at = -1

for i in range(1, len(jerseys)):

    if jerseys[i] == target:

        found_at = i

        break

  1. Test "target first" (91): the loop starts at index 1, so index 0 is never checked. Expected index 0; actual −1.
  2. The "middle" test passed, which is why one test is never enough.
  3. Fix: for i in range(len(jerseys)): so the loop starts at 0.

The loop skipped index 0. Boundary test cases (first and last element) catch this kind of off-by-one error.

Just for fun

Why did the linear search fail its driving test?

It kept checking every single mirror, one at a time, even after it had found the car behind it. (Someone forgot the break.)

Hands-on activity

Card search race

You need: a deck of playing cards (or 20 slips of paper numbered 1 to 20), a timer (a phone works), and a partner.

  1. Shuffle 10 cards and lay them face down in a row. This is an unsorted array.
  2. Your partner names a target card. Turn cards over from the left, one at a time, counting your comparisons, until you find it or reach the end. Record the count.
  3. Repeat 5 times with different targets, including one card that is not in the row. What was the most comparisons you ever needed? Why?
  4. Now find the highest card the same way: turn cards over from the left, keeping the best card so far in your hand and swapping it when you find a higher one. How many cards did you have to look at? (All 10, every time.)
  5. Write both algorithms in Python for a list of the same 10 numbers and run your test cases.

What to look for: the student explains that a missing target always costs the full 10 comparisons, and that a maximum always needs every element checked.

Your turn

1

Write code that finds the lowest value in prices = [4.99, 2.49, 3.75, 2.99] without using min().

2

Why should highest start at a[0] instead of 0?

3

Predict the output:

a = [5, 8, 2, 8]

best = 0

for i in range(1, len(a)):

    if a[i] > a[best]:

        best = i

print(best)

4

What should a linear search return when the target is missing, and why that value?

5

What does break do inside a search loop?

6

List four test cases for a search of [12, 40, 7, 25].

More practice: warm-up

1

What is the maximum of −3, −8, −1, −12?

2

In a list of 50 names, at most how many comparisons does a linear search need?

3

Which operator tells you whether a value is in a list?

4

What is a sentinel value?

5

To find a minimum, which comparison do you use, > or <?

More practice: on your own

1

Write code that prints the longest word in words = ["Iqaluit", "Regina", "Charlottetown", "Victoria"].

2

Write a linear search that prints the index of the last match of target in a (or −1).

3

Predict the output:

a = [4, 9, 1, 9, 3]

lo = a[0]

hi = a[0]

for x in a:

    if x < lo:

        lo = x

    if x > hi:

        hi = x

print(hi - lo)

4

Why does jerseys.index(50) crash when 50 is not in the list? Write a safe version.

5

A search for the coldest day starts with coldest_i = 0 and, inside an index loop, uses if temps[i] <= temps[coldest_i]: coldest_i = i. For [-5, -9, -9], which index does it report, and how is that different from <?

Challenge

1

Write code that finds the second-highest distinct value in a list of at least two different numbers, in one loop. Test it with [7, 3, 9, 9, 5].

2

Suppose a linear search on a list of 1 000 000 names takes 0.05 s in the worst case on a certain laptop. Roughly how long would the worst case take for 4 000 000 names, and why?

3

Design a full test plan (at least 5 cases with expected outputs) for a program that prints the index of the minimum of a list of temperatures.

Check your understanding

Quiz

1.

To find the highest value in a list a, what should the tracker start at?

2.

A linear search of 200 elements is looking for a value that is not there. How many comparisons does it make?

3.

What does break do in a loop?

4.

Which test case would catch a search loop that starts at range(1, len(a))?

5.

a = [6, 2, 6]. With if a[i] > a[best]: best = i, starting at best = 0, what is best at the end?

6.

Why is −1 a good "not found" result?

For parents and teachers

Big idea: This completes the array algorithms named in A2.3: finding the highest or lowest value and performing a linear search. A4.5 (validating with a full range of test cases) is introduced with the test plan table.

How to help: Do the card activity together. Then ask, "What test would break this program?" and let your teen try to break their own code with first, last, missing and empty cases.

Common difficulties:

  • Starting the max tracker at 0 (fails for negative data) or the min tracker at 0 (fails for positive data). Always start at the first element.
  • Forgetting break, or using it when all matches are wanted.
  • Printing "not found" inside the loop on every non-match. The not-found decision can only be made after the loop.
  • Testing with only one easy input. Encourage the habit of writing expected outputs before running.