Suppose I have list as follow:
lst = [0,10,20,30,40,50,60,70]
I want elements from lst from index = 5
to index = 2
in cyclic order.
lst[5:2]
yields []
I want lst[5:2] = [50,60,70,0,10]
. Is there any simple library function to do this?
Suppose I have list as follow:
lst = [0,10,20,30,40,50,60,70]
I want elements from lst from index = 5
to index = 2
in cyclic order.
lst[5:2]
yields []
I want lst[5:2] = [50,60,70,0,10]
. Is there any simple library function to do this?
Simply split the slicing in two if the second term is smaller than the first:
lst = [0,10,20,30,40,50,60,70]
def circslice(l, a, b):
if b>=a:
return l[a:b]
else:
return l[a:]+l[:b]
circslice(lst, 5, 2)
output: [50, 60, 70, 0, 10]
Using a deque
as suggested in comments:
from collections import deque
d = deque(lst)
a,b = 5,2
d.rotate(-a)
list(d)[:len(lst)-a+b]
NB. I find it not very practical as it requires to make a copy of the list to create the deque, and another copy to slice
For something that allows you to still use the native slicing syntax and that maintains static typing compatibility, you can use a light wrapper class around your sequence:
from typing import Generic, Protocol, TypeVar
S = TypeVar('S', bound="ConcatSequence")
class CircularView(Generic[S]):
def __init__(self, seq: S) -> None:
self.seq = seq
def __getitem__(self, s: slice) -> S:
if s.start <= s.stop:
return self.seq[s]
else:
wrap = len(self.seq) % s.step if s.step else 0
return self.seq[s.start::s.step] + self.seq[wrap:s.stop:s.step]
lst = [0, 10, 20, 30, 40, 50, 60, 70]
print(CircularView(lst)[2:5]) # [20, 30, 40]
print(CircularView(lst)[5:2]) # [50, 60, 70, 0, 10]
print(CircularView(lst)[5:2:2]) # [50, 70, 0]
print(CircularView(lst)[5:3:2]) # [50, 70, 0, 20]
print(CircularView(lst)[4:3:3]) # [40, 70, 20]
with the optional protocol for static typing
class ConcatSequence(Protocol):
"""
A sequence that implements concatenation via '__add__'.
This protocol is required instead of using
'collections.abc.Sequence' since not all sequence types
implement '__add__' (for example, 'range').
"""
def __add__(self, other):
...
def __getitem__(self, item):
...
def __len__(self):
...
This method passes type checking with mypy.
You could use a function like this:
def circular_indexing(list_, start_index, end_index) -> list:
return [*list_[start_index:len(list_)], *list_[0:end_index]]
For example:
list1 = [0, 1, 2, 3]
def circular_indexing(list_, start_index, end_index) -> list:
return [*list_[start_index:len(list_)], *list_[0:end_index]]
print(circular_indexing(list1, 2, 1))
Output: [2, 3, 0]
There are two fast/easy solutions to this problem.
The first, and more complicated method, would be to overwrite the default python library implementation of the python list.__getitem__
method, which has been referenced in other places on StackOverflow.
This would allow you to reference the slicing as you would normally, i.e. list[5:3]
, and it would, in theory, behave as you define. This would be a "local expansion" of the default library.
Transversely, you could implement your own function that will iterate over your list in a circular manner, that meets your own criterion. Some pseudo-code:
def foo(left_idx, right_idx):
if right_idx < left_idx:
wrap index when right bound has been reached
else:
iterate normally