17

I am currently teaching some python programming to some fairly young students. One thing I want them to learn is how to write a for-loop.

So far, the way I have shown it to the students is like this:

for i in range(1,11):
    print(i)

which gives a loop where i goes from 1 to 10.

My problem is that it seems strange to students that they need to write 11 as the second argument to range(1,11) when they want the loop to go up to 10. The students find this confusing.

In C/C++ and related languages, such a loop can be written like this:

for(int i = 1; i <= 10; i++)
    { /* do something */ }

It seems to me like the C++ way of expressing the loop is more intuitive since in that case I can explicitly write 1 and 10 which are the first and last values that I want the loop variable to take.

When working with for-loops in python, I end up telling the students something like "we just have to accept that we need to write 11 when we want the loop to go to 10, it is a bit annoying but you just have to learn that the range function works in that way". I am not happy about that; I want them to learn that programming is fun and I am afraid this kind of thing makes it less fun.

Since python is often described as a language emphasizing readability, I suspect there is a nicer way to express a for-loop, a way that would cause less confusion for my students.

Is there a better and/or less confusing way to express this kind of for-loop in the python language?

Elias
  • 913
  • 6
  • 22
  • 5
    Why don't you just explain them the range() function? The upper limit (2nd argument to the function) is not included in the generated numbers because it is a upper limit; the function generates numbers up to this limit. – danrodlor May 08 '19 at 12:31
  • 5
    It's not just `range()` - Python uses half open intervals like this everywhere. See https://www.quora.com/Why-are-Python-ranges-half-open-exclusive-instead-of-closed-inclusive for a discussion of why. – brunns May 08 '19 at 12:35
  • 4
    I've been programming in C for more than 30 years and I very, very rarely write a for loop the way you have done. Usually I am going to use the loop variable as an array index, which means it must start from zero. For the same reasoning, I often write a python for loop as `for n in range(10)`, which loops ten times. This concept would be easier to understand if you start with that expression, instead of the `range(1,11)` idiom. Don't teach your students that using 0 as a list index is some kind of weird fetish. It's perfectly natural once you do it a few times. – Paul Cornelius May 20 '19 at 10:09
  • 9
    What a lot of people also mentioned here and @Paul as well in the comments. Just use range(10) but explain it's starts on zero. If you want to print the numbers 1 to 10, just use print(i + 1). You still have to explain the zero based nature but it's more logic to use range(10) then range(1,11) EDIT: Also never say something like, "just because it is". It has it's reason, try to explain them. – Wimanicesir May 21 '19 at 14:28
  • I would suggest going with your own array rather than using range. Since it looks like you are trying to avoid the confusion of zero based counting. The goal of this lesson is to teach for loop; focus on *only* that. Introducing range would give added complexity to the lesson (depending on the age of the kids). – Marcel Wilson May 23 '19 at 11:24
  • For questions like this, please try [cseducators.se]. – Karl Knechtel Jul 30 '22 at 02:34

14 Answers14

17

Remind them that there is a reason the range function works this way. One helpful property of it is that the number of times the loop will run is equal to the second argument of range minus the first argument.

I think people get really hung up on this, but the fact is for loops in Python are very different than from C. In C, for loops are basically a wrapper around a while loop.

These two examples should help show the difference between how loops work in C versus python.

# for(int x=1; x <= 10; x++)
x = 1
while x <= 10:
    print(x)
    x += 1


i = [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]  # range(1, 11)
for x in i:
    print(i)

But honestly, the real problem here is that all loops and arrays are easier to understand and work with if they start at zero, not one. Please consider adjusting your examples to start at zero.

This way, if you want to loop 10 times, you use the number 10.

   # for(int x=0; x < 10; x++)
x = 0
while x < 10:
    print(x)
    x += 1


i = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]  # range(10)
for x in i:
    print(i)
soundstripe
  • 1,454
  • 11
  • 19
  • 2
    I think that comparing the loops to another programming language may not be the ideal way to teach young students (as written in the question). – Gábor Fekete May 22 '19 at 10:12
17

You could show them this code for a better understanding:

start = 1
length = 10
for i in range(start,start+length):
    print(i)

There is also another feature that works like this, it's called slice.

Gábor Fekete
  • 1,343
  • 8
  • 16
  • 1
    I think that's the option to go for. Perhaps first introducing the students to `for i in [1,2,3]:` (and variations of this), and then asking them *where* this list of number *starts* and *how long* it is. Transferring `[1,2,3]` to `range(1,1+3)` should make perfect sense then, I'd presume. – Asmus May 22 '19 at 19:02
8

