Back

Activity 1.20: Loop Patterns

divider

Activity 1.20

Loop Patterns

Key Concepts

Accumulator and Counter

Tracking a Maximum

Averages That Are Actually Right

You Have Already Written One of These Twice

Activity 1.19 had you sum 1 through 10, and then keep a running mass while crates loaded.

Those were the same pattern, and it has a name.

Almost every loop you write for the rest of this course is one of four shapes. Today they get their names, and the starting values that make them work.

1. Accumulator

int total = 0;
for (int i = 1; i <= 4; i++)
{
total = total + i;
}
Console.WriteLine($"Total: {total}");

You wrote this exact loop last session. Predict the total.

1. Accumulator

Terminal window
Total: 10

A variable that grows across passes. It starts at 0 when you are adding — and at 1 if you are ever multiplying, because starting a product at zero leaves it at zero forever.

2. Counter

int hotDays = 0;
for (int i = 1; i <= 5; i++)
{
Console.Write($"Reading {i}? ");
int temp = int.Parse(Console.ReadLine());
if (temp > 30)
{
hotDays = hotDays + 1;
}
}
Console.WriteLine($"Readings above 30: {hotDays}");

Read the line inside the if carefully. It is not the accumulator's line.

2. Counter

Terminal window
Reading 1? 28 [Enter]
Reading 2? 34 [Enter]
Reading 3? 31 [Enter]
Reading 4? 22 [Enter]
Reading 5? 35 [Enter]
Readings above 30: 3

It looks like an accumulator, but it counts how many times something was true rather than adding up the values themselves.

Accumulator or Counter?

  • total = total + reading — accumulator
  • count = count + 1 — counter

One adds the data. The other adds one.

Mixing them up gives you an answer that looks perfectly plausible and is wrong, and nothing will tell you.

3. Tracking a Maximum

int highest = 0;
for (int i = 1; i <= 3; i++)
{
Console.Write("Reading? ");
int temp = int.Parse(Console.ReadLine());
if (temp > highest)
{
highest = temp;
}
}
Console.WriteLine($"Highest: {highest}");

Keep the biggest thing seen so far. This one is correct for most data, which is exactly the problem.

Now Watch It Break

All three readings below zero
Reading? -5 [Enter]
Reading? -12 [Enter]
Reading? -3 [Enter]
Highest: 0

It reported a maximum that was never in the data. Every reading was negative, so not one of them ever beat the starting 0 — and 0 survived to the end.

Starting a Maximum Safely

  • Start below anything possible, like -999 — simple, but you have to be right about what is possible
  • Or start from the first value you see — always correct, and slightly more work

Starting a maximum at 0 is the classic bug of this pattern, and it only appears when the data happens to be all negative.

A minimum has the same bug upside down.

4. Average

int total = 12;
int count = 5;
double average = total / count;
Console.WriteLine($"Average: {average:F1}");

Five readings adding up to 12. The variable is a double and the format is :F1. Predict what prints.

4. Average

Terminal window
Average: 2.0

2.0, and the right answer is 2.4.

total and count are both int, so C# did whole-number division, got 2, threw away the remainder, and only then put the result into a double.

Activity 1.6 Warned You About This

17 / 5 is 3 in C#, and back in Activity 1.6 that was a curiosity.

Today it is the difference between a right answer and a wrong one that looks right.

2.0 has a decimal point on it. It looks like a formatted average. It is a truncated integer wearing a costume.

One Character Fixes It

int total = 12;
double count = 5;
double average = total / count;
Console.WriteLine($"Average: {average:F1}");

Look at line 2 and nothing else.

One Character Fixes It

Terminal window
Average: 2.4

If either side of the division is a double, the division is a decimal one. Declaring count as a double does it, and so does writing the number as 5.0.

:F1 only decides how a number is printed. It cannot put back a remainder that was already thrown away.

Today's Objectives

  • Accumulating a total across a loop
  • Counting how many values met a condition
  • Tracking a maximum and a minimum safely
  • Computing an average that is not quietly truncated
  • Running several patterns over one pass of input

Key Terms

Accumulator
A variable that builds up a total across passes.
Counter
A variable that counts how many times something happened.
Running maximum
The largest value seen so far.
Initialization
The starting value a variable is given before the loop. For these four patterns it is the part that decides whether they work.

'F' → Fullscreen

divider

Build

Today you build a fishing game in a new program named 1-20-fishing-trip. Ten casts, and on each one you choose shallow water or deep. By the end it reports your trip using all four of today's patterns.

How to read the code blocks. Each task's code block shows your whole program, from line 1, as it should look when that task is finished.

