Skip to main content

ring_seq/
lib.rs

1//! Circular (ring) sequence operations for Rust slices.
2//!
3//! `ring-seq` extends `[T]` (and, through [`Deref`](core::ops::Deref) coercion, [`Vec<T>`],
4//! arrays, and [`Box<[T]>`](Box)) with operations that treat the sequence as
5//! **circular** — the element after the last wraps back to the first.
6//!
7//! # Quick start
8//!
9//! ```
10//! use ring_seq::RingSeq;
11//!
12//! // Indexing wraps around
13//! assert_eq!(*[10, 20, 30].apply_o(4), 20);
14//!
15//! // Rotation produces a new Vec
16//! assert_eq!([0, 1, 2].rotate_right(1), vec![2, 0, 1]);
17//!
18//! // Comparison up to rotation
19//! assert!([0, 1, 2].is_rotation_of(&[2, 0, 1]));
20//!
21//! // Canonical (necklace) form for deduplication
22//! assert_eq!([2, 0, 1].canonical(), vec![0, 1, 2]);
23//!
24//! // Symmetry detection
25//! assert_eq!([0, 1, 0, 1].rotational_symmetry(), 2);
26//! ```
27//!
28//! # Naming convention
29//!
30//! Most methods use plain, Rust-idiomatic names (`take_while`, `span`,
31//! `contains_slice`, etc.) since every method on [`RingSeq`] is circular by
32//! definition.
33//!
34//! A few methods carry a distinguishing name to avoid confusion with
35//! standard-library counterparts:
36//!
37//! * `apply_o` — circular element access (the `_o` signals a circular index).
38//! * `slice_o` — circular slicing (`slice` is a fundamental Rust type).
39//! * `circular_windows`, `circular_chunks`, `circular_enumerate` — circular
40//!   variants of [`windows`](slice::windows), [`chunks`](slice::chunks), and
41//!   [`enumerate`](Iterator::enumerate).
42//!
43//! # Interaction with `[T]::rotate_left` / `[T]::rotate_right`
44//!
45//! The standard library provides in-place `rotate_left(&mut self, mid: usize)`
46//! and `rotate_right(&mut self, mid: usize)` on mutable slices. This crate's
47//! methods have the same names but differ in signature: they take `&self` and
48//! an `isize` step (which may be negative), and return a new `Vec<T>`. The
49//! compiler resolves calls unambiguously based on mutability and argument type.
50//! If you need to force the circular variant on a `&mut` slice, call
51//! `.as_ref()` or reborrow as `&*slice` first.
52
53mod iterators;
54
55pub use iterators::{Reflections, Reversions, Rotations, RotationsAndReflections, SlidingO};
56
57// ============================================================================
58// AxisLocation
59// ============================================================================
60
61/// A location on a circular sequence where a reflectional-symmetry axis passes.
62///
63/// # Variants
64///
65/// * [`Vertex`](AxisLocation::Vertex) — the axis passes through the element at
66///   the given index.
67/// * [`Edge`](AxisLocation::Edge) — the axis passes between two consecutive
68///   elements.
69#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
70pub enum AxisLocation {
71    /// The axis passes through the element at this index.
72    Vertex(usize),
73    /// The axis passes between the elements at these two consecutive indices.
74    /// The invariant `j == (i + 1) % n` is maintained by [`AxisLocation::edge`].
75    Edge(usize, usize),
76}
77
78impl AxisLocation {
79    /// Constructs an [`Edge`](AxisLocation::Edge) between consecutive elements
80    /// of a ring of size `n`, starting at index `i`.
81    ///
82    /// The second index is computed as `(i + 1) % n`.
83    ///
84    /// # Panics
85    ///
86    /// Panics if `n` is zero.
87    ///
88    /// # Examples
89    ///
90    /// ```
91    /// use ring_seq::AxisLocation;
92    ///
93    /// assert_eq!(AxisLocation::edge(2, 3), AxisLocation::Edge(2, 0));
94    /// assert_eq!(AxisLocation::edge(0, 4), AxisLocation::Edge(0, 1));
95    /// ```
96    #[must_use]
97    pub fn edge(i: usize, n: usize) -> Self {
98        assert!(n > 0, "ring size must be positive");
99        let ii = i % n;
100        AxisLocation::Edge(ii, (ii + 1) % n)
101    }
102}
103
104// ============================================================================
105// RingSeq trait
106// ============================================================================
107
108/// Circular (ring) sequence operations on `[T]`.
109///
110/// Import this trait to gain circular methods on slices, [`Vec`]s, and arrays:
111///
112/// ```
113/// use ring_seq::RingSeq;
114/// ```
115pub trait RingSeq<T> {
116    // ── Indexing ────────────────────────────────────────────────────────
117
118    /// Normalizes a circular index to `[0, len)`.
119    ///
120    /// Uses Euclidean remainder so that negative indices wrap correctly.
121    ///
122    /// # Panics
123    ///
124    /// Panics if the slice is empty (division by zero).
125    ///
126    /// # Examples
127    ///
128    /// ```
129    /// use ring_seq::RingSeq;
130    ///
131    /// assert_eq!([10, 20, 30].index_from(4), 1);
132    /// assert_eq!([10, 20, 30].index_from(-1), 2);
133    /// ```
134    #[must_use]
135    fn index_from(&self, i: isize) -> usize;
136
137    /// Returns a reference to the element at circular index `i`.
138    ///
139    /// # Panics
140    ///
141    /// Panics if the slice is empty.
142    ///
143    /// # Examples
144    ///
145    /// ```
146    /// use ring_seq::RingSeq;
147    ///
148    /// assert_eq!(*[10, 20, 30].apply_o(3), 10);
149    /// assert_eq!(*[10, 20, 30].apply_o(-1), 30);
150    /// ```
151    #[must_use]
152    fn apply_o(&self, i: isize) -> &T;
153
154    // ── Transforming ───────────────────────────────────────────────────
155
156    /// Rotates the sequence to the right by `step` positions, returning a new
157    /// `Vec`.
158    ///
159    /// A negative step rotates to the left.
160    ///
161    /// # Examples
162    ///
163    /// ```
164    /// use ring_seq::RingSeq;
165    ///
166    /// assert_eq!([0, 1, 2].rotate_right(1), vec![2, 0, 1]);
167    /// assert_eq!([0, 1, 2].rotate_right(-1), vec![1, 2, 0]);
168    /// ```
169    #[must_use]
170    fn rotate_right(&self, step: isize) -> Vec<T>
171    where
172        T: Clone;
173
174    /// Rotates the sequence to the left by `step` positions, returning a new
175    /// `Vec`.
176    ///
177    /// A negative step rotates to the right.
178    ///
179    /// # Examples
180    ///
181    /// ```
182    /// use ring_seq::RingSeq;
183    ///
184    /// assert_eq!([0, 1, 2].rotate_left(1), vec![1, 2, 0]);
185    /// ```
186    #[must_use]
187    fn rotate_left(&self, step: isize) -> Vec<T>
188    where
189        T: Clone;
190
191    /// Rotates the sequence so that circular index `i` becomes the first
192    /// element.
193    ///
194    /// Equivalent to [`rotate_left`](RingSeq::rotate_left).
195    ///
196    /// # Examples
197    ///
198    /// ```
199    /// use ring_seq::RingSeq;
200    ///
201    /// assert_eq!([0, 1, 2].start_at(1), vec![1, 2, 0]);
202    /// ```
203    #[must_use]
204    fn start_at(&self, i: isize) -> Vec<T>
205    where
206        T: Clone;
207
208    /// Reflects (reverses) the sequence and rotates so that circular index `i`
209    /// is the first element.
210    ///
211    /// Equivalent to `start_at(i + 1)` followed by `reverse`.
212    ///
213    /// # Examples
214    ///
215    /// ```
216    /// use ring_seq::RingSeq;
217    ///
218    /// assert_eq!([0, 1, 2].reflect_at(0), vec![0, 2, 1]);
219    /// ```
220    #[must_use]
221    fn reflect_at(&self, i: isize) -> Vec<T>
222    where
223        T: Clone;
224
225    // ── Slicing ────────────────────────────────────────────────────────
226
227    /// Length of the longest prefix of elements, starting from circular index
228    /// `from`, that satisfy `pred`.
229    ///
230    /// # Examples
231    ///
232    /// ```
233    /// use ring_seq::RingSeq;
234    ///
235    /// assert_eq!([0, 1, 2].segment_length(|x| x % 2 == 0, 2), 2);
236    /// ```
237    #[must_use]
238    fn segment_length(&self, pred: impl Fn(&T) -> bool, from: isize) -> usize;
239
240    /// Takes the longest prefix of elements, starting from circular index
241    /// `from`, that satisfy `pred`.
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// use ring_seq::RingSeq;
247    ///
248    /// assert_eq!([0, 1, 2, 3, 4].take_while(|&x| x < 3, 1), vec![1, 2]);
249    /// ```
250    #[must_use]
251    fn take_while(&self, pred: impl Fn(&T) -> bool, from: isize) -> Vec<T>
252    where
253        T: Clone;
254
255    /// Drops the longest prefix of elements, starting from circular index
256    /// `from`, that satisfy `pred`.
257    ///
258    /// # Examples
259    ///
260    /// ```
261    /// use ring_seq::RingSeq;
262    ///
263    /// assert_eq!([0, 1, 2, 3, 4].drop_while(|&x| x < 3, 1), vec![3, 4, 0]);
264    /// ```
265    #[must_use]
266    fn drop_while(&self, pred: impl Fn(&T) -> bool, from: isize) -> Vec<T>
267    where
268        T: Clone;
269
270    /// Splits the circular sequence at the first element (starting from
271    /// circular index `from`) that does **not** satisfy `pred`.
272    ///
273    /// Returns `(take_while(pred, from), drop_while(pred, from))`.
274    ///
275    /// # Examples
276    ///
277    /// ```
278    /// use ring_seq::RingSeq;
279    ///
280    /// let (a, b) = [0, 1, 2, 3, 4].span(|&x| x < 3, 1);
281    /// assert_eq!(a, vec![1, 2]);
282    /// assert_eq!(b, vec![3, 4, 0]);
283    /// ```
284    #[must_use]
285    fn span(&self, pred: impl Fn(&T) -> bool, from: isize) -> (Vec<T>, Vec<T>)
286    where
287        T: Clone;
288
289    /// Selects a circular interval of elements from index `from` (inclusive)
290    /// to index `to` (exclusive).
291    ///
292    /// The resulting slice can be **longer** than the ring when `to - from`
293    /// exceeds the ring length, because the ring repeats circularly.
294    ///
295    /// Returns an empty `Vec` when `from >= to` or the ring is empty.
296    ///
297    /// # Examples
298    ///
299    /// ```
300    /// use ring_seq::RingSeq;
301    ///
302    /// assert_eq!([0, 1, 2].slice_o(-1, 4), vec![2, 0, 1, 2, 0]);
303    /// assert_eq!([0, 1, 2].slice_o(1, 3), vec![1, 2]);
304    /// ```
305    #[must_use]
306    fn slice_o(&self, from: isize, to: isize) -> Vec<T>
307    where
308        T: Clone;
309
310    /// Tests whether this ring contains `slice` as a contiguous circular
311    /// subsequence.
312    ///
313    /// The slice may wrap around the ring boundary and may even be longer
314    /// than the ring (repeating elements are matched cyclically).
315    ///
316    /// # Examples
317    ///
318    /// ```
319    /// use ring_seq::RingSeq;
320    ///
321    /// assert!([0, 1, 2].contains_slice(&[2, 0, 1, 2, 0]));
322    /// assert!(![0, 1, 2].contains_slice(&[1, 0]));
323    /// ```
324    #[must_use]
325    fn contains_slice(&self, slice: &[T]) -> bool
326    where
327        T: PartialEq;
328
329    /// Finds the first circular index at or after `from` where `slice` appears
330    /// as a contiguous subsequence.
331    ///
332    /// Returns `None` if not found.
333    ///
334    /// # Examples
335    ///
336    /// ```
337    /// use ring_seq::RingSeq;
338    ///
339    /// assert_eq!([0, 1, 2].index_of_slice(&[2, 0, 1, 2, 0], 0), Some(2));
340    /// ```
341    #[must_use]
342    fn index_of_slice(&self, slice: &[T], from: isize) -> Option<usize>
343    where
344        T: PartialEq;
345
346    /// Finds the last circular index at or before `end` where `slice` appears
347    /// as a contiguous subsequence.
348    ///
349    /// Returns `None` if not found.
350    ///
351    /// # Examples
352    ///
353    /// ```
354    /// use ring_seq::RingSeq;
355    ///
356    /// assert_eq!(
357    ///     [0, 1, 2, 0, 1, 2].last_index_of_slice(&[2, 0], -1),
358    ///     Some(5),
359    /// );
360    /// ```
361    #[must_use]
362    fn last_index_of_slice(&self, slice: &[T], end: isize) -> Option<usize>
363    where
364        T: PartialEq;
365
366    // ── Iterating ──────────────────────────────────────────────────────
367
368    /// Sliding windows of `size` elements, advancing by `step` each time,
369    /// wrapping around the ring boundary.
370    ///
371    /// # Panics
372    ///
373    /// Panics if `size` or `step` is zero.
374    ///
375    /// # Examples
376    ///
377    /// ```
378    /// use ring_seq::RingSeq;
379    ///
380    /// let windows: Vec<_> = [0, 1, 2].circular_windows(2, 1).collect();
381    /// assert_eq!(windows, vec![vec![0, 1], vec![1, 2], vec![2, 0]]);
382    /// ```
383    #[must_use]
384    fn circular_windows(&self, size: usize, step: usize) -> SlidingO<T>
385    where
386        T: Clone;
387
388    /// Partitions the circular sequence into `ceil(n / size)` non-overlapping
389    /// chunks of exactly `size` elements each; the last chunk wraps across the
390    /// seam when `size` does not divide `n`.
391    ///
392    /// # Panics
393    ///
394    /// Panics if `size` is zero.
395    ///
396    /// # Examples
397    ///
398    /// ```
399    /// use ring_seq::RingSeq;
400    ///
401    /// let groups: Vec<_> = [0, 1, 2, 3, 4].circular_chunks(2).collect();
402    /// assert_eq!(groups, vec![vec![0, 1], vec![2, 3], vec![4, 0]]);
403    /// ```
404    #[must_use]
405    fn circular_chunks(&self, size: usize) -> SlidingO<T>
406    where
407        T: Clone;
408
409    /// Pairs each element with its original (circular) index, starting from
410    /// circular index `from`.
411    ///
412    /// # Examples
413    ///
414    /// ```
415    /// use ring_seq::RingSeq;
416    ///
417    /// assert_eq!(
418    ///     ['a', 'b', 'c'].circular_enumerate(1),
419    ///     vec![('b', 1), ('c', 2), ('a', 0)],
420    /// );
421    /// ```
422    #[must_use]
423    fn circular_enumerate(&self, from: isize) -> Vec<(T, usize)>
424    where
425        T: Clone;
426
427    /// All rotations of this ring.
428    ///
429    /// Yields `n` items for a non-empty ring (starting from the identity
430    /// rotation), or a single empty `Vec` for an empty ring.
431    ///
432    /// # Examples
433    ///
434    /// ```
435    /// use ring_seq::RingSeq;
436    ///
437    /// let rots: Vec<_> = [0, 1, 2].rotations().collect();
438    /// assert_eq!(rots, vec![vec![0, 1, 2], vec![1, 2, 0], vec![2, 0, 1]]);
439    /// ```
440    #[must_use]
441    fn rotations(&self) -> Rotations<'_, T>;
442
443    /// The original ring and its reflection at index 0.
444    ///
445    /// Yields 2 items for a non-empty ring, or a single empty `Vec` for an
446    /// empty ring.
447    ///
448    /// # Examples
449    ///
450    /// ```
451    /// use ring_seq::RingSeq;
452    ///
453    /// let refs: Vec<_> = [0, 1, 2].reflections().collect();
454    /// assert_eq!(refs, vec![vec![0, 1, 2], vec![0, 2, 1]]);
455    /// ```
456    #[must_use]
457    fn reflections(&self) -> Reflections<'_, T>;
458
459    /// The original ring and its reversal.
460    ///
461    /// Yields 2 items for a non-empty ring, or a single empty `Vec` for an
462    /// empty ring.
463    ///
464    /// # Examples
465    ///
466    /// ```
467    /// use ring_seq::RingSeq;
468    ///
469    /// let revs: Vec<_> = [0, 1, 2].reversions().collect();
470    /// assert_eq!(revs, vec![vec![0, 1, 2], vec![2, 1, 0]]);
471    /// ```
472    #[must_use]
473    fn reversions(&self) -> Reversions<'_, T>;
474
475    /// All rotations of the original ring followed by all rotations of its
476    /// reflection.
477    ///
478    /// Yields `2n` items for a non-empty ring, or a single empty `Vec` for an
479    /// empty ring.
480    ///
481    /// # Examples
482    ///
483    /// ```
484    /// use ring_seq::RingSeq;
485    ///
486    /// let all: Vec<_> = [0, 1, 2].rotations_and_reflections().collect();
487    /// assert_eq!(all, vec![
488    ///     vec![0, 1, 2], vec![1, 2, 0], vec![2, 0, 1],
489    ///     vec![0, 2, 1], vec![2, 1, 0], vec![1, 0, 2],
490    /// ]);
491    /// ```
492    #[must_use]
493    fn rotations_and_reflections(&self) -> RotationsAndReflections<'_, T>
494    where
495        T: Clone;
496
497    // ── Comparing ──────────────────────────────────────────────────────
498
499    /// Tests whether this ring is a rotation of `that`.
500    ///
501    /// Two sequences are rotations of each other iff they have the same length
502    /// and one appears as a contiguous substring inside the other repeated
503    /// twice.
504    ///
505    /// # Examples
506    ///
507    /// ```
508    /// use ring_seq::RingSeq;
509    ///
510    /// assert!([0, 1, 2].is_rotation_of(&[1, 2, 0]));
511    /// assert!(![0, 1, 2].is_rotation_of(&[0, 2, 1]));
512    /// ```
513    #[must_use]
514    fn is_rotation_of(&self, that: &[T]) -> bool
515    where
516        T: PartialEq;
517
518    /// Tests whether this ring is the reflection at index 0 of `that` (or
519    /// identical to it).
520    ///
521    /// This checks a single specific reflection axis.  For rotation-insensitive
522    /// comparison, see [`is_rotation_or_reflection_of`](RingSeq::is_rotation_or_reflection_of).
523    ///
524    /// # Examples
525    ///
526    /// ```
527    /// use ring_seq::RingSeq;
528    ///
529    /// assert!([0, 1, 2].is_reflection_of(&[0, 2, 1]));
530    /// ```
531    #[must_use]
532    fn is_reflection_of(&self, that: &[T]) -> bool
533    where
534        T: PartialEq + Clone;
535
536    /// Tests whether this ring is the reversal of `that` (or identical to it).
537    ///
538    /// # Examples
539    ///
540    /// ```
541    /// use ring_seq::RingSeq;
542    ///
543    /// assert!([0, 1, 2].is_reversion_of(&[2, 1, 0]));
544    /// ```
545    #[must_use]
546    fn is_reversion_of(&self, that: &[T]) -> bool
547    where
548        T: PartialEq;
549
550    /// Tests whether this ring is a rotation **or** a reflection of `that`.
551    ///
552    /// # Examples
553    ///
554    /// ```
555    /// use ring_seq::RingSeq;
556    ///
557    /// assert!([0, 1, 2].is_rotation_or_reflection_of(&[2, 0, 1]));
558    /// assert!([0, 1, 2].is_rotation_or_reflection_of(&[0, 2, 1]));
559    /// ```
560    #[must_use]
561    fn is_rotation_or_reflection_of(&self, that: &[T]) -> bool
562    where
563        T: PartialEq + Clone;
564
565    /// Finds the rotation offset that aligns this ring with `that`.
566    ///
567    /// Returns `Some(k)` such that `self.start_at(k) == that`, or `None` if no
568    /// rotation matches (or sizes differ).
569    ///
570    /// # Examples
571    ///
572    /// ```
573    /// use ring_seq::RingSeq;
574    ///
575    /// assert_eq!([0, 1, 2].rotation_offset(&[2, 0, 1]), Some(2));
576    /// assert_eq!([0, 1, 2].rotation_offset(&[0, 2, 1]), None);
577    /// ```
578    #[must_use]
579    fn rotation_offset(&self, that: &[T]) -> Option<usize>
580    where
581        T: PartialEq;
582
583    /// The number of positions at which corresponding elements differ.
584    ///
585    /// # Panics
586    ///
587    /// Panics if the two slices have different lengths.
588    ///
589    /// # Examples
590    ///
591    /// ```
592    /// use ring_seq::RingSeq;
593    ///
594    /// assert_eq!([1, 0, 1, 1].hamming_distance(&[1, 1, 0, 1]), 2);
595    /// ```
596    #[must_use]
597    fn hamming_distance(&self, that: &[T]) -> usize
598    where
599        T: PartialEq;
600
601    /// The minimum Hamming distance over all rotations of this ring.
602    ///
603    /// Returns `0` iff `that` is a rotation of `self`.
604    ///
605    /// # Panics
606    ///
607    /// Panics if the two slices have different lengths.
608    ///
609    /// # Examples
610    ///
611    /// ```
612    /// use ring_seq::RingSeq;
613    ///
614    /// assert_eq!([1, 0, 1, 1].min_rotational_hamming_distance(&[1, 1, 0, 1]), 0);
615    /// ```
616    #[must_use]
617    fn min_rotational_hamming_distance(&self, that: &[T]) -> usize
618    where
619        T: PartialEq;
620
621    // ── Necklace ───────────────────────────────────────────────────────
622
623    /// The starting index of the lexicographically smallest rotation
624    /// ([Booth's algorithm](https://en.wikipedia.org/wiki/Lexicographically_least_circular_substring),
625    /// *O(n)*).
626    ///
627    /// Returns `0` for empty or single-element sequences.
628    ///
629    /// # Examples
630    ///
631    /// ```
632    /// use ring_seq::RingSeq;
633    ///
634    /// assert_eq!([2, 0, 1].canonical_index(), 1);
635    /// ```
636    #[must_use]
637    fn canonical_index(&self) -> usize
638    where
639        T: Ord;
640
641    /// The lexicographically smallest rotation of this ring (necklace canonical
642    /// form).
643    ///
644    /// Two rings are rotations of each other iff their canonical forms are
645    /// equal — useful for hashing and deduplicating equivalent rings.
646    ///
647    /// # Examples
648    ///
649    /// ```
650    /// use ring_seq::RingSeq;
651    ///
652    /// assert_eq!([2, 0, 1].canonical(), vec![0, 1, 2]);
653    /// ```
654    #[must_use]
655    fn canonical(&self) -> Vec<T>
656    where
657        T: Clone + Ord;
658
659    /// The lexicographically smallest representative under both rotation and
660    /// reflection (bracelet canonical form).
661    ///
662    /// Two rings belong to the same bracelet equivalence class iff their
663    /// bracelet forms are equal — useful when mirror images are considered
664    /// identical.
665    ///
666    /// # Examples
667    ///
668    /// ```
669    /// use ring_seq::RingSeq;
670    ///
671    /// assert_eq!([2, 1, 0].bracelet(), vec![0, 1, 2]);
672    /// assert_eq!([0, 1, 2].bracelet(), vec![0, 1, 2]);
673    /// ```
674    #[must_use]
675    fn bracelet(&self) -> Vec<T>
676    where
677        T: Clone + Ord;
678
679    // ── Symmetry ───────────────────────────────────────────────────────
680
681    /// The order of rotational symmetry: the number of distinct rotations that
682    /// map the ring onto itself.
683    ///
684    /// Returns 1 for sequences with no rotational symmetry (only the identity),
685    /// and `n` when all elements are equal.
686    ///
687    /// # Examples
688    ///
689    /// ```
690    /// use ring_seq::RingSeq;
691    ///
692    /// assert_eq!([0, 1, 2, 0, 1, 2].rotational_symmetry(), 2);
693    /// assert_eq!([0, 1, 2].rotational_symmetry(), 1);
694    /// assert_eq!([5, 5, 5].rotational_symmetry(), 3);
695    /// ```
696    #[must_use]
697    fn rotational_symmetry(&self) -> usize
698    where
699        T: PartialEq;
700
701    /// Indices of elements where a reflectional-symmetry axis passes nearby.
702    ///
703    /// More precisely, the "shift" values for which the ring equals its
704    /// reversal rotated by that shift.
705    ///
706    /// # Examples
707    ///
708    /// ```
709    /// use ring_seq::RingSeq;
710    ///
711    /// assert_eq!(
712    ///     [2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2].symmetry_indices(),
713    ///     vec![0, 3, 6, 9],
714    /// );
715    /// ```
716    #[must_use]
717    fn symmetry_indices(&self) -> Vec<usize>
718    where
719        T: PartialEq;
720
721    /// Axes of reflectional symmetry, expressed as pairs of
722    /// [`AxisLocation`]s where each axis intersects the ring.
723    ///
724    /// # Examples
725    ///
726    /// ```
727    /// use ring_seq::{AxisLocation, RingSeq};
728    ///
729    /// let axes = [0, 1, 0].reflectional_symmetry_axes();
730    /// assert_eq!(axes.len(), 1);
731    /// assert_eq!(axes[0], (AxisLocation::Vertex(1), AxisLocation::Edge(2, 0)));
732    /// ```
733    #[must_use]
734    fn reflectional_symmetry_axes(&self) -> Vec<(AxisLocation, AxisLocation)>
735    where
736        T: PartialEq;
737
738    /// The number of reflectional symmetries (the number of symmetry axes).
739    ///
740    /// # Examples
741    ///
742    /// ```
743    /// use ring_seq::RingSeq;
744    ///
745    /// assert_eq!([2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2].symmetry(), 4);
746    /// assert_eq!([0, 1, 2].symmetry(), 0);
747    /// ```
748    #[must_use]
749    fn symmetry(&self) -> usize
750    where
751        T: PartialEq;
752}
753
754// ============================================================================
755// Implementation
756// ============================================================================
757
758impl<T> RingSeq<T> for [T] {
759    // ── Indexing ────────────────────────────────────────────────────────
760
761    #[inline]
762    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
763    fn index_from(&self, i: isize) -> usize {
764        i.rem_euclid(self.len() as isize) as usize
765    }
766
767    #[inline]
768    fn apply_o(&self, i: isize) -> &T {
769        &self[self.index_from(i)]
770    }
771
772    // ── Transforming ───────────────────────────────────────────────────
773
774    fn rotate_right(&self, step: isize) -> Vec<T>
775    where
776        T: Clone,
777    {
778        if self.is_empty() {
779            return vec![];
780        }
781        let n = self.len();
782        let j = n - self.index_from(step);
783        let mut v = Vec::with_capacity(n);
784        v.extend_from_slice(&self[j..]);
785        v.extend_from_slice(&self[..j]);
786        v
787    }
788
789    #[inline]
790    fn rotate_left(&self, step: isize) -> Vec<T>
791    where
792        T: Clone,
793    {
794        self.rotate_right(-step)
795    }
796
797    #[inline]
798    fn start_at(&self, i: isize) -> Vec<T>
799    where
800        T: Clone,
801    {
802        self.rotate_left(i)
803    }
804
805    fn reflect_at(&self, i: isize) -> Vec<T>
806    where
807        T: Clone,
808    {
809        let mut v = self.start_at(i + 1);
810        v.reverse();
811        v
812    }
813
814    // ── Slicing ────────────────────────────────────────────────────────
815
816    fn segment_length(&self, pred: impl Fn(&T) -> bool, from: isize) -> usize {
817        if self.is_empty() {
818            return 0;
819        }
820        let n = self.len();
821        let start = self.index_from(from);
822        let mut count = 0;
823        for k in 0..n {
824            if pred(&self[(start + k) % n]) {
825                count += 1;
826            } else {
827                break;
828            }
829        }
830        count
831    }
832
833    fn take_while(&self, pred: impl Fn(&T) -> bool, from: isize) -> Vec<T>
834    where
835        T: Clone,
836    {
837        if self.is_empty() {
838            return vec![];
839        }
840        let n = self.len();
841        let start = self.index_from(from);
842        let mut result = Vec::new();
843        for k in 0..n {
844            let elem = &self[(start + k) % n];
845            if pred(elem) {
846                result.push(elem.clone());
847            } else {
848                break;
849            }
850        }
851        result
852    }
853
854    fn drop_while(&self, pred: impl Fn(&T) -> bool, from: isize) -> Vec<T>
855    where
856        T: Clone,
857    {
858        if self.is_empty() {
859            return vec![];
860        }
861        let n = self.len();
862        let start = self.index_from(from);
863        let prefix_len = self.segment_length(&pred, from);
864        let mut result = Vec::with_capacity(n - prefix_len);
865        for k in prefix_len..n {
866            result.push(self[(start + k) % n].clone());
867        }
868        result
869    }
870
871    fn span(&self, pred: impl Fn(&T) -> bool, from: isize) -> (Vec<T>, Vec<T>)
872    where
873        T: Clone,
874    {
875        if self.is_empty() {
876            return (vec![], vec![]);
877        }
878        let n = self.len();
879        let start = self.index_from(from);
880        let prefix_len = self.segment_length(&pred, from);
881        let mut taken = Vec::with_capacity(prefix_len);
882        for k in 0..prefix_len {
883            taken.push(self[(start + k) % n].clone());
884        }
885        let mut dropped = Vec::with_capacity(n - prefix_len);
886        for k in prefix_len..n {
887            dropped.push(self[(start + k) % n].clone());
888        }
889        (taken, dropped)
890    }
891
892    fn slice_o(&self, from: isize, to: isize) -> Vec<T>
893    where
894        T: Clone,
895    {
896        if self.is_empty() || from >= to {
897            return vec![];
898        }
899        #[allow(clippy::cast_sign_loss)] // `to - from` is positive due to guard above
900        let length = (to - from) as usize;
901        let start = self.index_from(from);
902        self[start..]
903            .iter()
904            .chain(self.iter().cycle())
905            .take(length)
906            .cloned()
907            .collect()
908    }
909
910    fn contains_slice(&self, slice: &[T]) -> bool
911    where
912        T: PartialEq,
913    {
914        if slice.is_empty() {
915            return true;
916        }
917        if self.is_empty() {
918            return false;
919        }
920        let n = self.len();
921        let m = slice.len();
922        (0..n).any(|start| (0..m).all(|j| self[(start + j) % n] == slice[j]))
923    }
924
925    fn index_of_slice(&self, slice: &[T], from: isize) -> Option<usize>
926    where
927        T: PartialEq,
928    {
929        if slice.is_empty() {
930            return if self.is_empty() {
931                Some(0)
932            } else {
933                Some(self.index_from(from))
934            };
935        }
936        if self.is_empty() {
937            return None;
938        }
939        let n = self.len();
940        let m = slice.len();
941        let start = self.index_from(from);
942        for k in 0..n {
943            let i = (start + k) % n;
944            if (0..m).all(|j| self[(i + j) % n] == slice[j]) {
945                return Some(i);
946            }
947        }
948        None
949    }
950
951    fn last_index_of_slice(&self, slice: &[T], end: isize) -> Option<usize>
952    where
953        T: PartialEq,
954    {
955        if slice.is_empty() {
956            return if self.is_empty() {
957                Some(0)
958            } else {
959                Some(self.index_from(end))
960            };
961        }
962        if self.is_empty() {
963            return None;
964        }
965        let n = self.len();
966        let m = slice.len();
967        let end_idx = self.index_from(end);
968        for k in 0..n {
969            let i = (end_idx + n - k) % n;
970            if (0..m).all(|j| self[(i + j) % n] == slice[j]) {
971                return Some(i);
972            }
973        }
974        None
975    }
976
977    // ── Iterating ──────────────────────────────────────────────────────
978
979    fn circular_windows(&self, size: usize, step: usize) -> SlidingO<T>
980    where
981        T: Clone,
982    {
983        assert!(size > 0, "window size must be positive");
984        assert!(step > 0, "step must be positive");
985        if self.is_empty() {
986            return SlidingO {
987                data: vec![],
988                window_size: size,
989                step,
990                pos: 0,
991            };
992        }
993        let total_len = step * (self.len() - 1) + size;
994        #[allow(clippy::cast_possible_wrap)]
995        SlidingO {
996            data: self.slice_o(0, total_len as isize),
997            window_size: size,
998            step,
999            pos: 0,
1000        }
1001    }
1002
1003    fn circular_chunks(&self, size: usize) -> SlidingO<T>
1004    where
1005        T: Clone,
1006    {
1007        assert!(size > 0, "group size must be positive");
1008        if self.is_empty() {
1009            return SlidingO {
1010                data: vec![],
1011                window_size: size,
1012                step: size,
1013                pos: 0,
1014            };
1015        }
1016        let count = (self.len() + size - 1) / size;
1017        let total_len = count * size;
1018        #[allow(clippy::cast_possible_wrap)]
1019        SlidingO {
1020            data: self.slice_o(0, total_len as isize),
1021            window_size: size,
1022            step: size,
1023            pos: 0,
1024        }
1025    }
1026
1027    fn circular_enumerate(&self, from: isize) -> Vec<(T, usize)>
1028    where
1029        T: Clone,
1030    {
1031        let n = self.len();
1032        if n == 0 {
1033            return vec![];
1034        }
1035        let start = self.index_from(from);
1036        (0..n)
1037            .map(|k| {
1038                let idx = (start + k) % n;
1039                (self[idx].clone(), idx)
1040            })
1041            .collect()
1042    }
1043
1044    fn rotations(&self) -> Rotations<'_, T> {
1045        Rotations {
1046            ring: self,
1047            index: 0,
1048            total: if self.is_empty() { 1 } else { self.len() },
1049        }
1050    }
1051
1052    fn reflections(&self) -> Reflections<'_, T> {
1053        Reflections {
1054            ring: self,
1055            state: 0,
1056        }
1057    }
1058
1059    fn reversions(&self) -> Reversions<'_, T> {
1060        Reversions {
1061            ring: self,
1062            state: 0,
1063        }
1064    }
1065
1066    fn rotations_and_reflections(&self) -> RotationsAndReflections<'_, T>
1067    where
1068        T: Clone,
1069    {
1070        let reflected = self.reflect_at(0);
1071        RotationsAndReflections {
1072            ring: self,
1073            reflected,
1074            index: 0,
1075            total: if self.is_empty() { 1 } else { self.len() * 2 },
1076        }
1077    }
1078
1079    // ── Comparing ──────────────────────────────────────────────────────
1080
1081    fn is_rotation_of(&self, that: &[T]) -> bool
1082    where
1083        T: PartialEq,
1084    {
1085        if self.len() != that.len() {
1086            return false;
1087        }
1088        if self.is_empty() {
1089            return true;
1090        }
1091        contains_as_rotation(self, that)
1092    }
1093
1094    fn is_reflection_of(&self, that: &[T]) -> bool
1095    where
1096        T: PartialEq + Clone,
1097    {
1098        if self.len() != that.len() {
1099            return false;
1100        }
1101        self == that || self.reflect_at(0) == that
1102    }
1103
1104    fn is_reversion_of(&self, that: &[T]) -> bool
1105    where
1106        T: PartialEq,
1107    {
1108        if self.len() != that.len() {
1109            return false;
1110        }
1111        self == that || self.iter().rev().eq(that.iter())
1112    }
1113
1114    fn is_rotation_or_reflection_of(&self, that: &[T]) -> bool
1115    where
1116        T: PartialEq + Clone,
1117    {
1118        if self.len() != that.len() {
1119            return false;
1120        }
1121        contains_as_rotation(self, that) || contains_as_rotation(&self.reflect_at(0), that)
1122    }
1123
1124    fn rotation_offset(&self, that: &[T]) -> Option<usize>
1125    where
1126        T: PartialEq,
1127    {
1128        if self.len() != that.len() {
1129            return None;
1130        }
1131        if self.is_empty() {
1132            return Some(0);
1133        }
1134        let n = self.len();
1135        (0..n).find(|&i| (0..n).all(|j| self[(i + j) % n] == that[j]))
1136    }
1137
1138    fn hamming_distance(&self, that: &[T]) -> usize
1139    where
1140        T: PartialEq,
1141    {
1142        assert_eq!(self.len(), that.len(), "sequences must have the same size");
1143        self.iter().zip(that.iter()).filter(|(a, b)| a != b).count()
1144    }
1145
1146    fn min_rotational_hamming_distance(&self, that: &[T]) -> usize
1147    where
1148        T: PartialEq,
1149    {
1150        assert_eq!(self.len(), that.len(), "sequences must have the same size");
1151        let n = self.len();
1152        if n == 0 {
1153            return 0;
1154        }
1155        let mut best = usize::MAX;
1156        let mut k = 0;
1157        while k < n && best != 0 {
1158            let mut count = 0;
1159            let mut ai = k;
1160            let mut i = 0;
1161            while i < n && count < best {
1162                if self[ai] != that[i] {
1163                    count += 1;
1164                }
1165                ai += 1;
1166                if ai == n {
1167                    ai = 0;
1168                }
1169                i += 1;
1170            }
1171            if count < best {
1172                best = count;
1173            }
1174            k += 1;
1175        }
1176        best
1177    }
1178
1179    // ── Necklace ───────────────────────────────────────────────────────
1180
1181    fn canonical_index(&self) -> usize
1182    where
1183        T: Ord,
1184    {
1185        if self.len() <= 1 {
1186            0
1187        } else {
1188            booth_least_rotation(self)
1189        }
1190    }
1191
1192    fn canonical(&self) -> Vec<T>
1193    where
1194        T: Clone + Ord,
1195    {
1196        #[allow(clippy::cast_possible_wrap)]
1197        self.start_at(self.canonical_index() as isize)
1198    }
1199
1200    fn bracelet(&self) -> Vec<T>
1201    where
1202        T: Clone + Ord,
1203    {
1204        let a = self.canonical();
1205        let b = self.reflect_at(0).canonical();
1206        if a <= b {
1207            a
1208        } else {
1209            b
1210        }
1211    }
1212
1213    // ── Symmetry ───────────────────────────────────────────────────────
1214
1215    fn rotational_symmetry(&self) -> usize
1216    where
1217        T: PartialEq,
1218    {
1219        let n = self.len();
1220        if n < 2 {
1221            return 1;
1222        }
1223        let smallest_period = (1..=n)
1224            .find(|&shift| n % shift == 0 && (0..n - shift).all(|i| self[i] == self[i + shift]));
1225        n / smallest_period.unwrap_or(n)
1226    }
1227
1228    fn symmetry_indices(&self) -> Vec<usize>
1229    where
1230        T: PartialEq,
1231    {
1232        let n = self.len();
1233        if n == 0 {
1234            return vec![];
1235        }
1236        let reversed: Vec<&T> = self.iter().rev().collect();
1237        (0..n)
1238            .filter(|&shift| (0..n).all(|i| self[i] == *reversed[(i + shift) % n]))
1239            .collect()
1240    }
1241
1242    fn reflectional_symmetry_axes(&self) -> Vec<(AxisLocation, AxisLocation)>
1243    where
1244        T: PartialEq,
1245    {
1246        let n = self.len();
1247        if n == 0 {
1248            return vec![];
1249        }
1250
1251        #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
1252        self.symmetry_indices()
1253            .into_iter()
1254            .map(|shift| {
1255                let raw_k = (n as isize - 1 - shift as isize) % n as isize;
1256                let k = if raw_k < 0 {
1257                    (raw_k + n as isize) as usize
1258                } else {
1259                    raw_k as usize
1260                };
1261
1262                if n % 2 != 0 {
1263                    // Odd n: one vertex fixed point, opposite edge
1264                    let v = (k * (n + 1) / 2) % n;
1265                    let opp = (v + n / 2) % n;
1266                    (AxisLocation::Vertex(v), AxisLocation::edge(opp, n))
1267                } else if k % 2 == 0 {
1268                    // Even n, even k: two vertex fixed points
1269                    let v1 = k / 2;
1270                    let v2 = (v1 + n / 2) % n;
1271                    (AxisLocation::Vertex(v1), AxisLocation::Vertex(v2))
1272                } else {
1273                    // Even n, odd k: two edge midpoints
1274                    let e1 = (k - 1) / 2;
1275                    let e2 = (e1 + n / 2) % n;
1276                    (AxisLocation::edge(e1, n), AxisLocation::edge(e2, n))
1277                }
1278            })
1279            .collect()
1280    }
1281
1282    fn symmetry(&self) -> usize
1283    where
1284        T: PartialEq,
1285    {
1286        self.symmetry_indices().len()
1287    }
1288}
1289
1290// ============================================================================
1291// Private helpers
1292// ============================================================================
1293
1294/// Tests whether `that` appears as a rotation of `ring`, using the
1295/// "concatenate and search" technique: any rotation of `ring` is a contiguous
1296/// substring of `ring ++ ring[..n-1]`.
1297fn contains_as_rotation<T: PartialEq>(ring: &[T], that: &[T]) -> bool {
1298    if ring.is_empty() {
1299        return true;
1300    }
1301    let n = ring.len();
1302    // Check if `that` appears as a contiguous substring in ring++ring[..n-1]
1303    // without allocating, by using circular indexing.
1304    let doubled_len = 2 * n - 1;
1305    (0..n).any(|start| {
1306        (0..n).all(|j| {
1307            let idx = (start + j) % doubled_len;
1308            let elem = if idx < n { &ring[idx] } else { &ring[idx - n] };
1309            *elem == that[j]
1310        })
1311    })
1312}
1313
1314/// Booth's O(n) algorithm for finding the starting index of the
1315/// lexicographically smallest rotation.
1316#[allow(
1317    clippy::cast_sign_loss,
1318    clippy::cast_possible_wrap,
1319    clippy::many_single_char_names
1320)]
1321fn booth_least_rotation<T: Ord>(s: &[T]) -> usize {
1322    let n = s.len();
1323    let len = 2 * n;
1324    let mut f: Vec<isize> = vec![-1; len];
1325    let mut k: usize = 0;
1326    let at = |idx: usize| &s[idx % n];
1327
1328    for j in 1..len {
1329        let sj = at(j);
1330        let mut i = f[j - k - 1];
1331        while i != -1 && at(k + i as usize + 1) != sj {
1332            if sj < at(k + i as usize + 1) {
1333                k = j - i as usize - 1;
1334            }
1335            i = f[i as usize];
1336        }
1337        if i == -1 && at(k) != sj {
1338            if sj < at(k) {
1339                k = j;
1340            }
1341            f[j - k] = -1;
1342        } else {
1343            f[j - k] = i + 1;
1344        }
1345    }
1346    k
1347}
1348
1349// ============================================================================
1350// Tests
1351// ============================================================================
1352
1353#[cfg(test)]
1354mod tests {
1355    use super::*;
1356
1357    // ── Indexing ────────────────────────────────────────────────────────
1358
1359    #[test]
1360    fn index_from_positive() {
1361        assert_eq!([10, 20, 30].index_from(0), 0);
1362        assert_eq!([10, 20, 30].index_from(2), 2);
1363        assert_eq!([10, 20, 30].index_from(3), 0);
1364        assert_eq!([10, 20, 30].index_from(7), 1);
1365    }
1366
1367    #[test]
1368    fn index_from_negative() {
1369        assert_eq!([10, 20, 30].index_from(-1), 2);
1370        assert_eq!([10, 20, 30].index_from(-3), 0);
1371        assert_eq!([10, 20, 30].index_from(-4), 2);
1372    }
1373
1374    #[test]
1375    #[should_panic]
1376    fn index_from_empty_panics() {
1377        let empty: &[i32] = &[];
1378        let _ = empty.index_from(0);
1379    }
1380
1381    #[test]
1382    fn apply_o_wraps() {
1383        assert_eq!(*[10, 20, 30].apply_o(0), 10);
1384        assert_eq!(*[10, 20, 30].apply_o(3), 10);
1385        assert_eq!(*[10, 20, 30].apply_o(-1), 30);
1386    }
1387
1388    #[test]
1389    #[should_panic]
1390    fn apply_o_empty_panics() {
1391        let empty: &[i32] = &[];
1392        let _ = empty.apply_o(0);
1393    }
1394
1395    #[test]
1396    fn apply_o_single_element() {
1397        assert_eq!(*[42].apply_o(0), 42);
1398        assert_eq!(*[42].apply_o(100), 42);
1399        assert_eq!(*[42].apply_o(-99), 42);
1400    }
1401
1402    // ── Transforming ───────────────────────────────────────────────────
1403
1404    #[test]
1405    fn rotate_right_basic() {
1406        assert_eq!([0, 1, 2].rotate_right(1), vec![2, 0, 1]);
1407        assert_eq!([0, 1, 2].rotate_right(2), vec![1, 2, 0]);
1408        assert_eq!([0, 1, 2].rotate_right(3), vec![0, 1, 2]);
1409    }
1410
1411    #[test]
1412    fn rotate_right_negative() {
1413        assert_eq!([0, 1, 2].rotate_right(-1), vec![1, 2, 0]);
1414    }
1415
1416    #[test]
1417    fn rotate_right_empty() {
1418        let empty: &[i32] = &[];
1419        assert_eq!(empty.rotate_right(5), Vec::<i32>::new());
1420    }
1421
1422    #[test]
1423    fn rotate_left_basic() {
1424        assert_eq!([0, 1, 2].rotate_left(1), vec![1, 2, 0]);
1425    }
1426
1427    #[test]
1428    fn start_at_basic() {
1429        assert_eq!([0, 1, 2].start_at(1), vec![1, 2, 0]);
1430        assert_eq!([0, 1, 2].start_at(-1), vec![2, 0, 1]);
1431    }
1432
1433    #[test]
1434    fn reflect_at_basic() {
1435        assert_eq!([0, 1, 2].reflect_at(0), vec![0, 2, 1]);
1436        assert_eq!([0, 1, 2].reflect_at(1), vec![1, 0, 2]);
1437    }
1438
1439    #[test]
1440    fn reflect_at_empty() {
1441        let empty: &[i32] = &[];
1442        assert_eq!(empty.reflect_at(0), Vec::<i32>::new());
1443    }
1444
1445    #[test]
1446    fn reflect_at_single() {
1447        assert_eq!([42].reflect_at(0), vec![42]);
1448    }
1449
1450    // ── Slicing ────────────────────────────────────────────────────────
1451
1452    #[test]
1453    fn segment_length_basic() {
1454        assert_eq!([0, 1, 2].segment_length(|x| x % 2 == 0, 2), 2);
1455        assert_eq!([0, 1, 2].segment_length(|_| true, 0), 3);
1456        assert_eq!([0, 1, 2].segment_length(|_| false, 0), 0);
1457    }
1458
1459    #[test]
1460    fn segment_length_empty() {
1461        let empty: &[i32] = &[];
1462        assert_eq!(empty.segment_length(|_| true, 0), 0);
1463    }
1464
1465    #[test]
1466    fn take_while_basic() {
1467        assert_eq!([0, 1, 2, 3, 4].take_while(|&x| x < 3, 1), vec![1, 2]);
1468    }
1469
1470    #[test]
1471    fn drop_while_basic() {
1472        assert_eq!([0, 1, 2, 3, 4].drop_while(|&x| x < 3, 1), vec![3, 4, 0]);
1473    }
1474
1475    #[test]
1476    fn span_basic() {
1477        let (a, b) = [0, 1, 2, 3, 4].span(|&x| x < 3, 1);
1478        assert_eq!(a, vec![1, 2]);
1479        assert_eq!(b, vec![3, 4, 0]);
1480    }
1481
1482    #[test]
1483    fn span_all_pass() {
1484        let (a, b) = [1, 2, 3].span(|_| true, 0);
1485        assert_eq!(a, vec![1, 2, 3]);
1486        assert!(b.is_empty());
1487    }
1488
1489    #[test]
1490    fn span_none_pass() {
1491        let (a, b) = [1, 2, 3].span(|_| false, 0);
1492        assert!(a.is_empty());
1493        assert_eq!(b, vec![1, 2, 3]);
1494    }
1495
1496    #[test]
1497    fn slice_o_basic() {
1498        assert_eq!([0, 1, 2].slice_o(1, 3), vec![1, 2]);
1499    }
1500
1501    #[test]
1502    fn slice_o_wrapping() {
1503        assert_eq!([0, 1, 2].slice_o(-1, 4), vec![2, 0, 1, 2, 0]);
1504    }
1505
1506    #[test]
1507    fn slice_o_empty_result() {
1508        assert_eq!([0, 1, 2].slice_o(3, 3), Vec::<i32>::new());
1509        assert_eq!([0, 1, 2].slice_o(5, 2), Vec::<i32>::new());
1510    }
1511
1512    #[test]
1513    fn slice_o_empty_ring() {
1514        let empty: &[i32] = &[];
1515        assert_eq!(empty.slice_o(0, 5), Vec::<i32>::new());
1516    }
1517
1518    #[test]
1519    fn contains_slice_basic() {
1520        assert!([0, 1, 2].contains_slice(&[2, 0]));
1521        assert!([0, 1, 2].contains_slice(&[2, 0, 1, 2, 0]));
1522        assert!(![0, 1, 2].contains_slice(&[1, 0]));
1523    }
1524
1525    #[test]
1526    fn contains_slice_empty_slice() {
1527        assert!([0, 1, 2].contains_slice(&[]));
1528    }
1529
1530    #[test]
1531    fn contains_slice_empty_ring() {
1532        let empty: &[i32] = &[];
1533        assert!(!empty.contains_slice(&[1]));
1534        assert!(empty.contains_slice(&[]));
1535    }
1536
1537    #[test]
1538    fn index_of_slice_basic() {
1539        assert_eq!([0, 1, 2].index_of_slice(&[2, 0, 1, 2, 0], 0), Some(2));
1540        assert_eq!([0, 1, 2].index_of_slice(&[0, 1], 0), Some(0));
1541        assert_eq!([0, 1, 2].index_of_slice(&[9], 0), None);
1542    }
1543
1544    #[test]
1545    fn index_of_slice_with_from() {
1546        assert_eq!([0, 1, 2, 0, 1, 2].index_of_slice(&[0, 1], 1), Some(3));
1547    }
1548
1549    #[test]
1550    fn last_index_of_slice_basic() {
1551        assert_eq!([0, 1, 2, 0, 1, 2].last_index_of_slice(&[2, 0], -1), Some(5));
1552    }
1553
1554    // ── Iterating ──────────────────────────────────────────────────────
1555
1556    #[test]
1557    fn circular_windows_basic() {
1558        let windows: Vec<_> = [0, 1, 2].circular_windows(2, 1).collect();
1559        assert_eq!(windows, vec![vec![0, 1], vec![1, 2], vec![2, 0]]);
1560    }
1561
1562    #[test]
1563    fn circular_windows_empty() {
1564        let windows: Vec<Vec<i32>> = ([] as [i32; 0]).circular_windows(2, 1).collect();
1565        assert!(windows.is_empty());
1566    }
1567
1568    #[test]
1569    fn circular_windows_exact_size() {
1570        let iter = [0, 1, 2].circular_windows(2, 1);
1571        assert_eq!(iter.len(), 3);
1572    }
1573
1574    #[test]
1575    #[should_panic]
1576    fn circular_windows_zero_size_panics() {
1577        let _ = [0, 1, 2].circular_windows(0, 1);
1578    }
1579
1580    #[test]
1581    #[should_panic]
1582    fn circular_windows_zero_step_panics() {
1583        let _ = [0, 1, 2].circular_windows(1, 0);
1584    }
1585
1586    #[test]
1587    fn circular_chunks_basic() {
1588        let groups: Vec<_> = [0, 1, 2, 3, 4].circular_chunks(2).collect();
1589        assert_eq!(groups, vec![vec![0, 1], vec![2, 3], vec![4, 0]]);
1590    }
1591
1592    #[test]
1593    fn circular_chunks_evenly_divisible() {
1594        let groups: Vec<_> = [0, 1, 2, 3].circular_chunks(2).collect();
1595        assert_eq!(groups, vec![vec![0, 1], vec![2, 3]]);
1596    }
1597
1598    #[test]
1599    fn circular_enumerate_basic() {
1600        assert_eq!(
1601            ['a', 'b', 'c'].circular_enumerate(1),
1602            vec![('b', 1), ('c', 2), ('a', 0)]
1603        );
1604    }
1605
1606    #[test]
1607    fn circular_enumerate_empty() {
1608        let empty: &[i32] = &[];
1609        assert!(empty.circular_enumerate(0).is_empty());
1610    }
1611
1612    #[test]
1613    fn rotations_basic() {
1614        let rots: Vec<_> = [0, 1, 2].rotations().collect();
1615        assert_eq!(rots, vec![vec![0, 1, 2], vec![1, 2, 0], vec![2, 0, 1]]);
1616    }
1617
1618    #[test]
1619    fn rotations_empty() {
1620        let empty: &[i32] = &[];
1621        let rots: Vec<_> = empty.rotations().collect();
1622        assert_eq!(rots, vec![Vec::<i32>::new()]);
1623    }
1624
1625    #[test]
1626    fn rotations_exact_size() {
1627        assert_eq!([0, 1, 2].rotations().len(), 3);
1628        let empty: &[i32] = &[];
1629        assert_eq!(empty.rotations().len(), 1);
1630    }
1631
1632    #[test]
1633    fn reflections_basic() {
1634        let refs: Vec<_> = [0, 1, 2].reflections().collect();
1635        assert_eq!(refs, vec![vec![0, 1, 2], vec![0, 2, 1]]);
1636    }
1637
1638    #[test]
1639    fn reflections_empty() {
1640        let empty: &[i32] = &[];
1641        let refs: Vec<_> = empty.reflections().collect();
1642        assert_eq!(refs, vec![Vec::<i32>::new()]);
1643    }
1644
1645    #[test]
1646    fn reversions_basic() {
1647        let revs: Vec<_> = [0, 1, 2].reversions().collect();
1648        assert_eq!(revs, vec![vec![0, 1, 2], vec![2, 1, 0]]);
1649    }
1650
1651    #[test]
1652    fn rotations_and_reflections_basic() {
1653        let all: Vec<_> = [0, 1, 2].rotations_and_reflections().collect();
1654        assert_eq!(
1655            all,
1656            vec![
1657                vec![0, 1, 2],
1658                vec![1, 2, 0],
1659                vec![2, 0, 1],
1660                vec![0, 2, 1],
1661                vec![2, 1, 0],
1662                vec![1, 0, 2],
1663            ]
1664        );
1665    }
1666
1667    #[test]
1668    fn rotations_and_reflections_empty() {
1669        let empty: &[i32] = &[];
1670        let all: Vec<_> = empty.rotations_and_reflections().collect();
1671        assert_eq!(all, vec![Vec::<i32>::new()]);
1672    }
1673
1674    #[test]
1675    fn rotations_and_reflections_exact_size() {
1676        assert_eq!([0, 1, 2].rotations_and_reflections().len(), 6);
1677    }
1678
1679    // ── Comparing ──────────────────────────────────────────────────────
1680
1681    #[test]
1682    fn is_rotation_of_true() {
1683        assert!([0, 1, 2].is_rotation_of(&[1, 2, 0]));
1684        assert!([0, 1, 2].is_rotation_of(&[0, 1, 2]));
1685    }
1686
1687    #[test]
1688    fn is_rotation_of_false() {
1689        assert!(![0, 1, 2].is_rotation_of(&[0, 2, 1]));
1690        assert!(![0, 1, 2].is_rotation_of(&[0, 1]));
1691    }
1692
1693    #[test]
1694    fn is_rotation_of_empty() {
1695        let empty: &[i32] = &[];
1696        assert!(empty.is_rotation_of(&[]));
1697    }
1698
1699    #[test]
1700    fn is_reflection_of_true() {
1701        assert!([0, 1, 2].is_reflection_of(&[0, 2, 1]));
1702        assert!([0, 1, 2].is_reflection_of(&[0, 1, 2])); // identity
1703    }
1704
1705    #[test]
1706    fn is_reflection_of_false() {
1707        assert!(![0, 1, 2].is_reflection_of(&[1, 0, 2]));
1708    }
1709
1710    #[test]
1711    fn is_reversion_of_true() {
1712        assert!([0, 1, 2].is_reversion_of(&[2, 1, 0]));
1713        assert!([0, 1, 2].is_reversion_of(&[0, 1, 2])); // identity
1714    }
1715
1716    #[test]
1717    fn is_reversion_of_false() {
1718        assert!(![0, 1, 2].is_reversion_of(&[0, 2, 1]));
1719    }
1720
1721    #[test]
1722    fn is_rotation_or_reflection_of_true() {
1723        assert!([0, 1, 2].is_rotation_or_reflection_of(&[2, 0, 1])); // rotation
1724        assert!([0, 1, 2].is_rotation_or_reflection_of(&[0, 2, 1])); // reflection
1725        assert!([0, 1, 2].is_rotation_or_reflection_of(&[1, 0, 2])); // rotated reflection
1726    }
1727
1728    #[test]
1729    fn align_to_found() {
1730        assert_eq!([0, 1, 2].rotation_offset(&[2, 0, 1]), Some(2));
1731        assert_eq!([0, 1, 2].rotation_offset(&[0, 1, 2]), Some(0));
1732    }
1733
1734    #[test]
1735    fn align_to_not_found() {
1736        assert_eq!([0, 1, 2].rotation_offset(&[0, 2, 1]), None);
1737    }
1738
1739    #[test]
1740    fn align_to_different_sizes() {
1741        assert_eq!([0, 1, 2].rotation_offset(&[0, 1]), None);
1742    }
1743
1744    #[test]
1745    fn align_to_empty() {
1746        let empty: &[i32] = &[];
1747        assert_eq!(empty.rotation_offset(&[]), Some(0));
1748    }
1749
1750    #[test]
1751    fn hamming_distance_basic() {
1752        assert_eq!([1, 0, 1, 1].hamming_distance(&[1, 1, 0, 1]), 2);
1753        assert_eq!([1, 2, 3].hamming_distance(&[1, 2, 3]), 0);
1754    }
1755
1756    #[test]
1757    #[should_panic(expected = "sequences must have the same size")]
1758    fn hamming_distance_different_sizes_panics() {
1759        let _ = [1, 2, 3].hamming_distance(&[1, 2]);
1760    }
1761
1762    #[test]
1763    fn min_rotational_hamming_distance_basic() {
1764        assert_eq!(
1765            [1, 0, 1, 1].min_rotational_hamming_distance(&[1, 1, 0, 1]),
1766            0
1767        );
1768    }
1769
1770    #[test]
1771    fn min_rotational_hamming_distance_nonzero() {
1772        assert_eq!(
1773            [0, 0, 0, 1].min_rotational_hamming_distance(&[1, 1, 0, 0]),
1774            1
1775        );
1776    }
1777
1778    #[test]
1779    fn min_rotational_hamming_distance_empty() {
1780        let empty: &[i32] = &[];
1781        assert_eq!(empty.min_rotational_hamming_distance(&[]), 0);
1782    }
1783
1784    // ── Necklace ───────────────────────────────────────────────────────
1785
1786    #[test]
1787    fn canonical_index_basic() {
1788        assert_eq!([2, 0, 1].canonical_index(), 1);
1789        assert_eq!([0, 1, 2].canonical_index(), 0);
1790    }
1791
1792    #[test]
1793    fn canonical_index_empty_and_single() {
1794        let empty: &[i32] = &[];
1795        assert_eq!(empty.canonical_index(), 0);
1796        assert_eq!([42].canonical_index(), 0);
1797    }
1798
1799    #[test]
1800    fn canonical_basic() {
1801        assert_eq!([2, 0, 1].canonical(), vec![0, 1, 2]);
1802        assert_eq!([0, 1, 2].canonical(), vec![0, 1, 2]);
1803    }
1804
1805    #[test]
1806    fn canonical_all_equal() {
1807        assert_eq!([5, 5, 5].canonical(), vec![5, 5, 5]);
1808    }
1809
1810    #[test]
1811    fn canonical_rotations_are_equal() {
1812        let ring = [3, 1, 4, 1, 5];
1813        let canon = ring.canonical();
1814        for rot in ring.rotations() {
1815            assert_eq!(rot.canonical(), canon);
1816        }
1817    }
1818
1819    #[test]
1820    fn bracelet_basic() {
1821        assert_eq!([2, 1, 0].bracelet(), vec![0, 1, 2]);
1822        assert_eq!([0, 1, 2].bracelet(), vec![0, 1, 2]);
1823    }
1824
1825    #[test]
1826    fn bracelet_reflection_equivalence() {
1827        let a = [0, 1, 2];
1828        let b = [0, 2, 1];
1829        assert_eq!(a.bracelet(), b.bracelet());
1830    }
1831
1832    // ── Symmetry ───────────────────────────────────────────────────────
1833
1834    #[test]
1835    fn rotational_symmetry_basic() {
1836        assert_eq!([0, 1, 2, 0, 1, 2].rotational_symmetry(), 2);
1837        assert_eq!([0, 1, 2].rotational_symmetry(), 1);
1838        assert_eq!([5, 5, 5].rotational_symmetry(), 3);
1839    }
1840
1841    #[test]
1842    fn rotational_symmetry_small() {
1843        let empty: &[i32] = &[];
1844        assert_eq!(empty.rotational_symmetry(), 1);
1845        assert_eq!([42].rotational_symmetry(), 1);
1846        assert_eq!([1, 1].rotational_symmetry(), 2);
1847        assert_eq!([1, 2].rotational_symmetry(), 1);
1848    }
1849
1850    #[test]
1851    fn symmetry_indices_basic() {
1852        assert_eq!(
1853            [2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2].symmetry_indices(),
1854            vec![0, 3, 6, 9]
1855        );
1856    }
1857
1858    #[test]
1859    fn symmetry_indices_none() {
1860        assert!([0, 1, 2].symmetry_indices().is_empty());
1861    }
1862
1863    #[test]
1864    fn symmetry_indices_palindrome() {
1865        assert_eq!([0, 1, 0].symmetry_indices(), vec![0]);
1866    }
1867
1868    #[test]
1869    fn symmetry_basic() {
1870        assert_eq!([2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2].symmetry(), 4);
1871        assert_eq!([0, 1, 2].symmetry(), 0);
1872    }
1873
1874    #[test]
1875    fn symmetry_empty() {
1876        let empty: &[i32] = &[];
1877        assert_eq!(empty.symmetry(), 0);
1878    }
1879
1880    #[test]
1881    fn reflectional_symmetry_axes_odd() {
1882        let axes = [0, 1, 0].reflectional_symmetry_axes();
1883        assert_eq!(axes.len(), 1);
1884        assert_eq!(axes[0], (AxisLocation::Vertex(1), AxisLocation::Edge(2, 0)));
1885    }
1886
1887    #[test]
1888    fn reflectional_symmetry_axes_even_vertices() {
1889        // [0, 1, 1, 0] is symmetric — axis through vertices 0 and 2
1890        let axes = [0, 1, 1, 0].reflectional_symmetry_axes();
1891        assert!(!axes.is_empty());
1892    }
1893
1894    #[test]
1895    fn reflectional_symmetry_axes_empty() {
1896        let empty: &[i32] = &[];
1897        assert!(empty.reflectional_symmetry_axes().is_empty());
1898    }
1899
1900    // ── AxisLocation ───────────────────────────────────────────────────
1901
1902    #[test]
1903    fn axis_location_edge_constructor() {
1904        assert_eq!(AxisLocation::edge(2, 3), AxisLocation::Edge(2, 0));
1905        assert_eq!(AxisLocation::edge(0, 4), AxisLocation::Edge(0, 1));
1906        assert_eq!(AxisLocation::edge(3, 4), AxisLocation::Edge(3, 0));
1907    }
1908
1909    #[test]
1910    #[should_panic(expected = "ring size must be positive")]
1911    fn axis_location_edge_zero_size_panics() {
1912        let _ = AxisLocation::edge(0, 0);
1913    }
1914
1915    // ── Vec and array interop ──────────────────────────────────────────
1916
1917    #[test]
1918    fn works_on_vec() {
1919        let v = vec![0, 1, 2];
1920        assert_eq!(v.rotate_right(1), vec![2, 0, 1]);
1921        assert!(v.is_rotation_of(&[1, 2, 0]));
1922    }
1923
1924    #[test]
1925    fn works_on_boxed_slice() {
1926        let b: Box<[i32]> = vec![0, 1, 2].into_boxed_slice();
1927        assert_eq!(b.rotate_right(1), vec![2, 0, 1]);
1928    }
1929
1930    #[test]
1931    fn chained_operations() {
1932        // rotate_right returns Vec<T>, which derefs to [T], so we can chain
1933        let result = [0, 1, 2].rotate_right(1).reflect_at(0);
1934        // rotate_right(1) -> [2, 0, 1]
1935        // reflect_at(0) of [2, 0, 1] -> start_at(1).reverse() = [0, 1, 2].reverse() = [2, 1, 0]
1936        assert_eq!(result, vec![2, 1, 0]);
1937    }
1938
1939    // ── Booth's algorithm ──────────────────────────────────────────────
1940
1941    #[test]
1942    fn booth_all_same() {
1943        assert_eq!(booth_least_rotation(&[5, 5, 5, 5]), 0);
1944    }
1945
1946    #[test]
1947    fn booth_sorted() {
1948        assert_eq!(booth_least_rotation(&[0, 1, 2, 3]), 0);
1949    }
1950
1951    #[test]
1952    fn booth_rotated() {
1953        assert_eq!(booth_least_rotation(&[2, 3, 0, 1]), 2);
1954    }
1955
1956    #[test]
1957    fn booth_two_elements() {
1958        assert_eq!(booth_least_rotation(&[1, 0]), 1);
1959    }
1960
1961    // ── Property-style tests ───────────────────────────────────────────
1962
1963    #[test]
1964    fn rotate_right_then_left_is_identity() {
1965        let ring = [3, 1, 4, 1, 5, 9];
1966        for step in -10..=10 {
1967            assert_eq!(
1968                ring.rotate_right(step).rotate_left(step),
1969                ring.to_vec(),
1970                "failed for step = {step}"
1971            );
1972        }
1973    }
1974
1975    #[test]
1976    fn start_at_then_apply_o_recovers_element() {
1977        let ring = [10, 20, 30, 40, 50];
1978        for i in -10..10 {
1979            let rotated = ring.start_at(i);
1980            assert_eq!(rotated[0], *ring.apply_o(i));
1981        }
1982    }
1983
1984    #[test]
1985    fn all_rotations_have_same_canonical() {
1986        let ring = [5, 3, 1, 4, 2];
1987        let canon = ring.canonical();
1988        for rot in ring.rotations() {
1989            assert_eq!(rot.canonical(), canon);
1990        }
1991    }
1992
1993    #[test]
1994    fn all_rotations_and_reflections_have_same_bracelet() {
1995        let ring = [5, 3, 1, 4, 2];
1996        let brace = ring.bracelet();
1997        for variant in ring.rotations_and_reflections() {
1998            assert_eq!(variant.bracelet(), brace);
1999        }
2000    }
2001
2002    #[test]
2003    fn is_rotation_of_all_rotations() {
2004        let ring = [1, 2, 3, 4];
2005        for rot in ring.rotations() {
2006            assert!(ring.is_rotation_of(&rot));
2007        }
2008    }
2009
2010    #[test]
2011    fn reflect_at_is_involution() {
2012        let ring = [0, 1, 2, 3];
2013        // Applying reflect_at(0) twice should return to the original
2014        let once = ring.reflect_at(0);
2015        let twice = once.reflect_at(0);
2016        assert_eq!(twice, ring.to_vec());
2017    }
2018
2019    #[test]
2020    fn symmetry_count_divides_twice_size_for_nonempty() {
2021        // For a non-empty ring of size n, the number of reflectional
2022        // symmetries divides n.
2023        for ring in [
2024            vec![0, 1, 2],
2025            vec![1, 1, 1],
2026            vec![0, 1, 0, 1],
2027            vec![0, 1, 2, 3],
2028            vec![2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2],
2029        ] {
2030            let n = ring.len();
2031            let s = ring.symmetry();
2032            if s > 0 {
2033                assert_eq!(
2034                    n % s,
2035                    0,
2036                    "symmetry {s} does not divide ring size {n} for {ring:?}"
2037                );
2038            }
2039        }
2040    }
2041}