I believe there are two simple ways to answer the question. 1) One way to explain this answer is by using mathematical notation half closed interval [a,b). In this interval, one endpoint is included (In this example it is 'a' ) but not the other endpoint ('b'). So for your example,

for i in range(1,11):
     print(i)

(1,11) is a half closed interval where a and b are 1 and 11 respectively.

2) You can also explain using the following examples

    for i in range(1,11)  //in python 
        {do something}

    for(int i=1;i<11;i++)  //in C++
        {do something}

In both of these case, i iterates from 1 to 10. This seems more intuitive to me.

Saurav Rai
  • 2,171
  • 1
  • 15
  • 29
  • I really like the "half-open interval" explanation for people who already know about intervals (scientists, mathematicians). For those unfamiliar with intervals I find I end up spending a lot of time explaining what intervals are. – aschmied May 20 '19 at 16:21
  • I totally agree with your concern. You can read it in [link](http://mathworld.wolfram.com/Half-ClosedInterval.html). Sorry for the late post. For those who are not well versed with Mathematics, can understand using the second explanation. – Saurav Rai Jun 08 '19 at 10:22
5

Show them the two C++ variants:

# using <= operator
for(int i = 1; i <= 10; i++)
    { /* do something */ }

# using < operator
for(int i = 1; i < 11; i++)
    { /* do something */ }

And tell them that python's range function operates like the second.

T Burgis
  • 1,395
  • 7
  • 9
4

Your students will benefit from learning early that most programming languages use zero-based indexing. The easiest way to understand Python's range() function is to use it with only one argument.

for i in range(10):
    # will iterate exactly 10 times with i going from 0 to 9

In a zero-based world, this makes perfect sense.

Python's range, in interval notation, has an exclusive upper bound: [0..[10 as opposed to the inclusive interval that non-computer engineers use by default [1...10]

Given this, the parameter given to the range function is understood to be the exclusive "stop" value.

When you use an extra parameter to provide a starting point, it would be inconsistent to suddenly treat the stop value differently. This would make range(0,10) behave differently from range(10) and THAT would be very confusing.

The main difference with C is that range() implies a "Less than" comparison instead of "Less or equal":

 for i in range(A,B):   # for (i=A;i<B;i++)
     ...

You could provide your students with an "inclusiveRange" function to get them started without grasping the zero-based concepts.

 def inclusiveRange(start=0,end,step=1):
     return range(start,end+1,step)

They could use that instead of range()

 for i in inclusiveRange(1,10):
     ... # will iterate from 1 to 10 inclusively

The drawback is that the students will have to "unlearn" for loops when they start using list indexes or compute positions in a coordinate system. You could save them a lot of - 1 and off by one in the future by getting them on board with zero-based indexing today.

Alain T.
  • 40,517
  • 4
  • 31
  • 51
4

My problem is that it seems strange to students that they need to write 11 as the second argument to range(1,11) when they want the loop to go up to 10. The students find this confusing.

The confusion comes not from the for statement, but from the range. What you need to do is to split your explanation into two parts: first is that for iterates it's argument, and does not care about the content of the argument. So

for i in [1,2,3]:
    {*do something*}

iterates on the list with 3 elements.

Now, range is defined as a half-open interval of integers a <= x < b because this has many nice mathematical properties, like

len(range(a, b)) == b - a
len(range(a)) == a
range(a, a) == []
range(a, b) + range(b, c) == range(a, c)

and if the definition of the range would be a closed interval, the programmers would have to make adjustments with -1 here and there.

This is a nice blog post on the subj.

igrinis
  • 12,398
  • 20
  • 45
3

It's not "more" or "less" understandable, it's about how you describe it:

  • for x in something: iterates over values from iterable something as variable x. So what you need is to explain "what is returned by range(...)".
  • range(start, end,...) returns an object that produces sequence of integers from start (inclusive) to stop (exclusive). Just show them help(range)

And you what you ask is "how to make for-loop more like in C?"

There are some ways to do this:


def nrange(start, num_elements, step=1):
    """Similar to `range`, but second argument is number of elements."""
    return range(start, start + step*num_elements, step)


def inclusive_range(start_or_stop, stop=None, step=1):
    if stop is None:
        start = 0
        stop = start_or_stop+1
    else:
        start = start_or_stop
        stop = stop + step
    return range(start, stop, step)


_default_update_func = lambda item: item+1
def c_like_iterator(start, test_func, update_func=_default_update_func):
    cur_value = start
    while test_func(cur_value):
        yield cur_value
        cur_value = update_func(cur_value)

for i in nrange(1, 10):
    print(i)

for i in inclusive_range(1, 10):
    print(i)

for i in inclusive_range(10):  # this will give 11 elements because both 0 and 10 values are included
    print(i)

for i in c_like_iterator(1, lambda x: x<=10, lambda x: x+1):
    print(i)

for i in c_like_iterator(1, lambda x: x<11, lambda x: x+1):
    print(i)

for i in inclusive_range(1, -10, -1):
    print(i)

imposeren
  • 4,142
  • 1
  • 19
  • 27
2

Try this:

for i in range(10):
    print(i+1)

or

list_of_numbers = range(1,11)

for number in list_of_numbers:
    print(number)
Richard K Yu
  • 2,152
  • 3
  • 8
  • 21
2

It is important to make them understand the standard way that list and array start always by 0. In order to make it easy to understand, show them the code below, which would be easy to understand why an array start on the index 0. Do not avoid complexity, simplify it instead.

for i in range(10):
    i+=1
    print(i)

or

for i in range(10):
    print(i+1)
Jonathan Gagne
  • 4,241
  • 5
  • 18
  • 30
2

The problem isn't understanding the for loop, it is understanding the range excluding the endpoint. One way to see it is regarding parts of the range as fence posts and segments; building a "fence" with range(10) gives you a fence with 10 fence posts == |

|-|-|-|-|-|-|-|

However, the numbers you see count the fence segments == - you have to the left of the fence post (|), which would be 0-1-2-3-4-5-6-7-8-9

If you add a start, range(1,10), it still builds a fence with 10 fence posts, but your indexes only look at everything with at least 1 fence segment, so the first fence post is not part of your range. But as your indexes start at 0 for the first fence post you look at, you get 1-2-3-4-5-6-7-8-9; still no 10 because our original fence was only 10 posts long. So either you build a fence with 11 posts and ignore everything with less than 1 segment (range(1,11)) or you build a fence with 10 posts and pretend there is another segment to the left of it (range(0,10)+1).

Changing the step size to more than 1 just means you only count every 2nd / 3rd / etc fence post, starting at the first, so if there are more fence posts left but none more to count, the rest at the end are cut off and ignored.

J Lossner
  • 129
  • 10
2

I think one of the most important concepts in computer science learning is to understand how array indexing works. You should spend some time explaining this concept to the students, in a lucid manner with simple examples. Since LISP, almost all programming languages start with Zero-based numbering. Python and C++ are no different. In Python, simple lists can be used as examples to illustrate the concept.

#a simple list
In [15]: list1 = [1,2,3,4,5,6,7,8,9,10,11]
In [23]: len(list1) #number of items in the list
Out[23]: 11
In [26]: list1[0] #the first item in the list
Out[26]: 1
In [25]: print("list1 starts at index {}. The number at this index is {}.".format(list1.index(1), list1[0]))
list1 starts at index 0. The number at this index is 1.
In [37]: list1[10] #the last item in the list
Out[37]: 11
In [19]: print("list1 ends at index {}. The number at this index is {}.".format(len(list1)-1, list1[-1]))
list1 ends at index 10. The number at this index is 11.

As you will observe, the 'number' values are ahead of the 'index' values by 1. What if the list list1 started with 0 and ended with 11, then the index values and 'number' values would be the same but the number of items in the list will increase by 1 and this is because we included 0. Hold that thought for a moment as we'll need this:

In [29]: list2 = [0,1,2,3,4,5,6,7,8,9,10,11]
In [31]: len(list2) #total number of items in list
Out[31]: 12
In [32]: list2[0]
Out[32]: 0
In [35]: list2[11]
Out[35]: 11

Remember the syntax for range() according to the docs is: range(start, end, step). We will ignore step for this discussion.

So, now if I want to generate a similar list as list2 using range(), I can use the above information to form a generalized syntax for range():

Syntax: range(start, len(list2)+start), where start is the first item in the range, 
        list2 is a list and len(list2) is its size and the end is len(list2) + start

To get this into a list, I simply pass the above as an argument to the list() function. Its not important to get into the nitty gritty of this function, and used only for illustration. Thus, we get:

In [39]: list3 = list(range(0,len(list2)+0))    
In [40]: list3
Out[40]: [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11]    
In [41]: list2 == list3 #both lists are equivalent
Out[41]: True
In [42]: len(list3)
Out[42]: 12          #same number of items as list2
In [46]: for i in range(0,len(list3)+0): #can replace list3 with list2 to get same results
    ...:     print(i, end=' ')
        ...:

0 1 2 3 4 5 6 7 8 9 10 11   #Output         

The for loop iterates by index positions. So, i starts at index position 0 for item 0(start of range) and goes on till index position 11 for item 11(end of range = 11 + 0).

We can also verify the above syntax for list1 that has start as 1 and length as 11.

In [6]: list(range(1,len(list1)+1))
Out[6]: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11] #same as list1 above
In [15]: for i in range(1,len(list1)+1):
    ...:     print(i, end=' ')
    ...:
