Reputation: 41
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?
Upvotes: 3
Views: 659
Reputation: 14423
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.
Upvotes: 1
Reputation: 260790
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
Upvotes: 1
Reputation: 384
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
Upvotes: 0
Reputation: 504
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]
Upvotes: 0
Reputation: 260790
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]
Upvotes: 5