Ticket #4974 (closed defect: fixed)

Opened 20 months ago

Last modified 20 months ago

[with patch, positive review] eliminate normalize_slice in favor of a standard Python idiom

Reported by: jason Owned by: craigcitro
Priority: major Milestone: sage-3.3
Component: misc Keywords:
Cc: jason Author(s):
Report Upstream: Reviewer(s):
Merged in: Work issues:

Description

Also, it would be good to optimize it if possible.

[04:21] <craigcitro> yeah, file a ticket for that and assign it to me.
[04:21] <jason-> (couldn't get cimport to work...)
[04:22] <craigcitro> well, the pari gen.pyx probably wasn't the best place for that function
[04:22] <craigcitro> so it needs to be moved anyway

Attachments

normalize_size.patch Download (3.5 KB) - added by jason 20 months ago.
normalize_slice.draft Download (1.3 KB) - added by ylchapuy 20 months ago.
draft for the future patch
trac-4974.patch Download (3.9 KB) - added by craigcitro 20 months ago.

Change History

Changed 20 months ago by craigcitro

patch not necessarily ready for review yet.

Changed 20 months ago by craigcitro

  • status changed from new to assigned
  • summary changed from make sage.libs.pari.gen._normalize_slice a miscellaneous function for dealing with slices to [with patch, needs review] make sage.libs.pari.gen._normalize_slice a miscellaneous function for dealing with slices

I think this is good to go.

Changed 20 months ago by ylchapuy

Some questions/remarks. Shouldn't the following always be true ? (at least this is what I understand from the docstring)

normalize_slice( s , size_list) == range(size_list)[s.start:s.stop:s.step]

If no (which is the case actually) this looks quite incoherent to me; if yes, is there a reason not to rewrite this fonction as a one liner?

Changed 20 months ago by ylchapuy

forget the one liner thing it's obviously a bad idea

Changed 20 months ago by craigcitro

Wait, so disregarding whether or not we'd want to implement things that way -- are you still saying there are cases where that isn't true?

Changed 20 months ago by jason

There is an inconsistency with standard python:

[12:12] <jason-> sage.misc.misc_c.normalize_slice(slice(2,None,-1),3)
[12:12] <jason-> gives me 2
[12:12] <jason-> but 
[12:12] <jason-> sage: range(3)[2::-1]
[12:12] <jason-> [2, 1, 0]

Changed 20 months ago by craigcitro

  • summary changed from [with patch, needs review] make sage.libs.pari.gen._normalize_slice a miscellaneous function for dealing with slices to [with patch, needs work] make sage.libs.pari.gen._normalize_slice a miscellaneous function for dealing with slices

Jason Grout just brought up an example in IRC where exactly that would happen. Here's a case:

sage: range(3)[2::-1]
[2, 1, 0]

normalize_slice will not return that, since it interprets a missing stop to be equal to size. This isn't documented as part of the Python semantics, but I think we should switch it to behave the same way regardless. (Also, it'll be easy to implement.)

This is now needs work until I change this.

Changed 20 months ago by craigcitro

Another case brought up by Jason Grout:

sage: range(5)[-6:]
[0, 1, 2, 3, 4]

Changed 20 months ago by ylchapuy

and another one from the docstring...

sage: range(20)[5:8:-1]
[]

Changed 20 months ago by jason

Changed 20 months ago by jason

  • summary changed from [with patch, needs work] make sage.libs.pari.gen._normalize_slice a miscellaneous function for dealing with slices to [with patch, needs review] make sage.libs.pari.gen._normalize_slice a miscellaneous function for dealing with slices

apply normalize_size.patch on top of previous patch. This corrects all errors pointed out above and I believe is faster and simpler as well.

Changed 20 months ago by ylchapuy

  • summary changed from [with patch, needs review] make sage.libs.pari.gen._normalize_slice a miscellaneous function for dealing with slices to [with patch, needs work] make sage.libs.pari.gen._normalize_slice a miscellaneous function for dealing with slices

A lot better, but there are still bugs.

I would recommand to try something like this to test extensively:

def safe_norm(i,j,k,l):
    try:
        return sage.misc.misc_c.normalize_slice(slice(i,j,k),l)
    except ValueError:
        return "error"

def safe_range(i,j,k,l):
    try:
        return range(l)[i:j:k]
    except ValueError:
        return "error"

d=[-5,-4,-3,-2,-1,0,-1,-2,-3,-4,-5,None]
ld=len(d)-1
for r in xrange(500):
    i=d[randint(0,ld)]
    j=d[randint(0,ld)]
    k=d[randint(0,ld)]
    l=randint(-8,8)
    r1=safe_norm(i,j,k,l)
    r2=safe_range(i,j,k,l)
    if not r1==r2:
        print i,j,k,l,r1,r2

Changed 20 months ago by ylchapuy

draft for the future patch

Changed 20 months ago by ylchapuy

I have no clean install of sage to do the real patch, but I hope it helps...

Changed 20 months ago by jason

ylchapuy: nice. I like how you avoided the redundant comparisons in my code.

In scanning through your code, it has a few places where the stuff following the +=size lines should be taken out of the body of the if statement and placed after the if statement instead.

Changed 20 months ago by jason

Okay, I think this makes all of this work redundant. Equivalent functionality (and mostly equivalent or faster times) are found in the implementation:

range(*s.indices(size))

where s is the slice, size is the size of list.

Changed 20 months ago by jason

There are several nice things about the above. First, python handles all of the weird corner cases so that everything is consistent. Second, you can use xrange to get an iterator over the indices instead of the list. Third, you avoid one more function call.

Changed 20 months ago by craigcitro

And fourth, it does more error checking than we were doing (dealing with unnecessarily large size, etc) ...

I think we should do this. One question: should we keep all our doctests? It's like documentation for how you can use slices, which doesn't seem to appear elsewhere. :)

Changed 20 months ago by craigcitro

Changed 20 months ago by craigcitro

  • summary changed from [with patch, needs work] make sage.libs.pari.gen._normalize_slice a miscellaneous function for dealing with slices to [with patch, needs review] eliminate normalize_slice in favor of a standard Python idiom

What Jason says above is exactly right -- there's no point trying to outdo Python, because it already is quite fast, and does all this for us. Plus, we can't miss corner cases this way.

Ultimately, things could get faster with nice Cython support for slices. This isn't wildly pressing, but it should be easy to implement.

Apply only the newest trac-4974.patch. This patch simply removes normalize_slice.

We should make a note about using this for slices somewhere -- apparently, range(*s.indices(size)) is a standard Python idiom (it's in Python in a Nutshell), so we should use it more.

Changed 20 months ago by jason

  • summary changed from [with patch, needs review] eliminate normalize_slice in favor of a standard Python idiom to [with patch, positive review] eliminate normalize_slice in favor of a standard Python idiom

+1 to the code cruft removal and adoption of standard python constructs. I'm opening another ticket to add information about slices to the developer guide.

This patch applies and doctests pass in the file.

Changed 20 months ago by mabshoff

  • status changed from assigned to closed
  • resolution set to fixed

Merge trac-4974.patch in Sage 3.3.alpha0

Note: See TracTickets for help on using tickets.