Last active
May 25, 2023 04:51
-
-
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.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
# 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 |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
@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.