RingSeq

A library that adds new operations to Scala Seq for when a sequence needs to be considered circular, its elements forming a ring.

It works on any immutable / mutable Seq and sub-types.

Available for Scala 3.3.7 and 2.13.18, cross-published for the JVM, Scala.js, and Scala Native.

Setup

Add the following dependency to your build.sbt file:

libraryDependencies += "io.github.scala-tessella" %% "ring-seq" % "0.8.0" // Use %%% instead of %% if you're using ScalaJS

Getting started

First, start with the following import:

import io.github.scala_tessella.ring_seq.RingSeq._

Where the RingSeq object is imported, any collection under Seq will access the new methods. You can write something like:

"RING".rotateRight(1).mkString // GRIN
List(0, 1, 2, 3).startAt(2) // List(2, 3, 0, 1)
ListBuffer(1, 3, 5, 7, 9).reflectAt(3) // ListBuffer(7, 5, 3, 1, 9)

Need

Whenever data are structured in a circular sequence, chances are you don’t want to locally reinvent the wheel (pun intended).

Solution

RingSeq is a small, purely functional, self-contained library, where most of the circular use cases are already solved and building blocks provided for the others.

Leveraging Scala 3 extension or Scala 2 implicit class, it acts like a decorator, providing new circular methods to any collection under Seq.

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

Design

The same decorators could possibly be added to scala-collection-contrib and exist just there, but the underlying design has (at least for me) a very steep learning curve and the community help is weak. One clear maintenance advantage would be that the source code is the same for Scala 2.13 and Scala 3.

So, for now, is a conscious decision to achieve the same results with a simpler design (even if the source code is different between Scala 2.13 and Scala 3) and to publish in this, concise and hopefully useful, separate library.

Performance notes

The library works on any Seq, but 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 on it, convert once with .toVector.

Operations that enumerate rotations or reflections (rotations, isRotationOf, rotationalSymmetry, symmetry, symmetryIndices, containsSliceO, sliceO with large output, …) all scale linearly in the chosen output size, but each element access pays the indexing cost above.

Other languages

The same library is available also for:

The source code for this page can be found here.