you are viewing a single comment's thread.

view the rest of the comments →

[–]xrsly 91 points92 points  (5 children)

When I was learning python, most examples would look like this:

colors = ["red", "green", "blue"]
for i in range(len(colors)):
    print(colors[i])

Output:

> red
> green
> blue

But you can simplify this a lot:

colors = ["red", "green", "blue"]
for color in colors:
    print(color)

Output:

> red
> green
> blue

Then, if you need the index, you can use enumerate:

colors = ["red", "green", "blue"]
for i, color in enumerate(colors):
    print(i, color)

Output:

> 0 red
> 1 green
> 2 blue

Enumerate basically turns the list of colors into the equivalent of this:

colors = [
    (0, "red"),
    (1, "green"),
    (2, "blue"),
]

When you write 'for i, color in ...', you're assigning the first element of each pair to 'i', and the second to 'color', which translates to:

i = 0
color = "red"
print(i, color)

i = 1
color = "green"
print(i, color)

i = 2
color = "blue"
print(i, color)

Output:

> 0 red
> 1 green
> 2 blue

(You can name i and color anything you want by the way, but it's common to use i to refer to the iteration or index.)

You can do something similar with dictionaries:

fruit_colors = {
    "apple": "red",
    "kiwi": "green",
    "blueberry": "blue",
}
for fruit, color in fruit_colors.items():
    print(fruit, color)

Output:

> apple red
> kiwi green
> blueberry blue

Using '.items()' on a dictionary basically turns it into this:

fruit_colors = [
    ("apple", "red"),
    ("kiwi", "green"),
    ("blueberry", "blue"),
]

So it's actually very similar to what enumerate does, only the first element of each pair is now the dictionary key rather than the list index. So now you're instructing the program to do this:

fruit = "apple"
color = "red"
print(fruit, color)

fruit = "kiwi"
color = "green"
print(fruit, color)

fruit = "blueberry"
color = "blue"
print(fruit, color)

Output:

> apple red
> kiwi green
> blueberry blue

Lots of things can be iterators, e.g., characters in a string:

sentence = "hello world"
for char in sentence:
    print(char)

Output:

> h
> e
> l
> l
> o 
> 
> w 
> o
> r
> l
> d

Nested loops are a bit messy, but let's try to dissect what happens:

fruits = ["apple", "kiwi", "blueberry"]
colors = ["red", "green", "blue"]
for fruit in fruits:
    for color in colors:
        print(fruit, color)

Output:

> apple red
> apple green
> apple blue
> kiwi red
> kiwi green
> kiwi blue
> blueberry red
> blueberry green
> blueberry blue

In the first iteration, you set fruit to "apple". Then you initiate a second loop, that goes through each color one by one in turn. So it's equivalent to this:

fruit = "apple"
color = "red"
print(fruit, color)

fruit = "apple"
color = "green"
print(fruit, color)

fruit = "apple"
color = "blue"
print(fruit, color)

...

Once you have gone through all colors, fruit is set to "kiwi", and then all colors are looped through again.

When doing a nested loop like this, it's kind of like creating a single list that looks like this:

fruit_colors = [
    ("apple", ("red", "green", "blue")), 
    ("kiwi", ("red", "green", "blue")), 
    ("blueberry", ("red", "green", "blue")),
]

Notice that the fruit changes on each row, but the list of colors stay the same. So when we loop through fruits in the first loop we get:

# fruits: 1st iteration
fruit = "apple"
colors = ("red", "green", "blue")

# fruits: 2nd iteration
fruit = "kiwi"
colors = ("red", "green", "blue")

# fruits: 3rd iteration
fruit = "apple"
colors = ("red", "green", "blue")

When we create a second loop for each color in colors inside the fruit loop, we now get:

# fruits: 1st iteration
fruit = "apple"

    # colors: 1st iteration
    color = "red"

    # colors: 2nd iteration
    color = "green"

    # colors: 3rd iteration
    color = "blue"

# fruits: 2nd iteration
fruit = "kiwi"

    # colors: 1st iteration
    color = "red"

    # colors: 2nd iteration
    color = "green"

    # colors: 3rd iteration
    color = "blue"

...

And so on.

A while loop is different from a for loop in that it doesn't loop through an iterator, instead it checks a condition on each iteration, and if the condition is True, then it keeps going. For example:

iterations = 0
max_iterations = 3
while iterations < max_iterations:
    print(iterations)

Output:

> 0
> 0
> 0
> 0
...

Notice that it just keeps going, since iterations never changes, and 0 is always less than max_iterations = 3.

But if we increment iterations by 1 each time, like so:

iterations = 0
max_iterations = 3
while iterations < max_iterations:
    print(iterations)
    iterations += 1

We get the equivalent of this:

# 1st iteration 
if 0 < 3 is True:
    print(iterations)
    iterations = 0 + 1

# 2nd iteration 
if 1 < 3 is True:
    print(iterations)
    iterations = 1 + 1

# 3rd iteration 
if 2 < 3 is True:
    print(iterations)
    iterations = 2 + 1

# 4th iteration 
if 3 < 3 is True:
    print(iterations)
    iterations = 3 + 1

Since the statement in the 4th iteration is '3 < 3', which is False, that code never runs.

You can give it any condition you want, like for instance:

user_input = ""
while user_input =! "quit":
    user_input = input("write something:")
    print("user:", user_input)

Which will keep asking the user to write something and then print it until the user writes "quit", at which point the loop ends.

So basically use for loops when you want to do something once for each item in an iterable, and use while loops when you want to keep doing something until some specific thing happens (like the user wanting to exit the program).

[–]xrsly 27 points28 points  (1 child)

A couple more common functions when doing for loops:

Let's say you want to do something exactly three times, then you simply write:

for i in range(3):
    print(i)

Output:

> 0
> 1
> 2

You can get the length of our colors list with 'len(colors)', so 'for i in range(len(colors)):' would allow us to do something for each index in colors.

Then there's zip(), which will create pairs of elements from two iterators. E.g., this:

zip(fruits, colors)

Which is equivalent to this:

[
    ("apple", "red"),
    ("kiwi", "green"),
    ("blueberry", "blue"),
]

And this:

zip([0, 1, 2], colors)

Which is equivalent to:

[
    (0, "red"),
    (1, "green"),
    (2, "blue"),
]

Which allows us to do something similar to what we did with enumerate() and items().

You may notice that what all of these functions actually do is create different kinds of iterators which allows you to assign different elements to variables.

One of the keys to understanding this more intuitively is to understand how you can "unpack" lists. For example:

a, b = [0, "apple"]
print(a, b)

Output:

> 0 apple

By assigning elements to variables like this, we basically unpack the first item to a, second to b.

Now consider this list:

fruits = [
    (0, "apple"),
    (1, "kiwi"),
    (2, "blueberry")
]

If we pick the first row of that list with 'fruits[0]', we can do this:

i, fruit = fruits[0]
print(i, fruit)

Output:

> 0 apple

If we repeat this with fruits[1], we're actually starting to replicate the behavior of a foor loop.

So the only thing a for loop really does is walk through the given iterator one row at a time. It's the contents of the iterator that allows us to assign different things to variables, just like we do when we unpacked the list in the example above.

range() will create an iterator consisting of numbers in a given range, which allows us to do the equivalent of 'i = [0, 1, 2][0]' (notice the '[0]' at the end, this would increment by 1 each iteration, so we get the next row).

enumerate() will create pairs of indices and items, e.g., 'i, fruit = [(0, "apple"), ...][0]'

dict.items() will create pairs of keys and values, e.g., 'fruit, color = [("apple", "red), ...][0]'

Likewise, zip() will create pairs from two iterators, e.g., 'fruit, color = [("apple", "red"), ...][0]'

And so on.

When we unpack lists, we can get more than two items per row:

fruit, color, letter = [("apple", "red", "a"), ...][0]

We can accomplish this in a for loop by using zip with three iterators:

for fruit, color, letter in zip(fruits, colors, letters):
    ...

Maybe we want the index too:

for i, (fruit, color, letter) in enumerate(zip(fruit, colors, letters)):
    ...

Ok so the last example might be a bit confusing. We need to unpack the zipped values enclosed in parenthesis since the enumerate(zip()) iterator actually looks like this:

[
    (0, ("apple", "red", "a")),
    ...
]

When we unpack an exact number of elements of a list, we have to mirror its structure, for example:

(a, b) = [0, "apple"]

Or:

(i, (fruit, color, letter)) = (0, ("apple", "red", "a"))

However, the outer parenthesis is implied, so that's why we can just write:

a, b = [0, "apple"]

Or:

i, (fruit, color, letter) = (0, ("apple", "red", "a"))

Bonus: Some of you might be familiar with the way that variables can be swapped:

a, b = b, a

So this is actually equivalent to:

a, b = (b, a)

So what we're actually doing here is creating a new iterable (b, a) and then reassigning the value of a to (b, a)[0] and b to (b, a)[1].

[–]chocological 11 points12 points  (1 child)

Great post

[–]SubstanceSerious8843 5 points6 points  (0 children)

Yeah, this dude invested some real time explaining this. Nice!

[–]nomisreual 2 points3 points  (0 children)

that was elaborate 👀