Array Slicing
Represent a contiguous subrange of an array as either a view or a copied array.
Array Slicing
Array slicing selects a contiguous subrange from an array. A slice may be a view into the original storage, or it may be a new copied array.
You use it when an algorithm should operate on part of an array without manually passing start and end indices everywhere.
Problem
Given an array $A$ of length $n$, create a slice from index $l$ up to but not including index $r$:
$$ A[l:r] $$
where
$$ 0 \le l \le r \le n $$
Structure
A slice view usually stores:
- reference to the same backing array
- start offset
- length
For example:
$$ A = [8, 3, 5, 1, 9] $$
and
$$ A[1:4] = [3, 5, 1] $$
The slice view points into the same storage rather than copying elements.
Algorithm
Create a slice view:
slice(A, l, r):
if l < 0 or r > length(A) or l > r:
error "invalid slice range"
S.base = A
S.start = l
S.length = r - l
return S
Read from a slice:
get(S, i):
if i < 0 or i >= S.length:
error "index out of bounds"
return S.base[S.start + i]
Example
Let
$$ A = [8, 3, 5, 1, 9] $$
Create:
$$ S = A[1:4] $$
| slice index | base index | value |
|---|---|---|
| 0 | 1 | 3 |
| 1 | 2 | 5 |
| 2 | 3 | 1 |
Reading $S[2]$ maps to:
$$ A[1 + 2] = A[3] = 1 $$
Return $1$.
Correctness
The slice range check ensures that $l$ and $r$ describe a valid contiguous subrange of the base array. The slice stores length $r - l$, so every valid slice index $i$ satisfies $0 \le i < r - l$. Mapping it to $l + i$ gives an index in the original range $[l, r)$, so each slice access returns exactly the corresponding base-array element.
Complexity
| operation | view slice | copied slice |
|---|---|---|
| create slice | $O(1)$ | $O(k)$ |
| get | $O(1)$ | $O(1)$ |
| set | $O(1)$ | $O(1)$ |
Here:
$$ k = r - l $$
A view slice uses:
$$ O(1) $$
extra space.
A copied slice uses:
$$ O(k) $$
extra space.
When to Use
Array slicing is appropriate when:
- an algorithm works on subranges
- copying would be unnecessary
- start and end indices make code noisy
- recursive or divide-and-conquer logic needs clean boundaries
Be careful with view slices when:
- mutating the slice may affect the original array
- a small slice keeps a large backing array alive
- concurrent code may observe shared storage
Implementation
class Slice:
def __init__(self, base, start, end):
if start < 0 or end > len(base) or start > end:
raise IndexError("invalid slice range")
self.base = base
self.start = start
self.length = end - start
def get(self, i):
if i < 0 or i >= self.length:
raise IndexError("index out of bounds")
return self.base[self.start + i]
def set(self, i, x):
if i < 0 or i >= self.length:
raise IndexError("index out of bounds")
self.base[self.start + i] = x
type Slice[T any] struct {
base []T
start int
size int
}
func NewSlice[T any](base []T, start, end int) (*Slice[T], bool) {
if start < 0 || end > len(base) || start > end {
return nil, false
}
return &Slice[T]{
base: base,
start: start,
size: end - start,
}, true
}
func (s *Slice[T]) Get(i int) (T, bool) {
var zero T
if i < 0 || i >= s.size {
return zero, false
}
return s.base[s.start+i], true
}
func (s *Slice[T]) Set(i int, x T) bool {
if i < 0 || i >= s.size {
return false
}
s.base[s.start+i] = x
return true
}