John Russell wrote:

So, my question is this, and I realize that this is *very* basic - what is
going on with the last element? Why is it returning one less than I think it
logically should. Am I missing something here? There is not much of an
explanation in the book, but I would really like to understand what's going
on here.


If the book really doesn't explain this (as opposed to you just having missed it), that's a fairly serious lack!

Slicing in Python uses what is called "half-open intervals". There are four obvious ways to count slices:

(1) Closed interval:
    both the start index and the end index are included.

(2) Open interval:
    both the start index and end index are excluded.

(3) Half-open interval:
    the start index is included, and the end index excluded.

(4) Half-open interval (reversed sense):
    the start index is excluded, and the end index included.


Python uses #3 because it is generally the most simple and least error-prone. Essentially, you should consider a slice like sequence[a:b] to be equivalent to "take a slice of items from seq, start at index=a and stop when you reach b". Because you stop *at* b, b is excluded.

Indexes should be considered as falling *between* items, not on them:

0.1.2.3.4.5.6.7.8
|a|b|c|d|e|f|g|h|

Run an imaginary knife along the line marked "4", and you divide the sequence abcdefgh into two pieces: abcd and efgh.

Why are half-open intervals less error-prone? Because you have to adjust by 1 less often, and you have fewer off-by-one errors. E.g.

* Take n items starting from index i:

  Half-open: seq[i:i+n]
  Closed:    seq[i:i+n-1]

* Length of a slice [i:j]:

  Half-open: j-i
  Closed:    j-i+1

* Dividing a sequence into two slices with no overlap:

  Half-open:
    s = seq[:i]
    t = seq[i:]

  Closed:
    s = seq[:i-1]
    t = seq[i:]

* Splitting a string at a delimiter:
  s = "abcd:efgh"

  We want to split the string into two parts, everything before
  the colon and everything after the colon.

  Half-open:
    p = s.find(':')
    before = s[:p]
    after = s[p+1:]

  Closed:
    p = s.find(':')
    before = s[:p-1]
    after = s[p+1:]

* Empty slice:

  Half-open: seq[i:i] is empty
  Closed:    seq[i:i] has a single item


So half-open (start included, end excluded) has many desirable properties. Unfortunately, it has two weaknesses:

- most people intuitively expect #1;
- when slicing in reverse, you get more off-by-one errors.

There's not much you can do about people's intuition, but since reverse slices are much rarer than forward slices, that's a reasonable cost to pay.



--
Steven
_______________________________________________
Tutor maillist  -  Tutor@python.org
To unsubscribe or change subscription options:
http://mail.python.org/mailman/listinfo/tutor

Reply via email to