1 2 3 4 5 6 7 8 9 10 11

Again, the for-loop start at index position 0 as usual which has item 1(start of range) and ends at index position 10 which has item 11(end of range = 11 + 1).

Now, let's look at how this concept also applies to C++. We use the same list2 which has 12 items ,i.e. len(list2) = 12. Again, its not important to get into the details of everything in the following example if the students are not quite familiar with arrays. This is just for illustration purposes:

#include <iostream>

using namespace std;

int main()
{
    int list2[12] = {0,1,2,3,4,5,6,7,8,9,10,11};
    std::cout << "Length of array list2 = " << (sizeof(list2)/sizeof(*list2)) << std::endl;
    for(int i=0;i<(sizeof(list2)/sizeof(*list2));i++)
    {
        cout<<i<<' ';
    }
    return 0;
}

//Output:
Length of array list2 = 12                                                                                            
0 1 2 3 4 5 6 7 8 9 10 11  #Output

Notice i<(sizeof(list2)/sizeof(*list2)) makes sure that the final item is retrieved from the upper bound of the array, i.e. item 11 from index=12-1=11. This is exactly what the range() function does. If I do i<=(sizeof(list2)/sizeof(*list2)), that would also print 12 in the output which is not an item in list2 and will go beyond the array's upper bound. In Python, the range() function is more explicit about the array boundaries. So, if I override the range() syntax above and allow the for loop to iterate from 0 to an item beyond list2 by incrementing end by 1, I can print that item explicitly:

