Skip to content

Instantly share code, notes, and snippets.

@mathieucaroff
Last active May 25, 2023 04:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save mathieucaroff/0cf094325fb5294fb54c6a577f05a2c1 to your computer and use it in GitHub Desktop.
Save mathieucaroff/0cf094325fb5294fb54c6a577f05a2c1 to your computer and use it in GitHub Desktop.
Create a (read only) slice without creating a copy of the given sequence.
# stackoverflow.com/q/3485475/can-i-create-a-view-on-a-python-list
# This solution is based on python 3 range ability to be sliced and indexed
# in constant time.
#
# It supports slicing, equality comparsion, string casting, and reproducers,
# but doesn't support assigment (and I don't plan to add support for it).
#
# Creating a SliceableSequenceView of a SliceableSequenceView won't slow down
# access times as this case is detected.
try:
from collections.abc import Sequence
except ImportError:
from collections import Sequence # pylint: disable=no-name-in-module
class SliceableSequenceView(Sequence):
"""
A read-only sequence which allows slicing without copying the viewed list.
Supports negative indexes.
Usage:
li = list(range(100))
s = SliceableSequenceView(li)
u = SliceableSequenceView(li, slice(1,7,2))
v = s[1:7:2]
w = s[-99:-93:2]
li[1] += 10
assert li[1:7:2] == list(u) == list(v) == list(w)
"""
__slots__ = "seq range".split()
def __init__(self, seq, sliced=None):
"""
Accept any sequence (such as lists, strings or ranges).
"""
if sliced is None:
sliced = slice(len(seq))
ls = looksSliceable = True
ls = ls and hasattr(seq, "seq") and isinstance(seq.seq, Sequence)
ls = ls and hasattr(seq, "range") and isinstance(seq.range, range)
looksSliceable = ls
if looksSliceable:
self.seq = seq.seq
self.range = seq.range[sliced]
else:
self.seq = seq
self.range = range(len(seq))[sliced]
def __len__(self):
return len(self.range)
def __getitem__(self, i):
if isinstance(i, slice):
return SliceableSequenceView(self.seq, i)
return self.seq[self.range[i]]
def __str__(self):
r = self.range
s = slice(r.start, r.stop, r.step)
return str(self.seq[s])
def __repr__(self):
r = self.range
s = slice(r.start, r.stop, r.step)
return "SliceableSequenceView({!r})".format(self.seq[s])
def equal(self, otherSequence):
if self is otherSequence:
return True
if len(self) != len(otherSequence):
return False
for v, w in zip(self, otherSequence):
if v != w:
return False
return True
@mathieucaroff
Copy link
Author

@rmallow Thank you for the benchmark data; using SliceableSequenceView makes slicing run in constant time, but has the drawback that it slows down access time by a factor 12, since the __getItem__ function has a lot more work to do.

I'm a little puzzled by your answer: Why would you advice against using my code if you know that indexing will always be slow when using an indirect view over a sequence? All the answers to the Stackoverflow question have a similar drawback. Thank you for your time.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment