0

I need to access the previous two and next two elements(if exist). e.g. [..., a, b, c, d, e, ..] -> For c I want abcde.

This is easy for elements in the middle of a list, but in the beginning and end it will cause Index out of range errors. And if the list is smaller it becomes even more complicated.

I can do it with a bunch of if-elif-elif--else statements for all the cases, but is there any concise and elegant way to accomplish this?

I'm using python 2.7

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
hoodakaushal
  • 1,253
  • 2
  • 16
  • 31

2 Answers2

4

Just use slicing (but be careful about negative indices):

inputlist[max(0, index - 2):index + 3]

will get you a window of up to 5 elements around index; 2 before and 2 after.

The max() call ensures that the start index is capped to 0 or up.

This'll work with whatever method you used to produce your index. Say you are using a loop, together with enumerate() to keep track of the index:

def window(inputlist, index):
    return inputlist[max(0, index - 2):index + 3]

for index, elem in enumerate(inputlist):
    print window(inputlist, index)

or you found your element with list.index():

print window(inputlist, inputlist.index('ham'))

Demo:

>>> def window(inputlist, index):
...     return inputlist[max(0, index - 2):index + 3]
... 
>>> window(['foo', 'bar', 'baz', 'spam', 'ham', 'eggs'], 1)
['foo', 'bar', 'baz', 'spam']
>>> window(['foo', 'bar', 'baz', 'spam', 'ham', 'eggs'], 2)
['foo', 'bar', 'baz', 'spam', 'ham']
>>> window(['foo', 'bar', 'baz', 'spam', 'ham', 'eggs'], 4)
['baz', 'spam', 'ham', 'eggs']
>>> window(['foo', 'bar', 'baz', 'spam', 'ham', 'eggs'], 5)
['spam', 'ham', 'eggs']
Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
  • This works, but how come the larger indices don't throw off errors? Because if I do it for the last element, I'll be trying to access elements with index larger than the length? e.g. for `input = [1,2,3]` and `index = 1` this simplifies to `input[0:4]` – hoodakaushal Nov 03 '14 at 16:47
  • 1
    @hoodakaushal: slicing *always* returns a result. That result can be empty, but there are no out-of-bounds indices when it comes to slicing. – Martijn Pieters Nov 03 '14 at 16:50
  • @hoodakaushal: see [Why substring slicing index out of range works in Python?](http://stackoverflow.com/q/9490058) – Martijn Pieters Nov 03 '14 at 16:51
2

You want to slice your list

l = ['a','b','c','d','e','f','g','h','i','j']
print l[max(0, l.index('f') - 2):l.index('f') + 3]
['d', 'e', 'f', 'g', 'h']

Something that you need to be aware of when slicing is negative indexes.

A few other examples:

print l[max(0, l.index('a') - 2):l.index('a') + 3]
['a', 'b', 'c']

print l[max(0, l.index('j') - 2):l.index('j') + 3]
['h', 'i', 'j']

print l[max(0, l.index('c') - 2):l.index('c') + 3]
['a', 'b', 'c', 'd', 'e']

Important note:

If you have duplicate values in your list, the index() function will utilize the first index the value is seen:

l = ['a','b','c','d','e','f','g','h','i','j','f']
print l[max(0, l.index('f') - 2):l.index('f') + 3]
['d', 'e', 'f', 'g', 'h']

Notice that f appears twice. In this instance, it does not use the second index.

Andy
  • 49,085
  • 60
  • 166
  • 233