A library that adds new operations to Scala Seq
for when a sequence needs to be considered circular,
its elements forming a ring — the element after the last wraps back to the first.
It works on any immutable or mutable Seq and sub-type (Vector, List, String, ArraySeq, ListBuffer, …),
acting as a decorator via Scala 3 extension
or Scala 2 implicit class.
Available for Scala 3.3.7 and 2.13.18, cross-published for the JVM, Scala.js, and Scala Native.
Zero runtime dependencies.
Add the dependency to your build.sbt:
libraryDependencies += "io.github.scala-tessella" %% "ring-seq" % "0.7.0"
// Use %%% instead of %% for Scala.js or Scala NativeThen import the RingSeq object — every collection under Seq gains the new methods:
import io.github.scala_tessella.ring_seq.RingSeq.*
// Indexing wraps around
Seq(10, 20, 30).applyO(4) // 20
// Rotation produces a new collection of the same type
"RING".rotateRight(1).mkString // "GRIN"
List(0, 1, 2, 3).startAt(2) // List(2, 3, 0, 1)
// Comparison up to rotation
Seq(0, 1, 2).isRotationOf(Seq(2, 0, 1)) // true
// Canonical (necklace) form — for deduplication or hashing
Seq(2, 0, 1).canonical // Seq(0, 1, 2)
// Symmetry detection
Seq(0, 1, 0, 1).rotationalSymmetry // 2| Method | Description |
|---|---|
indexFrom(i) |
Normalize a circular index to [0, n) |
applyO(i) |
Element at circular index |
| Method | Description |
|---|---|
rotateRight(step) |
Rotate right by step (negative = left) |
rotateLeft(step) |
Rotate left by step (negative = right) |
startAt(i) |
Rotate so circular index i is first |
reflectAt(i) |
Reflect so circular index i is the axis head |
| Method | Description |
|---|---|
sliceO(from, to) |
Circular interval (can exceed ring length) |
containsSliceO(that) |
Does the ring contain that circularly? |
indexOfSliceO(that) |
First circular position of that |
lastIndexOfSliceO(that) |
Last circular position of that |
segmentLengthO(p, from) |
Length of prefix satisfying p |
takeWhileO(p, from) |
Prefix satisfying p |
dropWhileO(p, from) |
Remainder after that prefix |
spanO(p, from) |
(takeWhileO, dropWhileO) in one call |
| Method | Description |
|---|---|
slidingO(size, step) |
Circular sliding windows |
rotations |
All n rotations |
reflections |
Original + reflection |
reversions |
Original + reversal |
rotationsAndReflections |
All 2n variants |
groupedO(size) |
Fixed-size circular groups |
zipWithIndexO(from) |
Elements paired with their circular indices |
| Method | Description |
|---|---|
isRotationOf(that) |
Same elements, possibly rotated? |
isReflectionOf(that) |
Same elements, possibly reflected? |
isReversionOf(that) |
Same elements, possibly reversed? |
isRotationOrReflection(that) |
Either of the above? |
alignTo(that) |
Some(k) with startAt(k) == that, or None |
hammingDistance(that) |
Positional mismatches (same size required) |
minRotationalHammingDistance(that) |
Minimum distance over all rotations |
| Method | Description |
|---|---|
canonicalIndex |
Index of lex-smallest rotation (Booth's O(n)) |
canonical |
Lex-smallest rotation (necklace form) |
bracelet |
Lex-smallest under rotation and reflection |
| Method | Description |
|---|---|
rotationalSymmetry |
Order of rotational symmetry |
symmetryIndices |
Indices on each axis of reflectional symmetry |
reflectionalSymmetryAxes |
Full axis geometry as (AxisLocation, AxisLocation) pairs (Vertex / Edge) |
symmetry |
Number of reflectional symmetry axes |
The alternative circular versions of methods that already exist on Seq keep the same name
with an appended O suffix (meaning ring) — for example applyO is the circular version of apply.
Operations with no standard-library counterpart use plain names.
Circular operations involve random indexing, which is O(1) on IndexedSeq (e.g. Vector, ArraySeq, String) and O(n) on LinearSeq (e.g. List).
For best performance on large sequences:
- Prefer
Vector(or anyIndexedSeq) overList. - If you start from a
Listand call several circular operations, convert once with.toVector.
- 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
- Website — narrative docs and examples.
- Usage walkthrough.
- Scaladoc API — the Scala 3 API reference is valid for Scala 2.13 as well.
- Changelog.
The same library, adapted for the specific idiom, is available also for:
- Python — ring-seq-py
- Rust — ring-seq-rs
Licensed under either of
at your option.