RollingCounter
import { RollingCounter } from "jsr:@ende/data-structures/unstable-rolling-counter";class ยท experimental
class RollingCounter implements Iterable<number>, ReadonlyRollingCounterA fixed-size rolling counter.
The counter splits a window into a fixed number of segments, each tracking
a count. It has no built-in clock. Call
rotate to advance the
window on your own schedule.
The class is iterable and yields segment counts from oldest to newest.
| Method | Time complexity |
|---|---|
| increment(n) | Constant |
| rotate(steps) | Linear in min(steps, segment count) |
| current | Constant |
| total | Constant |
| segmentCount | Constant |
| at(index) | Constant |
| clear() | Linear in the number of segments |
| toArray() | Linear in the number of segments |
| toJSON() | Linear in the number of segments |
| [Symbol.iterator] | Linear in the number of segments |
| RollingCounter() | Linear in the number of segments |
| RollingCounter.from(source) | Linear in the number of segments |
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
// 3 segments, all starting at zero
const counter = new RollingCounter(3);
// Add 5 to the current (newest) segment
counter.increment(5);
assertEquals(counter.total, 5);
assertEquals([...counter], [0, 0, 5]);
// Advance the window, then add 3 to the new segment
counter.rotate();
counter.increment(3);
assertEquals(counter.total, 8);
assertEquals([...counter], [0, 5, 3]);
// Bulk-rotate 2 steps: evicts the two oldest (0 and 5)
counter.rotate(2);
assertEquals(counter.total, 3);
assertEquals([...counter], [3, 0, 0]);
Constructors
constructor
new RollingCounter(segmentCount: number)experimental
Creates a counter with the given number of segments, all starting at zero.
segmentCount: numberThe number of segments. Must be a positive integer.
Static methods
from
static from(source: RollingCounter | RollingCounterSnapshot): RollingCounterexperimental
Creates a counter from an existing RollingCounter or from a
snapshot previously obtained via
toJSON. The snapshot's
segments array defines both the number of segments and their initial
values, ordered oldest to newest (matching iteration order). The last
element is the current (newest) segment.
When given a RollingCounter, returns an independent clone with a
canonical internal layout: segments laid out oldest-to-newest with the
current segment at the last position. Mutating the clone does not affect
the source.
source: RollingCounter | RollingCounterSnapshotA
RollingCounterto clone, or a snapshot previously obtained fromtoJSON.
Returns
A new RollingCounter with the given state.
Example: Round-trip serialization
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const original = new RollingCounter(3);
original.increment(5);
original.rotate();
original.increment(3);
const snapshot = original.toJSON();
const restored = RollingCounter.from(snapshot);
assertEquals([...restored], [...original]);
assertEquals(restored.total, original.total);
assertEquals(restored.segmentCount, original.segmentCount);
Example: Cloning from a counter
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const original = new RollingCounter(3);
original.increment(5);
original.rotate();
original.increment(3);
const clone = RollingCounter.from(original);
clone.rotate();
clone.increment(7);
assertEquals([...original], [0, 5, 3]);
assertEquals([...clone], [5, 3, 7]);
Properties
[Symbol.toStringTag]
readonly [Symbol.toStringTag]: "RollingCounter"experimental
A string tag for the class, used by Object.prototype.toString().
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(3);
assertEquals(
Object.prototype.toString.call(counter),
"[object RollingCounter]",
);
Methods
increment
increment(n: number = 1): numberexperimental
Adds n to the current segment.
n: number = 1The amount to add. Defaults to
1. Must be a non-negative integer.
Returns
The new total across all segments.
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(3);
const total = counter.increment(5);
assertEquals(total, 5);
rotate
rotate(steps: number = 1): numberexperimental
Advances the window by steps, dropping the oldest segments. If steps
is at least segmentCount, all segments are cleared.
steps: number = 1How many steps to advance. Defaults to
1. Must be a non-negative integer.
Returns
The sum of the counts that were removed.
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(2);
counter.increment(10);
counter.rotate();
const evicted = counter.rotate();
assertEquals(evicted, 10);
Example: Bulk rotation
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(3);
counter.increment(5);
counter.rotate();
counter.increment(3);
const evicted = counter.rotate(2);
assertEquals(evicted, 5);
assertEquals(counter.total, 3);
current
get current(): numberexperimental
The count in the current (newest) segment.
Returns
The count in the current segment.
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(3);
counter.increment(5);
assertEquals(counter.current, 5);
counter.rotate();
assertEquals(counter.current, 0);
counter.increment(3);
assertEquals(counter.current, 3);
total
get total(): numberexperimental
The sum of all segment counts.
Returns
The sum of all segment counts.
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(3);
counter.increment(5);
counter.rotate();
counter.increment(3);
assertEquals(counter.total, 8);
segmentCount
get segmentCount(): numberexperimental
The number of segments in the window.
Returns
The number of segments.
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(5);
assertEquals(counter.segmentCount, 5);
at
at(index: number): number | undefinedexperimental
Returns the segment count at index, where index 0 is the oldest
segment and index segmentCount - 1 is the current (newest) segment.
Negative indices count from the newest end (at(-1) is the current
segment, same as current).
Returns undefined for out-of-range indices.
index: numberThe zero-based index in oldest-to-newest order. Negative values count from the newest end.
Returns
The count at the index, or undefined if out of range.
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(3);
counter.increment(5);
counter.rotate();
counter.increment(3);
assertEquals(counter.at(0), 0);
assertEquals(counter.at(1), 5);
assertEquals(counter.at(2), 3);
assertEquals(counter.at(-1), 3);
assertEquals(counter.at(99), undefined);
clear
clear(): voidexperimental
Resets all segments to zero, as if the counter were just created.
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(3);
counter.increment(10);
counter.clear();
assertEquals(counter.total, 0);
toArray
toArray(): number[]experimental
Returns a fresh array of segment counts ordered from oldest to newest (matching iteration order). The returned array is owned by the caller; mutating it does not affect the counter.
Returns
A new array of segment counts in oldest-to-newest order.
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(3);
counter.increment(5);
counter.rotate();
counter.increment(3);
assertEquals(counter.toArray(), [0, 5, 3]);
toJSON
toJSON(): RollingCounterSnapshotexperimental
Returns a serializable snapshot of the counter state. The segments
array is ordered oldest to newest (matching iteration order), so the
last element is the current segment.
The snapshot is compatible with JSON.stringify and can be restored
with RollingCounter.from.
Returns
A plain-object snapshot of the counter.
Example: JSON round-trip
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(3);
counter.increment(5);
const json = JSON.stringify(counter);
const restored = RollingCounter.from(JSON.parse(json));
assertEquals(restored.total, 5);
[Symbol.iterator]
[Symbol.iterator](): IterableIterator<number>experimental
Yields segment counts from oldest to newest.
Returns
An iterator over segment counts.
Example: Usage
import { RollingCounter } from "@ende/data-structures/unstable-rolling-counter";
import { assertEquals } from "@ende/assert";
const counter = new RollingCounter(3);
counter.increment(5);
counter.rotate();
counter.increment(3);
assertEquals([...counter], [0, 5, 3]);