The green lines marked with a + are the ones you add or change for that task — everything else is already in your file. Do not type the + sign.

Your fish are random, so nobody's numbers will match. Check every run by hand instead: the program prints each fish as it lands, so add them up yourself and compare. You cannot check a loop whose answer you do not already know.


Task 1: Ten Casts

  • Write the code below and play a trip. Each pass of the loop is one cast: you choose the water, and Random decides whether anything bites and how big it is.
  • totalWeight is an accumulator. It adds the weight of every fish. Add your fish up by hand and check that the total matches.
  • Notice where weight is declared. It is inside the loop, so it is brand new every cast — and that is fine, because totalWeight is the one that has to survive all ten. Be ready to say why totalWeight could not go inside.
  • Play one trip all shallow and one all deep. Write down both totals. Which water would you rather fish?
1-20-fishing-trip
int totalWeight = 0;
Console.WriteLine("Ten casts. Shallow water bites often. Deep water bites less, but bigger.");
Console.WriteLine();
for (int cast = 1; cast <= 10; cast++)
{
Console.Write($"Cast {cast}: shallow (1) or deep (2)? ");
int spot = int.Parse(Console.ReadLine());
int weight = 0;
int roll = Random.Shared.Next(1, 101);
if (spot == 1 && roll <= 75)
{
weight = Random.Shared.Next(1, 7);
}
if (spot == 2 && roll <= 30)
{
weight = Random.Shared.Next(4, 16);
}
if (weight > 0)
{
Console.WriteLine($" A {weight} lb fish!");
totalWeight = totalWeight + weight;
}
else
{
Console.WriteLine(" Nothing bites.");
}
}
Console.WriteLine();
Console.WriteLine($"Total weight: {totalWeight} lb");

Task 2: Count Them, and Keep the Biggest

  • Two more patterns join the same loop. One pass of input, three questions answered — that is the normal shape of this.
  • fishCaught adds one. totalWeight adds the weight. They look almost the same and they answer completely different questions.
  • Both go inside if (weight > 0). A cast that catches nothing should not be counted, and it cannot be the biggest fish.
  • biggest starts at 0, and here that is safe. The deck called starting a maximum at 0 the classic bug of this pattern. Work out why it cannot hurt this program — what is the lightest fish you can catch?
1-20-fishing-trip
int totalWeight = 0;
int fishCaught = 0;
int biggest = 0;
Console.WriteLine("Ten casts. Shallow water bites often. Deep water bites less, but bigger.");
Console.WriteLine();
for (int cast = 1; cast <= 10; cast++)
{
Console.Write($"Cast {cast}: shallow (1) or deep (2)? ");
int spot = int.Parse(Console.ReadLine());
int weight = 0;
int roll = Random.Shared.Next(1, 101);
if (spot == 1 && roll <= 75)
{
weight = Random.Shared.Next(1, 7);
}
if (spot == 2 && roll <= 30)
{
weight = Random.Shared.Next(4, 16);
}
if (weight > 0)
{
Console.WriteLine($" A {weight} lb fish!");
totalWeight = totalWeight + weight;
fishCaught = fishCaught + 1;
if (weight > biggest)
{
biggest = weight;
}
}
else
{
Console.WriteLine(" Nothing bites.");
}
}
Console.WriteLine();
Console.WriteLine($"Total weight: {totalWeight} lb");
Console.WriteLine($"Fish caught: {fishCaught} of 10");
Console.WriteLine($"Biggest: {biggest} lb");

Task 3: The Trip Report

  • The minimum is the maximum pattern with two things flipped — < instead of >, and a starting value of 999 instead of 0. Be ready to say why it starts that high.
  • The report is inside if (fishCaught > 0). No fish, no biggest, no smallest, and no average — there is nothing to report.
  • Type everything except the change on line 2 first. Leave fishCaught as an int, play a trip, and work out total ÷ fish on a calculator. Compare it with the average your program printed.
  • Then change int to double on line 2 and play again — the deck's one-word fix. Write down one wrong average and one right one. If your first trip happened to divide evenly, the bug hid; play until it does not.
