Amelio Vazquez-Reina
Amelio Vazquez-Reina

Reputation: 96350

Cyclical indexing of lists in Python

Say I have an array foo with e.g. elements [1, 2, 3], and that I want to retrieve elements of foo as if foo had been "infinitely concatenated".

For example foo[0:2] would return (like a normal list):

[1, 2]

and foo[0:5] would return:

[1, 2, 3, 1, 2]

while foo[7:13] would return:

[2, 3, 1, 2, 3, 1]

Are there any data containers in Python or extended modules that already facilitate this type of access? If not, what would be a good/easy way to provide this container?

Upvotes: 4

Views: 444

Answers (3)

user109839
user109839

Reputation:

Even if it will be ridiculously inefficient compared to the modulo based implementation suggested above, I think that using itertools could be a fun way to do it…

>>> from itertools import islice, cycle
>>> make_cyclic = lambda lst: lambda start, stop: list( islice( cycle( lst ), start, stop ) )
>>> make_cyclic( [ 1, 2, 3 ] )
>>> c(7, 13)
[2, 3, 1, 2, 3, 1]

Upvotes: 2

Pavel Anossov
Pavel Anossov

Reputation: 62928

I'm afraid you'll have to implement it yourself. It isn't difficult though:

class cyclist(list):
    def __getitem__(self, index):
        return list.__getitem__(self, index % len(self))

    def __getslice__(self, start, stop):
        return [self[n] for n in range(start, stop)]


foo = cyclist([1, 2, 3])
print foo[0:2]    # [1, 2]
print foo[7:13]   # [2, 3, 1, 2, 3, 1]
print foo[0:5]    # [1, 2, 3, 1, 2]

It's missing some details like taking care of omitted slice parameters, negative numbers in slices, and slice step.

Upvotes: 11

shx2
shx2

Reputation: 64328

You should be careful when working with sequences which look like lists but behave inherently different. I would suggest using Pavel Anossov's cool implementation, but providing designated get_cyclic_item and get_cyclic_slice, instead of overriding list's __getitem__ and __getslice__.

The user of the class can easily make assumptions about the behavior of the list he's using (expecting an ISA relation, as in "cyclicallist IS A list"), which would lead to bugs/errors.

Here are some examples for cases where using your list can get confusing, if the caller is not aware he's using a cyclicallist instead of a regular list...

a = cyclicallist([ 0, 1, 2 ])
# appending a value at the end changes an "existing" index
print a[100]
a.append(99)
print a[100]
# deleting a value changes an index preceding it
print a[100]
del a[999]  # currently gives an error: IndexError: list assignment index out of range
print a[100]  # even if no error, what should this print?
# hmm...
del a[100:99999]

And of course, the semantic for empty cyclicallist is not well-defined...

Upvotes: 2

Related Questions