18

I often find myself having to work with the last n items in a sequence, where n may be 0. The problem is that trying to slice with [-n:] won't work in the case of n == 0, so awkward special case code is required. For example

if len(b): 
    assert(isAssignableSeq(env, self.stack[-len(b):], b))
    newstack = self.stack[:-len(b)] + a
else: #special code required if len=0 since slice[-0:] doesn't do what we want
    newstack = self.stack + a

My question is - is there any way to get this behavior without requiring the awkward special casing? The code would be much simpler if I didn't have to check for 0 all the time.

jamylak
  • 128,818
  • 30
  • 231
  • 230
Antimony
  • 37,781
  • 10
  • 100
  • 107
  • Possible duplicate of [How to avoid inconsistent s\[i:-j\] slicing behaviour when j is sometimes 0?](https://stackoverflow.com/questions/42753582/how-to-avoid-inconsistent-si-j-slicing-behaviour-when-j-is-sometimes-0) – ivan_pozdeev Oct 20 '17 at 18:01

5 Answers5

23

Just use or's coalescing behavior.

>>> print 4 or None
4
>>> print -3 or None
-3
>>> print -0 or None
None

Explanation (from comments): In your case it would look like this self.stack[:(-len(b) or None)] reduces to self.stack[:None] if len(b) is 0, which in turn reduces to self.stack[:]

tturbo
  • 680
  • 5
  • 16
Ignacio Vazquez-Abrams
  • 776,304
  • 153
  • 1,341
  • 1,358
21

You can switch it from L[-2:] to L[len(L)-2:]

>>> L = [1,2,3,4,5]
>>> L[len(L)-2:]
[4, 5]
>>> L[len(L)-0:]
[]
jamylak
  • 128,818
  • 30
  • 231
  • 230
  • 1
    None of the answers are particularly appealing, but I think this one makes the most sense. If the expression resulting in L is short, than this is simpler then an explicit check. – Antimony Jul 05 '12 at 05:12
2

When you find yourself using a construct more than once, turn it into a function.

def last(alist, n):
    if n:
        return alist[:-n]
    return alist

newstack = last(self.stack, len(b)) + a

An even simpler version as suggested by EOL in the comments:

def last(alist, n):
    return alist[:-n] if n else alist[:]
Mark Ransom
  • 299,747
  • 42
  • 398
  • 622
  • 1
    You may also more simply write `return alist[:-n] if n else alist`. It may also be a good idea to copy the list *every time*: `… else alist[:]`. This actually leads to an even simpler answer, very close to Ignacio's: `return alist[:-n or None]`. – Eric O. Lebigot Jul 05 '12 at 04:57
  • 1
    I think that if the original version used an `else` clause it would be clearer, or just as clear as the version suggested by @EOL – jamylak Jul 05 '12 at 05:09
  • @jamylak, you may be right. I prefer leaving out the `else` when I can, but I admit it's down to personal preference. – Mark Ransom Jul 05 '12 at 05:11
  • 1
    @MarkRansom I agree but my preference is to always leave it in :D – jamylak Jul 05 '12 at 05:12
  • 1
    @MarkRansom, @jamylak: I don't think that using `else` should stricly be down to personal preference: using an explicit `else` instead of an implicit one (as in the current version of this answer) is arguably generally better: one does not need to read the full "then" part in order to know that the `if… return` structure is actually an `if then else` structure. This makes the code faster to parse, and therefore makes it more legible (in general: there are exceptions, for instance when the `else` clause would be very long). – Eric O. Lebigot Jul 05 '12 at 07:43
0

This is likely to be horribly inefficient, thanks to the double-reversing, but hopefully there's something to the idea of reversing the sequence to make the indexing easier:

a = [11, 7, 5, 8, 2, 6]

def get_last_n(seq, n):
    back_seq = seq[::-1]
    select = back_seq[:n]
    return select[::-1]

print(get_last_n(a, 3))
print(get_last_n(a, 0))

Returns:

[8, 2, 6]
[]
Marius
  • 58,213
  • 16
  • 107
  • 105
0

You can slip a conditional in there

L[-i if i else len(L):]

I think this version is less clear. You should use a comment along side it

L[-i or len(L):]
John La Rooy
  • 295,403
  • 53
  • 369
  • 502