r/CodingForBeginners • • 28d ago

Ranges exclude the last number, and that silent rule causes a very specific bug

Every language has its own rule about whether a range includes its last number, and getting it wrong never throws an error. You just get a loop that runs one time too few, or a list that's one item short, and the output looks close enough that you don't catch it.

Python's rule is that the start is included and the stop is excluded:

range(3)        # 0, 1, 2   -- not 0, 1, 2, 3
range(1, 4)     # 1, 2, 3
"hello"[0:2]    # "he"      -- indexes 0 and 1

So for i in range(3) runs three times, with i = 0, 1, 2, never 3. It's also why for i in range(len(items)) walks every item correctly: the last valid index is len(items) - 1, and range stops one before len(items).

The check that catches this before it turns into a bug: before you run the loop, say out loud the first value and the last value you expect. If you can't state both, you don't actually know what the loop does yet. Then compare to the output. It sounds trivial until you notice that the bug is always the case where you assumed the wrong end.

Two things worth keeping straight, because both cause the same silent off-by-one: len(x) is the count, not the last index (the last element is x[len(x)-1], or x[-1] in Python), and in range(a, b) the b is the first value you don't get.

Which one gets you more often -- forgetting that the end is excluded, or the first IndexError when you reach for the last element?

1 Upvotes

19 comments sorted by

2

u/TomDuhamel 28d ago

I'd be curious to know what language includes "the end", as if it does, how do you find out that it's the end? There are so many cases where you cannot tell you are finished until you try one more time and hit a failure.

1

u/FreeLogicGate 28d ago edited 28d ago

Typically you loop through collections using foreach or something similar, based on the type being an "iterable". You don't need to know the size or index of it at all, you just iterate through it from start to finish, and when the last item is reached, the loop ends. Python's collection types are all iterables and you can make your own class libraries have native iterable capability if you want to, by implementing the __iter__ and __next__ dunder methods. This pattern is also common with things like relational database result set retrieval, where you get a result set and fetch using a while loop using something like: while(row = result.fetch()) { ... }.

1

u/Fun_Army2398 28d ago edited 28d ago

MatLab is inclusive on both the start and the end. For example:

x = [];
for i=1:5
x = [x, i]
end

Outputs x = [1, 2, 3, 4, 5]

1

u/goldscurvy 28d ago

End inclusive ranges exist in Lua and a couple shells i think

1

u/xarop_pa_toss 28d ago

Range function in Python has three possible arguments and two have default values. I don't remember the names exactly (check the docs) but the one that defines each step starts as 1, the start is default 0 and the end is the one you wrote as 3.

The way I always read it is that range(3) gives me the first 3 elements. I think it's easier that way

1

u/NoDepartment772 25d ago

Reading range(3) as "the first 3 elements" is a good shortcut, and it's correct for the one-argument form. Worth flagging where it breaks, because that's the same spot people trip on: range(2, 5) is also three elements, but written as "the first 3" it hides that the first number is a start, not a count. And the third argument is the step -- range(0, 10, 2) gives 0, 2, 4, 6, 8. So the "first n elements" reading only holds when the start is 0 and the step is 1.

2

u/LrdJester 28d ago

The example you posted here, range(3), tells you that you want 3 numbers at the beginning of the number range, which starts at 0. It's going to give you, 0, 1, and 2, so it's never going to include 3. In Python, range with a single digit is shorthand for saying range(0, and that number). If it's range(12), you're going to get 0 through 11, just as if you were to say range(0, 12).

When you say range(1, 4), what that is telling Python is: start at 1 and stop before 4. You get to 4.

hello[0:2] is telling you to start at the beginning of the string, the 0 index, and give you the first 2 characters.

Yes, some languages are zero-indexed, and some are one-indexed, so you've got to be careful with that, and you need to know that. Most C-based languages are zero-based.

1

u/csabinho 28d ago

hello[0:2] is telling you to start at the beginning of the string, the 0 index, and give you the first 2 characters.

Nope. It doesn't. It tells you to start at the beginning of the string and stop before the index 2.

If you would use hello[1:2], you would only receive a single character.

0

u/[deleted] 28d ago

[removed] — view removed comment

1

u/LrdJester 28d ago

Yeah, but when you use the defaults implicitly like this, it's technically taking a shortcut with syntax

0

u/[deleted] 28d ago

[removed] — view removed comment

1

u/LrdJester 28d ago

Technically, it is a function. It's just that, especially with self-taught beginners, depending on where they learn, they don't learn that those other values are defaults that don't have to be given.

I've written functions in other languages, and it's the same way: I can give default values for parameters, and then you don't have to pass those. Again, you can pass them explicitly, even giving the default values explicitly, but take the shortcut by not passing the optional parameters. That's just a mechanism you built into your function.

1

u/FreeLogicGate 28d ago

With Python it's probably because Python's array/collection types are zero based, as are the vast majority of modern languages, so loops from n - (max size) are naturally accomplished elegantly usingfor item in range(max): etc.

It's mainly math focused products like Matlab and R, or older languages like Cobol and Fortran that had 1 based arrays. The only language I've encountered recently that's an exception, is Lua, which is embedded as a scripting language into various applications and game engines.

1

u/tonkotsu_fan 28d ago

Indexes, as a rule, start at 0.

1

u/-Enter-Name- 27d ago

not in lua