1-20-fishing-trip
int totalWeight = 0;
double fishCaught = 0;
int biggest = 0;
int smallest = 999;
Console.WriteLine("Ten casts. Shallow water bites often. Deep water bites less, but bigger.");
Console.WriteLine();
for (int cast = 1; cast <= 10; cast++)
{
Console.Write($"Cast {cast}: shallow (1) or deep (2)? ");
int spot = int.Parse(Console.ReadLine());
int weight = 0;
int roll = Random.Shared.Next(1, 101);
if (spot == 1 && roll <= 75)
{
weight = Random.Shared.Next(1, 7);
}
if (spot == 2 && roll <= 30)
{
weight = Random.Shared.Next(4, 16);
}
if (weight > 0)
{
Console.WriteLine($" A {weight} lb fish!");
totalWeight = totalWeight + weight;
fishCaught = fishCaught + 1;
if (weight > biggest)
{
biggest = weight;
}
if (weight < smallest)
{
smallest = weight;
}
}
else
{
Console.WriteLine(" Nothing bites.");
}
}
Console.WriteLine();
Console.WriteLine($"Total weight: {totalWeight} lb");
Console.WriteLine($"Fish caught: {fishCaught} of 10");
if (fishCaught > 0)
{
double average = totalWeight / fishCaught;
Console.WriteLine($"Biggest: {biggest} lb");
Console.WriteLine($"Smallest: {smallest} lb");
Console.WriteLine($"Average: {average:F1} lb");
}
else
{
Console.WriteLine("Not one bite. Try shallow water next time.");
}

Task 4: Break It on Purpose

Three changes, one at a time. Write down exactly what happens, and put each one back before the next.

  1. Change totalWeight = totalWeight + weight; to totalWeight = totalWeight + 1; and play a trip. Record the total weight and the average, and say which of the four patterns you accidentally wrote.
  2. Put it back. Now start smallest at 0 instead of 999. Record what it reports.
  3. Put it back. Change fishCaught > 0 to fishCaught >= 0, then answer 3 on every cast — there is no spot 3, so you are casting onto dry land. Record all five report lines.

Number 1 is the dangerous one, because it runs and looks fine. Say how long it would have taken you to notice if you had not been adding up your fish by hand. For number 3, say which of the five lines are starting values that leaked out, and look up what NaN stands for.


Challenge (Optional): Start From the First Fish

  • 999 works until somebody lands a 1,000 lb fish. Rewrite the biggest and the smallest so they start from the first fish instead of from a guess.
  • The first fish is the special one, and you already have a variable that tells you how many fish you have caught.
  • Add a comment explaining why this version cannot be broken by an unusual catch.
divider

Checkpoint

One real trip from the finished program. Your fish will be different, but your report has to agree with your fish.

Example Output
Ten casts. Shallow water bites often. Deep water bites less, but bigger.
Cast 1: shallow (1) or deep (2)? 1 [Enter]
A 5 lb fish!
Cast 2: shallow (1) or deep (2)? 1 [Enter]
A 2 lb fish!
Cast 3: shallow (1) or deep (2)? 2 [Enter]
Nothing bites.
Cast 4: shallow (1) or deep (2)? 1 [Enter]
A 2 lb fish!
Cast 5: shallow (1) or deep (2)? 2 [Enter]
Nothing bites.
Cast 6: shallow (1) or deep (2)? 2 [Enter]
Nothing bites.
Cast 7: shallow (1) or deep (2)? 1 [Enter]
Nothing bites.
Cast 8: shallow (1) or deep (2)? 1 [Enter]
A 2 lb fish!
Cast 9: shallow (1) or deep (2)? 2 [Enter]
Nothing bites.
Cast 10: shallow (1) or deep (2)? 1 [Enter]
A 1 lb fish!
Total weight: 12 lb
Fish caught: 5 of 10
Biggest: 5 lb
Smallest: 1 lb
Average: 2.4 lb
  • Your total weight matches your fish added up by hand
  • Your average has real decimals. 12 ÷ 5 is 2.4; if it reads 2.0 the division is still whole-number
  • You recorded both trip totals from Task 1, and one wrong and one right average from Task 3
  • All three Task 4 results are recorded
divider

Reflection

Answer the following questions before submitting your work.

  1. An accumulator and a counter look almost identical in code. Explain the difference, and give a question that each one answers about the same fishing trip.
  2. Starting biggest at 0 is safe in your fishing game, but it was a bug on the deck's temperature readings. Explain what is different about the data, and why a bug that only appears on some data is hard to catch.
  3. Before you changed fishCaught to a double, your average printed with a .0 on the end, and that looks like a properly formatted answer. Explain what C# actually did, and why :F1 could not have rescued it.
divider

Submit

Submit four things to the dropbox:

  1. Your 1-20-fishing-trip program file.
  2. Your two Task 1 trip totals, and one wrong and one right average from Task 3.
  3. All three Task 4 results — including which pattern number 1 turned into, and the five report lines from dry land.
  4. Your three reflection answers.

Activity Complete