Pythonic Circular List

Question:

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?

Asked By: john

||

Answers:

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

k = l[(i + 1) % len(l)]
Answered By: Ismail Badawi

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)]
Answered By: voithos

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).

Answered By: maxp

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
Answered By: Roman Susi
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]
Answered By: nsmon93

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]
Answered By: ioistired

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
Answered By: Tim Hughes
Categories: questions Tags: ,
Answers are sorted by their score. The answer accepted by the question owner as the best is marked with
at the top-right corner.