Putting a loop in a loop (a nested loop) doesn’t have new rules, but works in surprising ways, and there are some common tricks.
This chapter is mostly various nested loop examples, But I added two new loop
rules: break; and continue;. They are odd, but commonly used and make writing
loops easier.
The main trick with one loop inside the other is that the inner loop restarts each time. This nested loop below runs 48 times, printing every combination from (0,0) to (7,5):
If prints (0,0) then (0,1) up to (0,5), then restarts with (1,0) to (1,5), ending with
(7,5)
j seems like a bad choice for the inside loop variable, since it looks so much like
an i. But it’s traditional. If you have good names for the loop variables, use them. If
not, use i, and then j for an inside loop.
Sometimes it helps to rewrite as a simpler loop. Here’s the middle loop as a while, where we can clearly see j resets to 0 each time:
Those numbers look like grid coordinates, and that’s a common way nested loops are used: rows and columns. Here’s a program using the nested loops from above to make an 8 by 6 grid (it uses Instantiate and assumes you have a 1x1x1 Cube being copied, probably a prefab).
We already know that the inside runs 48 times, so this Instantiates 48 blocks.
In our minds, the loop’s job is to make row and col be every combination from (0,0) (7,5). The body of the loop is really “make a block, positioned where row and col say.”
The exact positioning numbers I used aren’t important. Block (0,0) is at position
(-6,-3), which is near the bottom-left corner of our screen. I spaced them 1.2f apart,
so we can see the gaps.
A fun thing for testing is to color or somehow change some of them, to see it better. Putting this in the loop makes the (0,0) Cube yellow, turns row 0 red, and column 0 green:
We should see yellow in the lower-left corner, red along the bottom and green
along the left side. So row 0 is the bottom, column 0 is the left.
I color things since it’s easy to get positions mixed up. For example, this change, to the blockPos.y line, makes the same arrangement, but puts row 0 at the top:
Or we can flip how row&col change x&y. This would make it 6 wide and 8 tall (instead of 8 wide and 6 tall.) It’s confusing, since the “rows” now run up and down:
These examples seem a little silly, but they point out what the nested loop does and doesn’t do. It makes all the combinations of (0,0) (0,1) …. If the outer loop counts to 10 and the inner counts to 14, you get 140 items in a 10x14 pattern. But how you orient and position them, if at all, is up to you.
A grid is a good picture of a loop – it’s easy to imagine a loop touching each row,
then the second loop making everything in that row. But a nested loop can be any
pattern in a pattern.
Suppose we need 11 12 13, and then 21 22 23, then 31 32 33 …up to 93. That’s a pattern in a pattern (count by 10’s, count 1,2,3 for each.) We can make it with a nested loop:
It seems like tens might be a better name for the first loop variable, then ones for
the second. I kept it as i and j since the pattern doesn’t have to be 10’s and
1’s.
For this next one, I’d like a line of blocks repeating small, medium, large over and over, like a sawblade. I can do that with a nested loop – the inner makes the three sizes, the outer makes several groups:
Placing them side-by-side with changing sizes is tricker than it looks.
Instead of trying to write a formula, I used a running total. pos.x starts on
the left, and is increased it by the size of the current block (1/2 over, then
place, then the other 1/2 over is to account for block placement using the
center).
This next one is a variation of that, but with colors. We’ll make a list of colors, with help from the Inspector, any amount. Then we’ll make 2-4 balls of each color, in random positions. That’s a nested loop: the outer one hits each color, the inner one makes 2-4 of that color:
I like to double-check the indexes: the inside uses Col[ci] to pick the color. The
ci loop goes from 0 to <Col.Count. So that gets them all, and doesn’t go off the
edge.
With those last two I wanted to show how a nested loop feels like a grid, but doesn’t have to be. The sizes one was a straight line, where-as this is just random.
The important thing about using a nested loop to make things is the description. “6 rows, with 8 each” is a nested description, so is “2-4 balls of each color.”
An old trick to get practice with nested loops and indexes is drawing various triangles – really, grids with some of the boxes left out. Instead of making 7 in each row, we can change it up. For example, a triangular grid, where rows have 1, then 2, then 3 then 4:
To keep things simple, I’m going to keep (0,0) on the lower left, with i going up, and j going right.
To simplify the loop code, and to show off functions and pointers more, I’ll move creating and placing the blocks into a function. The pos x/y formulas are unchanged:
This is mostly a wrapper around Instantiate. It returns a pointer to
the block it made, just in case we want to make more changes. A normal
use would be GameObject blk=newBlockAt(0,0);. That makes a block
in the lower-left 00 position. We’re allowed to ignore the return type. If
we don’t need to make any more changes, newBlockAt(0,0); by itself is
fine.
An obvious triangle is the picture from above: 1 block in the first/bottom row, left side, then 2 in the row above that, and so on. Each row is as long as the row # plus 1:
Before, the inner-loop always ran 8 times. Now, it runs based on an equation.
A shorter, more common version leaves out the extra numInRow variable. The equation for the row length goes directly into the inner loop test:
This is a mess to read, at first. The secret is that i isn’t changing as the inner
loops runs. When i is 2, j<i+1 is locked at j<3. Clearly, this is very prone to infinite
loops if the i’s and j’s are scrambled.
A variant of that triangle is putting the blocks on the right side, making a lower-right triangle. Before, each row started at 0 and went a different amount. For this one, each row starts in a different spot, and always goes to the end:
The first row runs all the way from 0-4, the one above it 1-4, 2-4, 3-4 then the top
is just 4.
If we changed the rowStart line to rowStart=4-i, we’d have an upper-right triangle. The bottom row would be 4-4, then 3-4, 2-4, 1-4 and 0-4 for the top row.
When I need to make something like this, I have to draw a numbered grid, mark
each row and check the start/end numbers, guess a formula, run it, tweak it
….
We can use both tricks to make a pyramid. The first row will go all the way across, but then each row with start at one more, and end at one less. This makes rows of length 9, 7, 5, 3, 1:
The inner loop could have been for(int j=i; j<=8-i; j++), but I think the
extra rowStart and rowEnd make it easier to read.
For more fun, here’s an upside-down 1, 3, 5, 7, 9, 11 pyramid:
A quick sanity-check: the bottom row, when i is 0, goes from 5-0 to 5+0, which is
just one thing, in slot 5. When i gets to the last row, on top, it’s 5. rowStart is 5-i
= 0 and rowEnd is 5+i = 10. So the top row is 0 to 10, which is 11 across, which is
correct.
A checkerbox is every other square. It’s not a wedge, but it seems like it belongs in this section. A really slick way is to go through every square and skip the ones where row+column is an even number:
Another try at a checkerboard is to have the loop making the rows go by 2’s, starting on 1 for even rows and 0 for odd rows. This isn’t as nice, but it works:
Loops have two special commands which seem like cheating, but are so nice we use
them anyway.
The first one is break;. It quits a loop immediately. If it’s a for loop, it won’t even run the third i++ part.
In some function examples, I’ve been using a mid-loop return; to quit a loop
right away. break is the same idea, but better.
This clumsy break-using loop searches an int-list for the first negative number. As soon as we find one, we can quit, with i on that number:
Notice how i needed to be declared before the loop. Otherwise it would be local
to the loop and would be thrown away.
A neater way is using extra variables to record what you need. This checks for the first ’e’ in a string and saves the index:
Without the break, this finds the last ’e’.
Then here’s a fakey one. I want to count the ’z’s, but strings with a space don’t count (if we see a space, the answer is 0):
break won’t let us do anything we couldn’t do before, but it can make the code
look nicer. When you see a break in a loop, it means we’ve seen all we need to see.
It’s often nicer than a done=true; style loop.
A common trick is a loop that runs forever, but you plan to use break; to stop it. The loop test is usually while(true). Everyone knows that means you’re using break or return to get out.
Here’s a forever loop that rolls 2 6-sided dice until they get different numbers:
A way to read this is “keep rolling both dice, quit when they’re different numbers.”
A funny thing is how the break; test is the opposite of what the loop test would
be. As a normal loop, this would be while(d1==d2). But the break uses if(d1!=d2).
The loop condition is when we keep going, the break; condition is when we
stop.
Here’s a for(true) plus break, abused to make a loop hitting every box in a list (never do this, but it’s still pretty cool):
You usually don’t have to think about break; while you write code. A loop will
just naturally have a place where it would be nice to quit right now, and you
remember the break; command.
Just so you know, break’s only quit one loop. A break; inside a nested loop will
quit only the inside. The outer loop will keep going as normal. That’s almost always
what we want.
The other loop shortcut command is continue;. It’s also used only by itself, inside a loop. It “skips to the next one” – it jumps to just before the final close-curly.
In this example it helps us print even numbers above 10, from some list:
This loop hits every box in A. The continue command doesn’t quit the whole loop early, just the step we’re on now.
But otherwise continue is like return or break, since it magically changes
flow-of-control. That last prints(n) loop line always runs, but only if the continue
lets it get there.
continue is mostly used when you’d need to use complicated if’s. For example this uses continue to count words in a list that don’t start with # and have the same first and last letters:
Without continue, we’d have to use two ugly ifs:
A common continue mistake is using it in a while loop and skipping the final i++ (that can’t happen with a for loop). This prints every word in W, skipping empty-strings, but has a fatal bug:
On an empty string, the continue jumps back before i increases. It will see the
same empty string again, go back, forever.
Fun continue observation: adding it as the last line in a loop does nothing. It
skips to the end, past 0 lines.
You can use break; and continue; in the same loop. They each do their normal thing.
Suppose we want to search through a list for short words (5 letters or less) and add them into one long string, but not past 25 letters total. If a word would go over, we skip it. This loop does that:
The break; happened to be at the end, but there’s no rule about that. I think this reads pretty well, if you remember continue means go to the next one and break means quit.
There are some natural List nested loops, for example checking whether two lists have any numbers in common.
The obvious way is how we’d do it by hand. To check two pages of numbers, put your finger on the first number on page 1, and scan page 2 for a match. Repeat for the next number on page 1. That’s a nested loop:
The outer loop selects A[0], then the inner loop compares it to everything in B. Then the outer loop moves to A[1], and the inner loop compares that to everything in B, and so on.
i goes with A, and j goes with B. I get them mixed-up a lot, using A[j] and B[i]
by mistake, which off-end crashes and gives wrong answers.
Here’s the same idea, but it counts how many things in A are also in B. It uses a break; so nothing gets double-counted ([3,6] and [3,3] have only one number in common):
The break; quits the one loop it’s in, which is perfect. We check whether A[0]
matches anything in B. If we find a match, we count it, quit the B loop, and move
onto A[1].
Checking just one list for duplicates is the same idea: take each item, and compare it to every other. But there are two differences. One is we can’t compare an item to itself (if we did, every list would appear to have duplicates). The other is we may as well skip double-compares: we compare box 0 to box 1, so there’s no need to compare box 1 to box 0.
Combining those ideas, we get this rule: the inner loop should compare A[i] to everything that comes after it in the list.
Another rule, not as important: the outer loop can stop one before the end (the last item has already been compared to everything):
That inner loop is a little like the triangle nested loops, the way it starts based on i. For a double-check: suppose i is 3. The inside loop starts by comparing A[3] to A[4], which is what should happen.
There are some very sneaky nested loops that sort a list. This is getting into the area
of algorithms – where three days of work comes down to a few innocent-looking
loops. But these aren’t too bad.
We know how to find the smallest item in a list. To sort, we’ll do that, add it to a new one, and cross it out from ours. Then we’ll do it again: find the new smallest (since we crossed-out the old smallest,) add that to the end of the new list, and cross that one out.
It works like this:
It’s a loop around “find the smallest” and is called a selection sort. Here’s the code for that crude idea above:
These are tricky. If you write something like this, put some print’s in the
middle so you can see where it’s messing up (the first try always messes
up).
There’s an even cleverer, more confusing way to do it with just one list. We find the smallest and switch it into the first position. Then find the smallest out of the rest and switch that into the second spot.
This is a real selection sort (you can probably look it up for more explanation):
If you liked this, insertion and bubble sort are the other two you could look up. They’re both nested loops that make no sense until you read the plan.