inclusive-lower-bound, exclusive-upper-bound (was Re: Range Operation pre-PEP)

Alex Martelli aleaxit at yahoo.com
Fri May 11 19:15:24 EDT 2001


"Andrew Maizels" <andrew at one.net.au> wrote in message
news:3AFBA6E0.6ABBCD93 at one.net.au...
    ...
> OK, next question: why does Python start indexes at zero?  Your example
> would work perfectly well if the range returned [1, 2, 3, 4] and the
> list was indexed starting with 1.  Basically, range(4) has to produce a
> list of four items, we just differ on what those items should be.

If indexes started at 1, then maybe so should ranges.  However,
having read the followups to this message, I think there are still
advantages of simplicity and regularity in having arrays (even if
one calls them lists:-) indexed from 0.  My master thesis, lo that
many years ago, included a large program in Fortran IV (1-based
index only) and had to do a lot of +1/-1 twiddling because of
that.  I didn't understand why at the time (having not yet met
Koenig's book "C traps & pitfalls", which introduced me to the
many advantages of half-open ranges -- array indexing being a
case of that), but now I think I do.

Suppose my 1-dimensional array/list needs at some point to
be 'seen' as composed of several adjacent subarrays, each of
length N -- just for example.  OK, what's M, the index in the
array of element I of subarray K?

If everything starts from 0:
    M = I+K*N
nice and simple.  If everything starts from 1:
    M = 1 + (I-1) + (K-1)*N
        = I + (K-1)*N
darn -- an unavoidable '-1'... 1-based indexing just isn't as
arithmetically nice as 0-based when you start having to
compute your indices.

And the reverse, too -- given M and N,
    K, I = divmod(M,N)
with 0-based indexing throughout -- nice, isn't it...?  What
about *one*-based indexing throughout...?  Hmmm, looks
like we'll have to do the -1 dance on M first, then the +1
one on both of the subresults...:
    K, I = divmod(M-1, N)
    K += 1
    I += 1
Doesn't it start to look as if indices WANT to be zero-based,
and coercing them to 1-based is simply a pretty artificial
choice requiring many +1's and -1's strewn around...?

OK, what about the "main diagonal" of the intrinsic 2D
array embedded in my 1D one -- first element of first
subarray, second element of second subarray, etc.  Can
I get the indices of that easily and naturally?  What's the
index M in the big array of the I-th element of this main
diagonal?  Well, when 0-based indexing is being used
throughout, M=I*(N+1) seems right.  When 1-based...:
    M = 1 + (I-1)*(N+1)
Again we have to do a -1 on I to move it from 1-based
to 0-based for the computation, and +1 on the result
to move the natural 0-based one to 1-based.  Just take
care to NOT do more or fewer -1's and +1's than needed
or a bug may emerge...:-).


OK, forget subarrays.  Say we just have two very long
arrays A and B.  We need to consider them starting
from indices IA and IB, and obtain a result by summing
corresponding elements -- the first element in our
result array is A[IA]+B[IB], and so on.  Simple, right?

OK, so, what's the I-th element of our result?
    C[I] = A[IA+I] + B[IB+I]
when all indices are 0-based.  If 1-based, though:
    C[I] = A[IA+I-1] + B[IB+I-1]
darn -- once again the -1 emerges!  Once again we
have to just about "translate" arithmetically-funny
1-based indices to the "natural" 0-based ones, and
so the -1 (or +1, depending).


You will no doubt find some counterexamples too, but
in general I think you'll notice that anytime two indices
need to be added, or other kinds of arithmetic on
indices are required, 0-based indices tend to behave
better, 1-based ones need some 'translation' (-1, +1)
far more often than the reverse case.


Alex






More information about the Python-list mailing list