2

With a Python list

L=[1,2,3,4]

I would like that L[m] = 0 if m is different to 0,1,2,3, ie :

...
L[-2]=0 
L[-1]=0  
L[0]=1
L[1]=2
L[2]=3
L[3]=4
L[4]=0
L[5]=0

and

L[-2:2] = [0, 0, 1, 2]

This won't work with a classical list or array. What is the good way to do this ?

Edit : This is a nice solution (given by one the answer here) :

class MyList(list):
    def __getitem__(self, index):
        return super(MyList, self).__getitem__(index) if index >= 0 and index < len(self) else 0

But I'm still unable to have

L[-2:2] = [0, 0, 1, 2]
Basj
  • 41,386
  • 99
  • 383
  • 673
  • Do you mean that assigning outside the bounds of the list (which negative indices won't necessarily do - they start from the end and work backwards) should create new entries? What should happen between the existing list and new entry? Why not just use a dictionary with integer keys? – jonrsharpe Nov 26 '13 at 13:06
  • So, let me get this straight. You have list `a` and list `b`. What you want to do is change all the elements in list `b` to 0, which are not members of list `a`? – Games Brainiac Nov 26 '13 at 13:07
  • @jonrsharpe Hey Jon! Long time no see. Remember me from Edx? If you've got some time, come over the the Python Chat room. – Games Brainiac Nov 26 '13 at 13:08
  • How about this: http://stackoverflow.com/questions/2574636/getting-a-default-value-on-index-out-of-range-in-python – davekr Nov 26 '13 at 13:09
  • 1
    I tried to implement the slicing thing with `__getslice__` but the syntax `a[-2:2]` passes the arugments `(2,2)` to `__getslice__` because it doesn't support negative arguments, and I don't know how to override this. – askewchan Nov 26 '13 at 15:03
  • 1
    I looked for documentation on `__getslice__` and found that it is deprecated. Instead `__getitem__` should accept a slice object. A slice object seems to have three attributes start, stop and step. – neil Nov 26 '13 at 15:13
  • @neil yep that's what I ended up doing, but didn't realize that `__getslice__` is actually deprecated. It seemed in my testing that `__getslice__` was called when I ran `a[-2:2]` (see bottom of my answer). Maybe because I'm using python 2? – askewchan Nov 26 '13 at 15:27

6 Answers6

6

You can convert L to dict:

In [1]: L=[1,2,3,4]

In [2]: D=dict([(x, y) for x, y in enumerate(L)])

In [3]: [D.get(i, 0) for i in xrange(-3, 5)]
Out[3]: [0, 0, 0, 1, 2, 3, 4, 0]
greg
  • 1,417
  • 9
  • 28
  • hum... `dict` seems to be the right data type for what I want... It it suited for large data amount (thousands of items) ? – Basj Nov 26 '13 at 13:15
  • Why not, a dict as built-in datatype is very fast. – greg Nov 26 '13 at 13:22
1

One of solve it problem is declare your own get function

def get(l, p):
   try:
       return l[p]
   except IndexError:
       return 0

ofc last line can be l.append(0) or something other

Naster
  • 344
  • 1
  • 7
1

You can use a special get function:

def get(l, p):
    return l[p] if p >= 0 and p < len(l) else 0

Or even override the __getitem__ method:

class MyList(list):
    def __getitem__(self, index):
        return super(MyList, self).__getitem__(index) if index >= 0 and index < len(self) else 0

Example:

>>> l2 = MyList([3, 5, 6, 8])
>>> l2[-1]
0
>>> l2[5]
0
>>> l2[2]
6
Maxime Chéramy
  • 17,761
  • 8
  • 54
  • 75
  • With your new class MyList, do you know how we could modify to be able to deal also with ranges like this `l2[-2:2]=[0,0,3,5,6]` ? – Basj Nov 26 '13 at 13:40
  • @Basj Python2 or Python3 ? I might have a solution but it appears to only work for Python3. – Maxime Chéramy Nov 26 '13 at 13:49
  • Also, would `[3, 5, 6]` instead of `[0,0,3,5,6]` be a good solution? – Maxime Chéramy Nov 26 '13 at 13:52
  • I use Python2. With your solution, `l2[-2:2]` gives `[]`. I would like `l2[-2:2]=[0,0,3,5,6]` (not [3,5,6]). Do you have an idea for this ? – Basj Nov 26 '13 at 13:56
  • I still cannot find how to be able to deal with such ranges... Do you have an idea ? – Basj Nov 26 '13 at 14:44
1

You could subclass build-in list to provide default value when accessing list items:

class MyList(list):
    def __getitem__(self, item):
        if isinstance(item, slice):
            step = item.step if item.step else 1
            return [self.__getitem__(i) for i in xrange(item.start, item.stop, step)]
        try:
            value = super(MyList, self).__getitem__(item)
        except IndexError:
            value = 0
        return value

    def __getslice__(self, start, stop):
        return self.__getitem__(slice(start, stop, None))

Example usage:

>> L = MyList([1,2,3,4])
>> L[0]
1
>> L[1]
2
>> L[2]
3
>> L[3]
4
>> L[4]
0
>> L[5]
0
>> L[0:6]
[1, 2, 3, 4, 0, 0]

Credit goes to How to override the slice functionality of list in its derived class

Community
  • 1
  • 1
davekr
  • 2,266
  • 1
  • 26
  • 32
  • With this class MyList, do you know how to deal with ranges and have for example `L[0:6]=[1,2,3,4,0,0]` ? – Basj Nov 26 '13 at 13:42
  • I've updated the answer. It's still a little bugy, but I hope you got the idea. – davekr Nov 26 '13 at 22:17
  • thanks a lot! It works for things like `L[0:6]=[1,2,3,4,0,0]`, but still not for `L[-2:6]` that should give `[0,0,1,2,3,4,0,0]`... I'm rather new into Python classes definition with slices, but I hope I can make it work :) – Basj Nov 26 '13 at 23:03
1

You can use slice objects, which are passed not to __getslice__ but to __getitem__ when using "extended slicing". Then, move the start of the slice to 0, and the stop to len - 1 while keeping track. Then add zeros:

class MyList(list):
    def __getitem__(self, item):
        if isinstance(item, slice):
            s, e = item.start, item.stop
            l = len(self) - 1
            left = -s if s < 0 else 0
            s = max(s, 0)
            right = e - l if e > l else 0
            e = min(e, l)
            return [0]*left + super(MyList, self).__getitem__(slice(s,e,item.step)) + [0]*right
        elif item < 0 or item >= len(self):
            return 0
        else:
            return super(MyList, self).__getitem__(item)

The catch is: you have to force your getslice call to send a slice object, which you can do in one of two ways.

>>> a[-2:2:1]   # the step = 1 is necessary
[0, 0, 1, 2]

or

>>> a[slice(-2,2)]
[0, 0, 1, 2]

Works on both ends:

>>> a[-2:6:1]
[0, 0, 1, 2, 3, 0, 0, 0]

Original attempt

If __getslice__ was passed the actual arguments given by a[-2:2], then this would work:

class MyList(list):
    def __getitem__(self, item):
        if item < 0 or item >= len(self):
            return 0
        return super(MyList, self).__getitem__(item)

    def __getslice__(self, s, e):
        print "input: ", s, e
        l = len(self) - 1
        left = -s if s < 0 else 0
        s = max(s, 0)
        right = e - l if e > l else 0
        e = min(e, l)

        return [0]*left + super(MyList, self).__getslice__(s,e) + [0]*right

But for some reason, a[-2:2] calls a.__getslice(2,2) with both values positive.

>>> a[-2:2]
input: 2 2
Community
  • 1
  • 1
askewchan
  • 45,161
  • 17
  • 118
  • 134
  • interesting, we are close to the result ! – Basj Nov 26 '13 at 15:09
  • I think I'm making progress, actually, using a slice object. brb :) – askewchan Nov 26 '13 at 15:11
  • OK it works now. Mutability of these lists might not behave correctly (as in, `b = a[-2:2:1]; b[0] = 88` might not do what you want it to do. But that's a tougher problem. Also note that I tested this in python 2. – askewchan Nov 26 '13 at 15:25
  • Great ! So it doesn't work with `a[-2:2]`, we really need the `a[-2:2:1]` ? – Basj Nov 26 '13 at 15:30
  • Yes, in my testing, because `a[-2:2]` is equivalent to `a.__getslice__(2,2)` and kills the negative. Perhaps this is different in python 3 (I tested with 2). – askewchan Nov 26 '13 at 15:38
  • I've checked in 3.3 and `__getitem__` is always called. – neil Nov 26 '13 at 18:22
  • 1
    @neil In that case, it would be easier to do in python 3, but [@Basj is using python 2](http://stackoverflow.com/questions/20217843/zero-outside-the-range-of-a-list-array/20220772?noredirect=1#comment30149972_20218104), so this will have to do. – askewchan Nov 26 '13 at 19:48
0

Random indexes out of list len work only for slices. If you want to get (or set) certain objects you must use indexes only in list range. So

L[4]=0
x=L[4]

does not work, but

L[4:6]=0,0 or L[4:6]=[0,0]
x,y=L[2:10000] # x=3,y=4

will work

Andrey Shokhin
  • 11,250
  • 1
  • 16
  • 15