[Python-ideas] Where did we go wrong with negative stride?

Joshua Landau joshua at landau.ws
Mon Oct 28 18:15:19 CET 2013


Apologies for the terrible post above; here it is in full and not
riddled with as many editing errors:

On 28 October 2013 06:33, Chris Angelico <rosuav at gmail.com> wrote:
> On Mon, Oct 28, 2013 at 10:45 AM, Greg Ewing
> <greg.ewing at canterbury.ac.nz> wrote:
>> Neal Becker wrote:
>>>
>>> One thing I find unfortunate and does trip me up in practice, is that
>>> if you want to do a whole sequence up to k from the end:
>>>
>>> u[:-k]
>>>
>>> hits a singularity if k=0
>>
>>
>> I think the only way to really fix this cleanly is to have
>> a different *syntax* for counting from the end, rather than
>> trying to guess from the value of the argument. I can't
>> remember ever needing to write code that switches dynamically
>> between from-start and from-end indexing, or between
>> forward and reverse iteration direction -- and if I ever
>> did, I'd be happy to write two code branches.
>
> If it'd help, you could borrow Pike's syntax for counting-from-end
> ranges: <2 means 2 from the end, <0 means 0 from the end. So
> "abcdefg"[:<2] would be "abcde", and "abcdefg"[:<0] would be
> "abcdefg". Currently that's invalid syntax (putting a binary operator
> with no preceding operand), so it'd be safe and unambiguous.

Agreed in entirety. I'm not sure that this is the best method, but
it's way better than the status quo. "<" or ">" with negative strides
should raise an error and should be the recommended method until
negatives are phazed out.

*BUT* there is another solution. It's harder to formulate but I think
it's more deeply intuitive.

The simple problem is this mapping:

list:  [x,  x,  x,  x,  x]
index:  0   1   2   3   4
       -5  -4  -3  -2  -1

Which is just odd. But you can stop thinking about them as *negative*
indexes and start thinking about NOT'd indexes:

       ~4  ~3  ~2  ~1  ~0

which you have to say looks OK.

Then you design slices around that.

To take the first four elements:

#>>>
"0123456789"[:4]
#>>> '0123'

To take the last four:

#>>>
"0123456789"[~4:] # Currently returns '56789'
#>>> '6789'

For slicing with a mixture:

"0123456789"[1:~1] # Currently returns '1234567'
#>>> '12345678'

"0123456789"[~5:5] # Currently returns '4'
#>>> ''

So the basic idea is that, for X:Y, X is closed iff positive and Y is
open iff positive. If you go over this in your head, it's quite
simple.

For ~6:7;
START: Count 6 from the back, looking at the *signposts* between
items, not the items.
END: Count 7 forward, looking at the *signposts* between items, not the items.

Thus you get, for "0123456789":

"|0|1|2|3|4|5|6|7|8|9|"
             S     E

    and thus, obviously, you get "456".

    And look, it matches our current negative form!

"0123456789"[-6:7]
#>>> '456'

Woah! *BUT* it works without silly coherence problems if you have -N,
because ~0 is -1!

אלעזר said the problem was with negative indexes, not strides, so it's
good that this solves it. So, how does this help with negative
*strides*? Well, Guido's

#>>>
"abcde"[::-1]
#>>> 'edcba'

would be hopefully solved by

"abcde"[:0:-1] # Currently returns 'edcb'
#>>> 'edcba'

because you can just *inverse* the "X is closed iff positive and Y is
open iff positive" rule.

Does this pan out nicely?

Really, we want

"abcde"[2:4][::-1] == "abcde"[4:2:-1]

which is exactly what happens.

I'm thinking I'll make a string subclass and try and "intuit" the
answers, but I think this is the right choice.

Anyone with me, even partially?


More information about the Python-ideas mailing list