# ring-seq
Circular (ring) sequence operations for Rust slices.
[](https://crates.io/crates/ring-seq)
[](https://docs.rs/ring-seq)
[](https://github.com/scala-tessella/ring-seq-rs#license)
Treat any `[T]`, `Vec<T>`, array, or `Box<[T]>` as a **circular** sequence
— the element after the last wraps back to the first.
- Zero dependencies. Zero `unsafe`. `#![no_std]` with a default `alloc`
feature.
- All transforms return lazy views or iterators; allocation is opt-in.
- Single entry point: `slice.circular()` gives you a `Circular<T>`
wrapper that hosts every operation.
## Quick start
```toml
[dependencies]
ring-seq = "0.3"
```
```rust
use ring_seq::AsCircular;
let r = [10, 20, 30].circular();
// Indexing wraps in both directions
assert_eq!(*r.apply(4), 20);
assert_eq!(*r.apply(-1), 30);
// Reindexed views — no allocation
let rotated: Vec<_> = r.rotate_right(1).iter().copied().collect();
assert_eq!(rotated, [30, 10, 20]);
// Comparison up to rotation / reflection
assert!(r.is_rotation_of(&[20, 30, 10]));
assert!(r.is_reflection_of(&[10, 30, 20]));
// Canonical (necklace) form — Booth's O(n)
assert_eq!(r.canonical(), [10, 20, 30]);
// Lazy iterators of views compose naturally
assert_eq!([0, 1, 0, 1].circular().rotational_symmetry(), 2);
```
## Operations on `Circular<T>`
### Indexing & iteration
| `apply(i)` | `&T` | Element at circular index (panics if empty) |
| `index_from(i)` | `usize` | Normalize a circular index to `[0, len)` |
| `iter()` | `CircularIter` | Walk the view's elements (lazy) |
### Reindexed views (lazy)
| `start_at(i)` | `Circular` | View starts at circular index `i` |
| `rotate_left(step)` | `Circular` | Shift left by `step` (negative = right) |
| `rotate_right(step)` | `Circular` | Shift right by `step` (negative = left) |
| `reflect_at(i)` | `Circular` | Reflect around index `i` |
### Bounded iteration (lazy)
| `slice(from, to)` | `CircularIter` | `max(to - from, 0)` elements, wrapping |
| `take_while(pred, from)` | `impl Iterator` | Prefix satisfying `pred` (≤ one lap) |
| `drop_while(pred, from)` | `impl Iterator` | Remainder after the prefix |
| `enumerate(from)` | `Enumerate` | `(&T, ring_index)` pairs |
### Iterators of views (lazy)
| `rotations()` | `Circular` × `n` | Every rotation |
| `reflections()` | `Circular` × 2 | Original + `reflect_at(0)` |
| `reversions()` | `Circular` × 2 | Original + reverse |
| `rotations_and_reflections()` | `Circular` × `2n` | All dihedral variants |
| `windows(size)` | `CircularIter` × `n` | Sliding windows, step 1 |
| `chunks(size)` | `CircularIter` × `ceil(n/size)` | Non-overlapping chunks |
### Comparison
| `is_rotation_of(other)` | Same elements, possibly rotated? |
| `is_reflection_of(other)` | Equals `self` or `self.reflect_at(0)` |
| `is_reversion_of(other)` | Equals `self` or its reverse |
| `is_rotation_or_reflection_of(other)` | Any dihedral variant |
| `rotation_offset(other)` | `Some(k)` where `self.start_at(k) == other` |
| `hamming_distance(other)` | Positional mismatches |
| `min_rotational_hamming_distance(other)` | Minimum over all rotations |
| `contains_slice(needle)` | Does `needle` appear circularly? |
| `index_of_slice(needle, from)` | First circular index where `needle` matches |
### Necklace & symmetry
| `canonical_index()` | `usize` | Index of lex-smallest rotation (Booth's *O(n)*; `alloc`) |
| `canonical()` | `Vec<T>` | Lex-smallest rotation (`alloc`) |
| `bracelet()` | `Vec<T>` | Lex-smallest under rotation + reflection (`alloc`) |
| `rotational_symmetry()` | `usize` | Order of rotational symmetry |
| `symmetry()` | `usize` | Number of reflectional axes |
| `symmetry_indices()` | `Vec<usize>` | Shifts where the view equals its reverse (`alloc`) |
| `reflectional_symmetry_axes()` | `Vec<(AxisLocation, AxisLocation)>` | Full axis geometry (`alloc`) |
### Materialization
| `to_vec()` | `Vec<T>` | Materialize the view (`alloc`, `T: Clone`) |
## `no_std`
```toml
[dependencies]
ring-seq = { version = "0.3", default-features = false }
```
Disabling the default `alloc` feature drops the methods that return owned
collections (`canonical`, `bracelet`, `symmetry_indices`,
`reflectional_symmetry_axes`, `to_vec`) and `canonical_index` (Booth's
algorithm needs an internal `Vec`). Everything else — the `Circular`
wrapper, every reindexed-view method, every iterator — depends only on
`core`.
## Use cases
- **Bioinformatics** — circular DNA/RNA sequence alignment and comparison
- **Graphics** — polygon vertex manipulation, closed curve operations
- **Procedural generation** — tile rings, symmetry-aware pattern generation
- **Music theory** — pitch-class sets, chord inversions
- **Combinatorics** — necklace/bracelet enumeration, Burnside's lemma
- **Embedded / robotics** — circular sensor arrays, rotary encoder positions
## Minimum Rust version
1.63
## Other languages
The same library, adapted for the specific idiom, is available also for:
- Python — [ring-seq-py](https://github.com/scala-tessella/ring-seq-py)
- Scala — [ring-seq](https://github.com/scala-tessella/ring-seq)
## License
Licensed under either of
- [Apache License, Version 2.0](LICENSE-APACHE)
- [MIT License](LICENSE-MIT)
at your option.