30

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?

john
  • 3,043
  • 5
  • 27
  • 48
  • Do you want a special behavior even if an index smaller than zero or larger than the length of the list is given? – jimifiki Jan 21 '12 at 06:30
  • 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 Jan 21 '12 at 07:29
  • 1
    you accepted an answer not taking care of out-of-range indices... – jimifiki Jan 22 '12 at 07:51
  • I'm confused. If you mod the index by the length of the list... how can it ever be out of range? – john Jan 22 '12 at 20:16
  • 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 Jan 22 '12 at 20:41
  • If you read my question closely you will see that `k[10]` would never happen because `l.index(n)` would only return something as large as `len(l) - 1` which in this case is `i = 7`. I increment `i` by `1` and that is all. And although I didn't write this explicitly, I preface `l.index(n)` with `if n in l` and handle errors from there appropriately. So the accepted answer is the solution for this particular problem, but thanks for looking out – john Jan 22 '12 at 20:50

7 Answers7

46

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)]
voithos
  • 68,482
  • 12
  • 101
  • 116
  • 6
    A random comment: note that if 0 <= i < len(l), then `l[(i + 1) % len(l)]` can also be written `l[i - (len(l)-1)]`, avoiding the modulo. (It gives an index that is often negative, which means counting from the end, but its value is correct.) – Armin Rigo Jun 21 '13 at 09:50
  • @ArminRigo, what u mean by `(It gives an index that is often negative, which means counting from the end, but its value is correct.)` – user5319825 Jun 12 '21 at 19:04
33

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 https://docs.python.org/library/itertools.html#itertools.cycle

from itertools import cycle

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

Also see the warning: Note, this member of the toolkit may require significant auxiliary storage (depending on the length of the iterable).

Kashyap
  • 15,354
  • 13
  • 64
  • 103
maxp
  • 5,454
  • 7
  • 28
  • 30
8

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

k = l[(i + 1) % len(l)]
Ismail Badawi
  • 36,054
  • 7
  • 85
  • 97
5

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]
ioistired
  • 105
  • 3
  • 8
  • 1
    If I print `a = CircularList([])` and then `a[0]` I get a ZeroDivisionError which is the wrong error. You should add ` except ZeroDivisionError: raise IndexError('list index out of range')` – Daniel Moskovich Sep 16 '18 at 10:54
0
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]
nsmon93
  • 3
  • 3
0

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
Tim Hughes
  • 3,144
  • 2
  • 24
  • 24
0

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
Roman Susi
  • 4,135
  • 2
  • 32
  • 47