In [13]: for i in range(0,len(list2)+1):
    ...:     if i>=len(list2):
    ...:         print("\nI am outside of list2: {}".format(i))
    ...:     else:
    ...:         print(i, end=' ')
    ...:
0 1 2 3 4 5 6 7 8 9 10 11
I am outside of list2: 12

What we understand from this discussion is that it is important to understand for-loop iteration as an index-based operation and the range(start,end) is a Python built-in function that merely composes values between the start & end range, inclusive of the start value and exclusive of the end value. Consider the total number of values traversed including the item at start(len(total) + start) as the end of the range. The for-loop implementation is independent of this and only considers index positions both in Python and C++.

amanb
  • 5,276
  • 3
  • 19
  • 38
1

Python for loops operate differently from traditional C style for loops as explained here. They function more like "for each" loops found in other languages or an iterator method. The range function is used in your example is just a convenient way to create a collection of integer objects to iterate over. The reason the "start" is included and the "stop" is not included is summed up here pretty well.

When it comes to explaining this to new programmers I would break this down and explain how the for loop operates and then explain how the range function can be used to supply the for loop with a set to iterate over.

Example: The for loop is used to execute the following code block for each item in a collection:

collection = [1, 2, 3]
for collectionMember in collection:
    print(collectionMember)

Next explain how the range function can be used to create a collection of integers.

An easy way to create a collection of integers that we can use to loop over is using the range() function:

collection = range(1,4)
print(collection)
for collectionMember in collection:
   print(collectionMember)

I know that this is not how for loops work in other languages but In my opinion if you are teaching them to program in python it would be easier to first teach them how the python versions work and then once they have a firm grasp on that explain how loops work in other languages and how that is different.

tmozden
  • 11
  • 2
1

I'm going to guess the kids are elementary school age and the thing to remember with teaching is keep it simple. Give them tiny nuggets of information at a time.
You want to teach them for loops? Teach them only about the for loop.

Assuming they know about strings and lists maybe go with this type of example:

somelist = ['a', 'b', 'c', 'd', 'e']
for letter in somelist:
    print(letter)
Marcel Wilson
  • 3,842
  • 1
  • 26
  • 55
0

From reading your post. I think everything can not be generalized. Separate things have their own way. programming concept are same but in different language their representation or syntax are different. So, If I were you than I will share the concept and than I will represent those concept in language syntax.

for-loop concept are same. (Initialize, condition, increment/decrement) Now If you want to write it in python than you can write. As for example.

start, stop, step = 1, 11, 1
for i in range(start, stop, step):
    print(i, end=', ')
# Output: 1 2 3 4 5 6 7 8 9 10 
  • start: Starting number of the sequence, default=0. (initialize)
  • stop: Generate numbers up to, but not including this number. (condition)
  • step: Difference between each number in the sequence, default=1. (increment/decrement)

Already python for-loop examples are given in other's answer. Thank you.

R.A.Munna
  • 1,699
  • 1
  • 15
  • 29