2

I have a list, e.g.

['a', 'b', 'c', ... 'z']

and I need to be able to repeatedly get a slice of fixed length, however, when the slice reaches the end it needs to continue as if the list was circular and loop back to the start, so that if the desired length was 3, one of the returned values would be ['z', 'a', 'b'].


I have read a few questions concerning circular lists and itertools' itertools.cycle (such as this one), however, none explain how to get a slice of said objects, and I tried but failed to get these to work.


My Code:

# a simplified version of my class
class MyClass:
    def __init__(self):
        self.all = list('abcdefghijklmnopqrstuvwxyz')
        self.length = len(self.all)
        self.range = slice(0, 3) # the starting slice

    def update(self, delta):
        # should also work when value of delta is negative
        new_start = (self.range.start + delta) % self.length
        new_stop = (self.range.stop + delta) % self.length
        self.range = slice(new_start, new_stop)
        # do things with self.visible...

    @property
    def visible(self):
        return self.all[self.range]


instance = MyClass()
for i in range(27):
    print(instance.visible)
    instance.update(1)

(Python 3.7)


Output:

['a', 'b', 'c']
['b', 'c', 'd']
...              # 20 lines, expected results
['w', 'x', 'y']
[]               # wanted ['x', 'y', 'z']
[]               # wanted ['y', 'z', 'a']
[]               # wanted ['z', 'a', 'b']
['a', 'b', 'c']

The problem with the above code is that once self.range.stop reaches self.length, it becomes smaller than self.range.start and then nothing is returned until self.range.start also reaches self.length and self.range.stop is no longer smaller.

How can this be fixed to allow for the slice (or, if required, multiple slices) to return the correct section of the list, even when this means values from both the start and end?

T.W.
  • 77
  • 6

3 Answers3

2

What about:

class MyClass:
    def __init__(self):
        self.all = list('abcdefghijklmnopqrstuvwxyz')
        self.length = len(self.all)
        self.range = slice(0, 3) # the starting slice

    def update(self, delta):
        # should also work when value of delta is negative
        new_start = (self.range.start + delta) % self.length
        new_stop = (self.range.stop + delta) % self.length
        self.range = slice(new_start, new_stop)
        # do things with self.visible...

    @property
    def visible(self):
        if self.range.start > self.range.stop:
          range_1 = slice(self.range.start, self.length)
          range_2 = slice(0, self.range.stop)

          return self.all[range_1] + self.all[range_2]

        return self.all[self.range]
Pierre V.
  • 1,625
  • 1
  • 11
  • 14
2

Here's a solution with python libraries:

from itertools import islice, cycle
from collections import deque

def cycled_slice(it, size):
    c = cycle(it)
    d = deque(islice(c, size), size)

    for x in c:
        yield list(d)
        d.append(x)

# demo

n = 0
for x in cycled_slice('abcdefg', 3):
    print(''.join(x))
    n += 1
    if n > 10:
        break

The idea is to create an endless iterator with cycle: abc -> abcabcabc..., populate a deque (fixed-length list) with the first chunk (islice) and then repeatedly append the next element to the deque and yield its content.

Docs:

georg
  • 211,518
  • 52
  • 313
  • 390
  • Thanks for your answer @georg! It works, exactly as I need, however, Pierre V.'s answer is more suitable for my situation. I am sure this will help someone. – T.W. Sep 08 '19 at 09:41
2

You can modify visible as following:

@property
def visible(self):
    return list(islice(cycle(self.all),self.range.start, self.range.start+3))

You will also need to import the itertools module:

from itertools import cycle, islice
Vasilis G.
  • 7,556
  • 4
  • 19
  • 29