# Array Slicing

# 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:

```id="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:

```id="slice-get"
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

```python id="array-slicing-python"
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
```

```go id="array-slicing-go"
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
}
```

