28
votes

Say I have a list,

l = [1, 2, 3, 4, 5, 6, 7, 8]

I want to grab the index of an arbitrary element and the values of its neighbors. For example,

i = l.index(n)
j = l[i-1]
k = l[i+1]

However, for the edge case when i == len(l) - 1 this fails. So I thought I'd just wrap it around,

if i == len(l) - 1:
    k = l[0]
else:
    k = l[i+1]

Is there a pythonic way to do this?

7
Do you want a special behavior even if an index smaller than zero or larger than the length of the list is given? - jimifiki
Just to wrap around. I always want j and k to point to something. And I want to be able to traverse the entire list via j or k. - john
you accepted an answer not taking care of out-of-range indices... - jimifiki
I'm confused. If you mod the index by the length of the list... how can it ever be out of range? - john
I meant that k[10] has a meaning, I tought you didn't want it to mean k[2] and you wanted an error to be raised. That's all. - jimifiki

7 Answers

43
votes

You could use the modulo operator!

i = len(l) - 1
jIndex = (i - 1) % len(l)
kIndex = (i + 1) % len(l)

j = l[jIndex]
k = l[kIndex]

Or, to be less verbose:

k = l[(i + 1) % len(l)]
27
votes

The easiest way to wrap around a fixed length list is with the % (modulo) operator

list_element = my_list[idx % len(my_list)]

but anyway look at http://docs.python.org/library/itertools.html

from itertools import cycle

for p in cycle([1,2,3]):
  print "endless cycle:", p
6
votes

The typical way to fit values to a certain range is to use the % operator:

k = l[(i + 1) % len(l)]
5
votes

If you want it as a class, I whipped up this quick CircularList:

import operator

class CircularList(list):
    def __getitem__(self, x):
        if isinstance(x, slice):
            return [self[x] for x in self._rangeify(x)]

        index = operator.index(x)
        try:
            return super().__getitem__(index % len(self))
        except ZeroDivisionError:
            raise IndexError('list index out of range')

    def _rangeify(self, slice):
        start, stop, step = slice.start, slice.stop, slice.step
        if start is None:
            start = 0
        if stop is None:
            stop = len(self)
        if step is None:
            step = 1
        return range(start, stop, step)

It supports slicing, so

CircularList(range(5))[1:10] == [1, 2, 3, 4, 0, 1, 2, 3, 4]
0
votes

In case you do not want to wrap around, the most Pythonic answer would be to use slices. Missing neighbor substituted with None. E.g.:

def nbrs(l, e):
   i = l.index(e)
   return (l[i-1:i] + [None])[0], (l[i+1:i+2] + [None])[0]

This is how the function can work:

>>> nbrs([2,3,4,1], 1)
(4, None)
>>> nbrs([1,2,3], 1)
(None, 2)
>>> nbrs([2,3,4,1,5,6], 1)
(4, 5)
>>> nbrs([], 1)
Traceback (most recent call last):
  File "<stdin>", line 1, in <module>
  File "<stdin>", line 2, in nbrs
ValueError: 1 is not in list
0
votes
a = [2,3,5,7,11,13]

def env (l, n, count):
    from itertools import cycle, islice
    index = l.index(n) + len(l)
    aux = islice (cycle (l), index - count, index + count + 1)
    return list(aux)

Behaves as follows

>>> env (a, 2,1)
[13, 2, 3]
>>> env (a,13,2)
[7, 11, 13, 2, 3]
>>> env (a,7,0)
[7]
0
votes

Using the modulo method that others have mentioned I have created a class with a property that implements a circular list.

class Circle:
    """Creates a circular array of numbers

    >>> c = Circle(30)
    >>> c.position
    -1
    >>> c.position = 10
    >>> c.position
    10
    >>> c.position = 20
    >>> c.position
    20
    >>> c.position = 30
    >>> c.position
    0
    >>> c.position = -5
    >>> c.position
    25
    >>>

    """
    def __init__(self, size):
        if not isinstance(size, int):  # validating length
            raise TypeError("Only integers are allowed")
        self.size = size

    @property
    def position(self):
        try:
            return self._position
        except AttributeError:
            return -1

    @position.setter
    def position(self, value):
        positions = [x for x in range(0, self.size)]
        i = len(positions) - 1
        k = positions[(i + value + 1) % len(positions)]
        self._position = k