scala-tessella / ring-seq   0.6.2

Apache License 2.0 Website GitHub

Extends Scala Seq with ring (circular) methods

Scala versions: 3.x 2.13
Scala.js versions: 1.x

RingSeq

ring-seq Scala version support Maven Central Scala.js CI Scala Steward badge

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.

Setup

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 Native

Then 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

Operations

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

Naming convention

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.

Performance notes

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 any IndexedSeq) over List.
  • If you start from a List and call several circular operations, convert once with .toVector.

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

Documentation

Other languages

The same library, adapted for the specific idiom, is available also for:

License

Licensed under either of

at your option.