Why We Start Indexing from 0 in Computer Science
cs.utexas.edu
cs.utexas.edu
It's extremely common to want to refer to some dynamic offset of a fixed location in memory, like if you have an array of equal-length items stored consecutively. If you have two literal addresses called Start and Index, you'd like to be able to say something like "Start[Index]" and have it mean "Read from Index, and whatever number is there, move that many spaces forward from Start, and give me that". It's handy that if Index is a cleared region of memory, you can say "Start[Index]" and have it mean the same thing as just "Start". More importantly, if "Start[Index]" only meant "Start" when Index was 1, that would imply that if Index were 0 that Start[Index] would refer to the spot right before Start, which is not only useless but outright dangerous.
But like many features of programming languages and operating systems (case-sensitivity, global menu bar, python's colon...), zero-based indexing is something that made perfect sense in the context and time in which it was originally designed and implemented, but it's really no longer necessary in most cases today, and only survives because of tradition and blind cargo cult-like copying of what others did before.
Traditions are not always arbitrary. Don't dismiss them until you're damned sure why they've survived, especially when you know for a fact that quantitative fields filled with high-order geniuses haven't bothered to scrap them. "Cargo-cult" thinking is a pernicious influence, but it's exceptionally rare that it's the complete explanation of anything.
myarray[2] doesn't mean 'myarray at time 2'
Since the latter are often used to approximate the former, this is quite handy.
See page 7 of this presentation: http://mvdirona.com/jrh/TalksAndPapers/GuidoVanRossum_21_yea...
Like a ruler, the array has markers (ticks) going from 0 to N, and the array elements are stored between ticks. So the index numbers simply refer to points, while the array elements have a "one-dimensional" extent.
With that mental model, starting at 0 falls into place very naturally.
That is going to make the "the 2nd item is number 1" thing SO much easier to explain when teaching. Thanks! (And it's good to have the rest of the reasoning there to answer the particularly curious students...)
Definitely falls into the "wish I'd thought of that" and "wish I'd heard someone say it that way 5 years ago" camps.
"I've never really agreed that numbering should start at zero (or at one), I think people use indices way too much and it gets in the way of clarity. I much prefer whole array or list operations with no fiddling with indices and off by one errors. I like Haskell's array API, for example. You can index by whatever is natural in each case and you can always get the whole list of valid indices for an array x by using "indices x". I think that's much nicer, and it's also what D is doing: instead of having a(n implicitly paired) starting and ending iterator to indicate a range of positions like the C++ library does, D's library uses a range object. Basically I'm saying you can represent a range of indices as a pair of indices, one pointing to the first valid position and the other pointing past the last valid one, but that's an implementation detail you don't need to expose, make a range abstraction and you'll be happier."
"I should also point out that mathematicians don't number at zero unless there is some advantage (the default is to start at one), but more importantly, the preferred style in mathematical arguments is to avoiding fiddling with indices as much as possible (since it's so easy to mess something up working at such a low level of abstraction)."
I disagree. While this may depend a little on which areas of mathematics you study, I've found that in situations where there is a reasonably clear / natural / non-arbtirary preference, it tends to be for zero-based natural numbers.
On the other hand, situations in which 1-based numbers are used, tend to look more like arbitrary aesthetic preferences. They tend to be situations where no particular `origin' is any better than any other, and one could (despite the ugliness) use 2- or 3-based numbering without adding any additional corner-cases.
My personal favourite `reason' that the set of natural numbers should include zero (and that numbering should start at zero) comes from set theory, where cardinal numbers are equivalence classes of sets of the same size. 0 is the smallest such number corresponding to the empty set. (For numbering sequences, 0 is also naturally the smallest ordinal number).
These kinds of reasons tend to crop up in category theory and other foundational topics too, which are some of the areas of mathematics closest to theoretical computer science.
Interested in counter-examples though; I'm sure at least some exist.
I was talking about a completely different use of numbers: numbering, that is assigning numbers as labels to things. There I think mathematicians on the whole prefer to number starting at 1 (or not to number at all and work with abstract indexing sets). Sometimes it is convenient to number starting at zero if it simplifies some formulas, but usually it doesn't matter.
You'd let N be the number of such objects, and then twenty following pages of text would contain summations from 1 to N. This is more compact than summations from 0 to N-1. And in a certain sense, there's one less "token" to remember (i.e., if you start at 1, you have 1 and N to remember, but when you start at 0, you have N, 0, and N-1 to remember).
The one that's more compact tends to be discipline-specific.
But it is an excellent example of thoughtful reasoning to choose between multiple (seemingly arbitrary) design options. I think we all benefit when language (and API) designers are as thoughtful as this.
Dijkstra deals with what index start with as well, but it comes out as a consequence of which bounds to choose for a range, so it's rather short.
From the article:
When dealing with a sequence of length N, the elements of which we wish to distinguish by subscript, the next vexing question is what subscript value to assign to its starting element. Adhering to convention a) yields, when starting with subscript 1, the subscript range 1 ≤ i < N+1; starting with 0, however, gives the nicer range 0 ≤ i < N.
When I have a five-page document, I number the pages as one, two, three, four, five, and say that the length of the document is five pages. If I have an "empty" document (assuming such a thing makes sense in the real world), it would have length zero, and since there are no elements to assign indices to, I would not assign them numbers.
I have to admit that I much prefer the Lua convention: in a sequence of N items, they are numbered 1, 2...N-1, N. The length of the sequence is equal to the index of the last item in the sequence. "Negative" indices are treated as if one was counting backwards from the end of the sequence, such that the items are numbered -N, (-N)+1, (-N)+2...-2, -1, i.e. the spoken forms "last," "second-to-last," "third-to-last..." This means that 1 to -1 encompasses the whole sequence. This does have the unfortunate side effect that 0 refers to no item, but even when describing an array implemented as a pointer we refer not to the "zeroth" element, but to the first and the last.
This also has an added benefit that the range notation used by Lua's numeric for loops makes perfect sense when compared to the notation used by Python's range statement. In Lua,
for n = 1, #tbl
produces all of the indices in the given table, as does for n in range(len(ls))
in Python, for a list. In Lua, this can simply produce the numbers 1, 2, 3, 4, 5, 6 for a table of six elements, which is intuitive even when using the numeric for to simply generate a series of integers: for n = 1, 6
However, in Python, the fact that range is used to generate the indices also means that range(1, 6) produces 1, 2, 3, 4, 5 ("Where the heck is the 6?") and a simple range(6) produces 0, 1, 2, 3, 4, 5 ("Still no 6, and where did that 0 come from?") To get 1, 2...6, you need range(7) ("What does this have to do with 7?")Dijkstra argued against this by noting that that makes adding 0 to the range impractical, but then he was considering natural numbers, not the half-open ranges we're talking about and using notation for here.
Me, I prefer integers and the a..b style, despite the off-by-one error.
If instead you use 1 <= i <= (NxM), 1 <= row <= M, 1 <= col <= N, then the mappings aren't nearly as clean. Sure, you could hide all this behind some kind of API, eg. Image.getPixel(x,y), but if you work with multidimensional arrays very often, eventually you'll need to marshal arrays back and forth between different containers that use different access APIs, and the simplest common representation to use for this kind of data is as a 1D array, so being able to work with nD data that is stored in a 1D array comes up fairly often.
In mathematics and physics the usage varies. Zero is used when it is really the beginning of something, like t0 for the start time of an experiment. In most other cases lists count from one and up. Matrix elements too are generally denoted from (1,1) to (n,m).
Other languages using 1-indexed arrays: Fortran, Cobol, Smalltalk, XPath, Erlang[0] and Lua (and a bunch of other very old languages like PL/1 or Algol).
All Wirthian languages (Pascal, Oberon, Ada, etc...) use configured index ranges (and provide primitives to query the array for its bounds), but I believe the convention is to use 1-index arrays when there are no special index semantics due to their strings being 1-indexed.
VB has configurable index ranges as well, but defaults to 0-indexed arrays.
You can also "configure" initial array index in Perl by setting `$[` I believe. Doing this will lead to you being beaten to death with ethernet cables by your colleagues. And if it does not, it should.
[0] though direct indexation is rarely used in erlang
Sure, it's a departure from mainstream languages, but in my opinion it's a perfectly sensible one.
Not necessarily. The reasoning in e.g. Python is that the index is not to an element, it's to one of the intervals around an element. The "first interval" (right before the first element) is 0, which naturally leads to -1 being right before the last element, thus arr[-1] being an array's last element, and arr[:-1] being everything but the last element.
Again we fall in the trap of endless discussions about bitheads, numheads and charheads.
The bithead will always think in zeros and ones, he is a hardcore c or c++ programmer, and his brain is damaged beyond repair. Avoid all discussions with that kind of specimen.
The numhead accepts both conventions, he is a python or ruby programmer, practical and gets the job done, he may ask why that weird convention but still goes on using it since it solves all his problems as it is.
The charhead is a vb or js programmer by luck, not by choice, he will never understand why we start from zero and will curse everytime he writes a for loop.
I've been in the three camps luckily escaping without any brain damage (I believe) and after 20 years programming I still prefer to count starting by one, like any human being.
I'm not convinced that people count starting at one.
Count the number of elephants in your bedroom. Assuming that there weren't any, how can you say that you started counting at 1 when the result of said counting was 0?