At 3/19/23 01:32 AM, Nabella wrote:
Honestly, I don't really know what I'm doing myself, but I'm still really glad you're here lurking the forums to straighten things out whenever I'm having trouble understanding something, like that time with function parameters a few months earlier.
No worries, my pleasure! If you have trouble with nested loops, maybe it would help to experiment with different levels and maybe express it in different concepts. For example, perhaps you might find converting it to a set representation.
Suppose you have an array of 10 numbers:
var arr:Array = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10];
Then, a single loop would be the equivalent of operating over the entire array:
for (var i:Number = 0; i < arr.length; i++) {
//code
}
If you add an if condition, then that narrows down your set, e.g. all numbers less than 5:
for (var i:Number = 0; i < arr.length; i++) {
if (arr[i] < 5) {
//code
}
}
That's fairly normal, but suppose you wanted to get the cumulative sum for all elements, such that
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
becomes
1, 3, 6, 10, 15, 21, 28, 36, 45, 55
for the sake of illustration, let's ignore the efficient method of keeping track of the current running total and let's just calculate the cumulative sum from scratch each time. You're going to go over all the elements before the one that's currently visited, so the code will look something like this:
var cumulatives:Array = []
for (var i:Number = 0; i < arr.length; i++) {
var sum:Number = 0
for (var j:Number = 0; j < i; j++) {
sum += arr[j];
}
cumulatives.push(sum)
}
which is almost equivalent to
for (var i:Number = 0; i < arr.length; i++) {
var sum:Number = 0
for (var j:Number = 0; j < arr.length; j++) {
if (j < i) {
sum += arr[j]; // bonus: why j and not i?
}
}
cumulatives.push(sum)
}
Because there are two iteration variables, i and j, you can imagine duplicating the set of numbers into two. Let's label them i and j for each of the loops:
However, because i and j are linked together with the loop condition j < i, you're effectively selecting a different subset each time i changes:
And then finally, for a given subset you calculate the sum and add that to the cumulatives array:
I can't really think of a way to decide whether you need a nested loop beyond experience, but a good starting point, I suppose, would be to ask:
- how am I going to get access to this item? [with a new loop]
Because at the root of it all, with a single loop, you can map*, or get access to all the elements in the set, but you only have access to them one at a time. With nested loops, you can have access to the original element you were "holding", as well as an entire set of items again, but with access to those elements one at a time as well. That's why we used a variable to store the cumulative sum at each stage, because we can only access the elements of the set one by one even in the inner loop. In other words, we're reducing the inner set (j) into the sum of all the elements it covers.
Whether you need to reduce or not depends on the task at hand. For finding the cumulative sum, we needed to reduce all the elements in the inner loop into one item. But in the case of a sorting operation, or a collision detection problem, there's no reduction involved - just accessing/mapping. Take the collision detection example - we need to check if all the balls collide with each other. Let's ask the question from earlier.
- How am I going to get access to [each ball in the array]?
You need 1 loop variable to get access to each ball in the array.
for (var i:Number = 0; i < balls.length; ++i) {
var ball = balls[i] // a single ball
}
We now want to check whether this ball collides with the rest. How do we get access to the other balls? With a nested loop. You can't use a separate one because then you no longer have access to "each" ball, only the last one:
for (var i:Number = 0; i < balls.length; ++i) {
var ball = balls[i] // a single ball
}
for (var j:Number = 0; j < balls.length; ++j) {
var otherBall = balls[j]
if (ball.hitTest(otherBall)) {
trace("hit") // won't work as expected - "ball" only refers to last ball in balls array
}
}
So if you use a nested array, you now have access to all the balls, as well as the current ball. But because "all the balls" also includes the current ball, you have to exclude the current ball by adding that condition:
for (var i:Number = 0; i < balls.length; ++i) {
var ball = balls[i] // a single ball
for (var j:Number = 0; j < balls.length; ++j) {
var otherBall = balls[j]
if (ball != otherBall) { // excludes "ball" from the set of balls to check for collision
if(ball.hitTest(otherBall)) {
trace("hit") // works now
}
}
// n.b. above two ifs usually written as "if (ball != otherBall && ball.hitTest(otherBall)) {}"
}
}
*mapping in programming generally refers to a function that transforms the elements of one array into another, but here I am using the definition, "to collect".
At 3/19/23 01:40 AM, Nabella wrote:
Oh my God, this video is making my brain melt.
But like, thank you for exposing it to me.
It's missing the earlier context where they first introduced bubble sort (which was covered in previous videos), so maybe it's not the best. But if visualizations help, then you might find better videos on youtube covering nested loops.
Also, I can't think of very many situations where you need three nested loops. They do exist (e.g. in matrix multiplication - can you see why?) but they're rare, so reconsider if you really need three loops for a particular task.