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?
Answers:
The typical way to fit values to a certain range is to use the %
operator:
k = l[(i + 1) % len(l)]
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)]
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).
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
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]
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]
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
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?
The typical way to fit values to a certain range is to use the %
operator:
k = l[(i + 1) % len(l)]
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)]
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).
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
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]
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]
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