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    /// Fixed-size circular groups (like [`circular_windows`](RingSeq::circular_windows) with
389    /// `step == size`).
390    ///
391    /// # Panics
392    ///
393    /// Panics if `size` is zero.
394    ///
395    /// # Examples
396    ///
397    /// ```
398    /// use ring_seq::RingSeq;
399    ///
400    /// let groups: Vec<_> = [0, 1, 2, 3, 4].circular_chunks(2).collect();
401    /// assert_eq!(
402    ///     groups,
403    ///     vec![vec![0, 1], vec![2, 3], vec![4, 0], vec![1, 2], vec![3, 4]],
404    /// );
405    /// ```
406    #[must_use]
407    fn circular_chunks(&self, size: usize) -> SlidingO<T>
408    where
409        T: Clone;
410
411    /// Pairs each element with its original (circular) index, starting from
412    /// circular index `from`.
413    ///
414    /// # Examples
415    ///
416    /// ```
417    /// use ring_seq::RingSeq;
418    ///
419    /// assert_eq!(
420    ///     ['a', 'b', 'c'].circular_enumerate(1),
421    ///     vec![('b', 1), ('c', 2), ('a', 0)],
422    /// );
423    /// ```
424    #[must_use]
425    fn circular_enumerate(&self, from: isize) -> Vec<(T, usize)>
426    where
427        T: Clone;
428
429    /// All rotations of this ring.
430    ///
431    /// Yields `n` items for a non-empty ring (starting from the identity
432    /// rotation), or a single empty `Vec` for an empty ring.
433    ///
434    /// # Examples
435    ///
436    /// ```
437    /// use ring_seq::RingSeq;
438    ///
439    /// let rots: Vec<_> = [0, 1, 2].rotations().collect();
440    /// assert_eq!(rots, vec![vec![0, 1, 2], vec![1, 2, 0], vec![2, 0, 1]]);
441    /// ```
442    #[must_use]
443    fn rotations(&self) -> Rotations<'_, T>;
444
445    /// The original ring and its reflection at index 0.
446    ///
447    /// Yields 2 items for a non-empty ring, or a single empty `Vec` for an
448    /// empty ring.
449    ///
450    /// # Examples
451    ///
452    /// ```
453    /// use ring_seq::RingSeq;
454    ///
455    /// let refs: Vec<_> = [0, 1, 2].reflections().collect();
456    /// assert_eq!(refs, vec![vec![0, 1, 2], vec![0, 2, 1]]);
457    /// ```
458    #[must_use]
459    fn reflections(&self) -> Reflections<'_, T>;
460
461    /// The original ring and its reversal.
462    ///
463    /// Yields 2 items for a non-empty ring, or a single empty `Vec` for an
464    /// empty ring.
465    ///
466    /// # Examples
467    ///
468    /// ```
469    /// use ring_seq::RingSeq;
470    ///
471    /// let revs: Vec<_> = [0, 1, 2].reversions().collect();
472    /// assert_eq!(revs, vec![vec![0, 1, 2], vec![2, 1, 0]]);
473    /// ```
474    #[must_use]
475    fn reversions(&self) -> Reversions<'_, T>;
476
477    /// All rotations of the original ring followed by all rotations of its
478    /// reflection.
479    ///
480    /// Yields `2n` items for a non-empty ring, or a single empty `Vec` for an
481    /// empty ring.
482    ///
483    /// # Examples
484    ///
485    /// ```
486    /// use ring_seq::RingSeq;
487    ///
488    /// let all: Vec<_> = [0, 1, 2].rotations_and_reflections().collect();
489    /// assert_eq!(all, vec![
490    ///     vec![0, 1, 2], vec![1, 2, 0], vec![2, 0, 1],
491    ///     vec![0, 2, 1], vec![2, 1, 0], vec![1, 0, 2],
492    /// ]);
493    /// ```
494    #[must_use]
495    fn rotations_and_reflections(&self) -> RotationsAndReflections<'_, T>
496    where
497        T: Clone;
498
499    // ── Comparing ──────────────────────────────────────────────────────
500
501    /// Tests whether this ring is a rotation of `that`.
502    ///
503    /// Two sequences are rotations of each other iff they have the same length
504    /// and one appears as a contiguous substring inside the other repeated
505    /// twice.
506    ///
507    /// # Examples
508    ///
509    /// ```
510    /// use ring_seq::RingSeq;
511    ///
512    /// assert!([0, 1, 2].is_rotation_of(&[1, 2, 0]));
513    /// assert!(![0, 1, 2].is_rotation_of(&[0, 2, 1]));
514    /// ```
515    #[must_use]
516    fn is_rotation_of(&self, that: &[T]) -> bool
517    where
518        T: PartialEq;
519
520    /// Tests whether this ring is the reflection at index 0 of `that` (or
521    /// identical to it).
522    ///
523    /// This checks a single specific reflection axis.  For rotation-insensitive
524    /// comparison, see [`is_rotation_or_reflection_of`](RingSeq::is_rotation_or_reflection_of).
525    ///
526    /// # Examples
527    ///
528    /// ```
529    /// use ring_seq::RingSeq;
530    ///
531    /// assert!([0, 1, 2].is_reflection_of(&[0, 2, 1]));
532    /// ```
533    #[must_use]
534    fn is_reflection_of(&self, that: &[T]) -> bool
535    where
536        T: PartialEq + Clone;
537
538    /// Tests whether this ring is the reversal of `that` (or identical to it).
539    ///
540    /// # Examples
541    ///
542    /// ```
543    /// use ring_seq::RingSeq;
544    ///
545    /// assert!([0, 1, 2].is_reversion_of(&[2, 1, 0]));
546    /// ```
547    #[must_use]
548    fn is_reversion_of(&self, that: &[T]) -> bool
549    where
550        T: PartialEq;
551
552    /// Tests whether this ring is a rotation **or** a reflection of `that`.
553    ///
554    /// # Examples
555    ///
556    /// ```
557    /// use ring_seq::RingSeq;
558    ///
559    /// assert!([0, 1, 2].is_rotation_or_reflection_of(&[2, 0, 1]));
560    /// assert!([0, 1, 2].is_rotation_or_reflection_of(&[0, 2, 1]));
561    /// ```
562    #[must_use]
563    fn is_rotation_or_reflection_of(&self, that: &[T]) -> bool
564    where
565        T: PartialEq + Clone;
566
567    /// Finds the rotation offset that aligns this ring with `that`.
568    ///
569    /// Returns `Some(k)` such that `self.start_at(k) == that`, or `None` if no
570    /// rotation matches (or sizes differ).
571    ///
572    /// # Examples
573    ///
574    /// ```
575    /// use ring_seq::RingSeq;
576    ///
577    /// assert_eq!([0, 1, 2].rotation_offset(&[2, 0, 1]), Some(2));
578    /// assert_eq!([0, 1, 2].rotation_offset(&[0, 2, 1]), None);
579    /// ```
580    #[must_use]
581    fn rotation_offset(&self, that: &[T]) -> Option<usize>
582    where
583        T: PartialEq;
584
585    /// The number of positions at which corresponding elements differ.
586    ///
587    /// # Panics
588    ///
589    /// Panics if the two slices have different lengths.
590    ///
591    /// # Examples
592    ///
593    /// ```
594    /// use ring_seq::RingSeq;
595    ///
596    /// assert_eq!([1, 0, 1, 1].hamming_distance(&[1, 1, 0, 1]), 2);
597    /// ```
598    #[must_use]
599    fn hamming_distance(&self, that: &[T]) -> usize
600    where
601        T: PartialEq;
602
603    /// The minimum Hamming distance over all rotations of this ring.
604    ///
605    /// Returns `0` iff `that` is a rotation of `self`.
606    ///
607    /// # Panics
608    ///
609    /// Panics if the two slices have different lengths.
610    ///
611    /// # Examples
612    ///
613    /// ```
614    /// use ring_seq::RingSeq;
615    ///
616    /// assert_eq!([1, 0, 1, 1].min_rotational_hamming_distance(&[1, 1, 0, 1]), 0);
617    /// ```
618    #[must_use]
619    fn min_rotational_hamming_distance(&self, that: &[T]) -> usize
620    where
621        T: PartialEq + Clone;
622
623    // ── Necklace ───────────────────────────────────────────────────────
624
625    /// The starting index of the lexicographically smallest rotation
626    /// ([Booth's algorithm](https://en.wikipedia.org/wiki/Lexicographically_least_circular_substring),
627    /// *O(n)*).
628    ///
629    /// Returns `0` for empty or single-element sequences.
630    ///
631    /// # Examples
632    ///
633    /// ```
634    /// use ring_seq::RingSeq;
635    ///
636    /// assert_eq!([2, 0, 1].canonical_index(), 1);
637    /// ```
638    #[must_use]
639    fn canonical_index(&self) -> usize
640    where
641        T: Ord;
642
643    /// The lexicographically smallest rotation of this ring (necklace canonical
644    /// form).
645    ///
646    /// Two rings are rotations of each other iff their canonical forms are
647    /// equal — useful for hashing and deduplicating equivalent rings.
648    ///
649    /// # Examples
650    ///
651    /// ```
652    /// use ring_seq::RingSeq;
653    ///
654    /// assert_eq!([2, 0, 1].canonical(), vec![0, 1, 2]);
655    /// ```
656    #[must_use]
657    fn canonical(&self) -> Vec<T>
658    where
659        T: Clone + Ord;
660
661    /// The lexicographically smallest representative under both rotation and
662    /// reflection (bracelet canonical form).
663    ///
664    /// Two rings belong to the same bracelet equivalence class iff their
665    /// bracelet forms are equal — useful when mirror images are considered
666    /// identical.
667    ///
668    /// # Examples
669    ///
670    /// ```
671    /// use ring_seq::RingSeq;
672    ///
673    /// assert_eq!([2, 1, 0].bracelet(), vec![0, 1, 2]);
674    /// assert_eq!([0, 1, 2].bracelet(), vec![0, 1, 2]);
675    /// ```
676    #[must_use]
677    fn bracelet(&self) -> Vec<T>
678    where
679        T: Clone + Ord;
680
681    // ── Symmetry ───────────────────────────────────────────────────────
682
683    /// The order of rotational symmetry: the number of distinct rotations that
684    /// map the ring onto itself.
685    ///
686    /// Returns 1 for sequences with no rotational symmetry (only the identity),
687    /// and `n` when all elements are equal.
688    ///
689    /// # Examples
690    ///
691    /// ```
692    /// use ring_seq::RingSeq;
693    ///
694    /// assert_eq!([0, 1, 2, 0, 1, 2].rotational_symmetry(), 2);
695    /// assert_eq!([0, 1, 2].rotational_symmetry(), 1);
696    /// assert_eq!([5, 5, 5].rotational_symmetry(), 3);
697    /// ```
698    #[must_use]
699    fn rotational_symmetry(&self) -> usize
700    where
701        T: PartialEq;
702
703    /// Indices of elements where a reflectional-symmetry axis passes nearby.
704    ///
705    /// More precisely, the "shift" values for which the ring equals its
706    /// reversal rotated by that shift.
707    ///
708    /// # Examples
709    ///
710    /// ```
711    /// use ring_seq::RingSeq;
712    ///
713    /// assert_eq!(
714    ///     [2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2].symmetry_indices(),
715    ///     vec![0, 3, 6, 9],
716    /// );
717    /// ```
718    #[must_use]
719    fn symmetry_indices(&self) -> Vec<usize>
720    where
721        T: PartialEq;
722
723    /// Axes of reflectional symmetry, expressed as pairs of
724    /// [`AxisLocation`]s where each axis intersects the ring.
725    ///
726    /// # Examples
727    ///
728    /// ```
729    /// use ring_seq::{AxisLocation, RingSeq};
730    ///
731    /// let axes = [0, 1, 0].reflectional_symmetry_axes();
732    /// assert_eq!(axes.len(), 1);
733    /// assert_eq!(axes[0], (AxisLocation::Vertex(1), AxisLocation::Edge(2, 0)));
734    /// ```
735    #[must_use]
736    fn reflectional_symmetry_axes(&self) -> Vec<(AxisLocation, AxisLocation)>
737    where
738        T: PartialEq;
739
740    /// The number of reflectional symmetries (the number of symmetry axes).
741    ///
742    /// # Examples
743    ///
744    /// ```
745    /// use ring_seq::RingSeq;
746    ///
747    /// assert_eq!([2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2].symmetry(), 4);
748    /// assert_eq!([0, 1, 2].symmetry(), 0);
749    /// ```
750    #[must_use]
751    fn symmetry(&self) -> usize
752    where
753        T: PartialEq;
754}
755
756// ============================================================================
757// Implementation
758// ============================================================================
759
760impl<T> RingSeq<T> for [T] {
761    // ── Indexing ────────────────────────────────────────────────────────
762
763    #[inline]
764    #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
765    fn index_from(&self, i: isize) -> usize {
766        i.rem_euclid(self.len() as isize) as usize
767    }
768
769    #[inline]
770    fn apply_o(&self, i: isize) -> &T {
771        &self[self.index_from(i)]
772    }
773
774    // ── Transforming ───────────────────────────────────────────────────
775
776    fn rotate_right(&self, step: isize) -> Vec<T>
777    where
778        T: Clone,
779    {
780        if self.is_empty() {
781            return vec![];
782        }
783        let n = self.len();
784        let j = n - self.index_from(step);
785        let mut v = Vec::with_capacity(n);
786        v.extend_from_slice(&self[j..]);
787        v.extend_from_slice(&self[..j]);
788        v
789    }
790
791    #[inline]
792    fn rotate_left(&self, step: isize) -> Vec<T>
793    where
794        T: Clone,
795    {
796        self.rotate_right(-step)
797    }
798
799    #[inline]
800    fn start_at(&self, i: isize) -> Vec<T>
801    where
802        T: Clone,
803    {
804        self.rotate_left(i)
805    }
806
807    fn reflect_at(&self, i: isize) -> Vec<T>
808    where
809        T: Clone,
810    {
811        let mut v = self.start_at(i + 1);
812        v.reverse();
813        v
814    }
815
816    // ── Slicing ────────────────────────────────────────────────────────
817
818    fn segment_length(&self, pred: impl Fn(&T) -> bool, from: isize) -> usize {
819        if self.is_empty() {
820            return 0;
821        }
822        let n = self.len();
823        let start = self.index_from(from);
824        let mut count = 0;
825        for k in 0..n {
826            if pred(&self[(start + k) % n]) {
827                count += 1;
828            } else {
829                break;
830            }
831        }
832        count
833    }
834
835    fn take_while(&self, pred: impl Fn(&T) -> bool, from: isize) -> Vec<T>
836    where
837        T: Clone,
838    {
839        if self.is_empty() {
840            return vec![];
841        }
842        let n = self.len();
843        let start = self.index_from(from);
844        let mut result = Vec::new();
845        for k in 0..n {
846            let elem = &self[(start + k) % n];
847            if pred(elem) {
848                result.push(elem.clone());
849            } else {
850                break;
851            }
852        }
853        result
854    }
855
856    fn drop_while(&self, pred: impl Fn(&T) -> bool, from: isize) -> Vec<T>
857    where
858        T: Clone,
859    {
860        if self.is_empty() {
861            return vec![];
862        }
863        let n = self.len();
864        let start = self.index_from(from);
865        let prefix_len = self.segment_length(&pred, from);
866        let mut result = Vec::with_capacity(n - prefix_len);
867        for k in prefix_len..n {
868            result.push(self[(start + k) % n].clone());
869        }
870        result
871    }
872
873    fn span(&self, pred: impl Fn(&T) -> bool, from: isize) -> (Vec<T>, Vec<T>)
874    where
875        T: Clone,
876    {
877        if self.is_empty() {
878            return (vec![], vec![]);
879        }
880        let n = self.len();
881        let start = self.index_from(from);
882        let prefix_len = self.segment_length(&pred, from);
883        let mut taken = Vec::with_capacity(prefix_len);
884        for k in 0..prefix_len {
885            taken.push(self[(start + k) % n].clone());
886        }
887        let mut dropped = Vec::with_capacity(n - prefix_len);
888        for k in prefix_len..n {
889            dropped.push(self[(start + k) % n].clone());
890        }
891        (taken, dropped)
892    }
893
894    fn slice_o(&self, from: isize, to: isize) -> Vec<T>
895    where
896        T: Clone,
897    {
898        if self.is_empty() || from >= to {
899            return vec![];
900        }
901        #[allow(clippy::cast_sign_loss)] // `to - from` is positive due to guard above
902        let length = (to - from) as usize;
903        let start = self.index_from(from);
904        self[start..]
905            .iter()
906            .chain(self.iter().cycle())
907            .take(length)
908            .cloned()
909            .collect()
910    }
911
912    fn contains_slice(&self, slice: &[T]) -> bool
913    where
914        T: PartialEq,
915    {
916        if slice.is_empty() {
917            return true;
918        }
919        if self.is_empty() {
920            return false;
921        }
922        let n = self.len();
923        let m = slice.len();
924        (0..n).any(|start| (0..m).all(|j| self[(start + j) % n] == slice[j]))
925    }
926
927    fn index_of_slice(&self, slice: &[T], from: isize) -> Option<usize>
928    where
929        T: PartialEq,
930    {
931        if slice.is_empty() {
932            return if self.is_empty() {
933                Some(0)
934            } else {
935                Some(self.index_from(from))
936            };
937        }
938        if self.is_empty() {
939            return None;
940        }
941        let n = self.len();
942        let m = slice.len();
943        let start = self.index_from(from);
944        for k in 0..n {
945            let i = (start + k) % n;
946            if (0..m).all(|j| self[(i + j) % n] == slice[j]) {
947                return Some(i);
948            }
949        }
950        None
951    }
952
953    fn last_index_of_slice(&self, slice: &[T], end: isize) -> Option<usize>
954    where
955        T: PartialEq,
956    {
957        if slice.is_empty() {
958            return if self.is_empty() {
959                Some(0)
960            } else {
961                Some(self.index_from(end))
962            };
963        }
964        if self.is_empty() {
965            return None;
966        }
967        let n = self.len();
968        let m = slice.len();
969        let end_idx = self.index_from(end);
970        for k in 0..n {
971            let i = (end_idx + n - k) % n;
972            if (0..m).all(|j| self[(i + j) % n] == slice[j]) {
973                return Some(i);
974            }
975        }
976        None
977    }
978
979    // ── Iterating ──────────────────────────────────────────────────────
980
981    fn circular_windows(&self, size: usize, step: usize) -> SlidingO<T>
982    where
983        T: Clone,
984    {
985        assert!(size > 0, "window size must be positive");
986        assert!(step > 0, "step must be positive");
987        if self.is_empty() {
988            return SlidingO {
989                data: vec![],
990                window_size: size,
991                step,
992                pos: 0,
993            };
994        }
995        let total_len = step * (self.len() - 1) + size;
996        #[allow(clippy::cast_possible_wrap)]
997        SlidingO {
998            data: self.slice_o(0, total_len as isize),
999            window_size: size,
1000            step,
1001            pos: 0,
1002        }
1003    }
1004
1005    fn circular_chunks(&self, size: usize) -> SlidingO<T>
1006    where
1007        T: Clone,
1008    {
1009        assert!(size > 0, "group size must be positive");
1010        if self.is_empty() {
1011            return SlidingO {
1012                data: vec![],
1013                window_size: size,
1014                step: size,
1015                pos: 0,
1016            };
1017        }
1018        self.circular_windows(size, size)
1019    }
1020
1021    fn circular_enumerate(&self, from: isize) -> Vec<(T, usize)>
1022    where
1023        T: Clone,
1024    {
1025        let n = self.len();
1026        if n == 0 {
1027            return vec![];
1028        }
1029        let start = self.index_from(from);
1030        (0..n)
1031            .map(|k| {
1032                let idx = (start + k) % n;
1033                (self[idx].clone(), idx)
1034            })
1035            .collect()
1036    }
1037
1038    fn rotations(&self) -> Rotations<'_, T> {
1039        Rotations {
1040            ring: self,
1041            index: 0,
1042            total: if self.is_empty() { 1 } else { self.len() },
1043        }
1044    }
1045
1046    fn reflections(&self) -> Reflections<'_, T> {
1047        Reflections {
1048            ring: self,
1049            state: 0,
1050        }
1051    }
1052
1053    fn reversions(&self) -> Reversions<'_, T> {
1054        Reversions {
1055            ring: self,
1056            state: 0,
1057        }
1058    }
1059
1060    fn rotations_and_reflections(&self) -> RotationsAndReflections<'_, T>
1061    where
1062        T: Clone,
1063    {
1064        let reflected = self.reflect_at(0);
1065        RotationsAndReflections {
1066            ring: self,
1067            reflected,
1068            index: 0,
1069            total: if self.is_empty() { 1 } else { self.len() * 2 },
1070        }
1071    }
1072
1073    // ── Comparing ──────────────────────────────────────────────────────
1074
1075    fn is_rotation_of(&self, that: &[T]) -> bool
1076    where
1077        T: PartialEq,
1078    {
1079        if self.len() != that.len() {
1080            return false;
1081        }
1082        if self.is_empty() {
1083            return true;
1084        }
1085        contains_as_rotation(self, that)
1086    }
1087
1088    fn is_reflection_of(&self, that: &[T]) -> bool
1089    where
1090        T: PartialEq + Clone,
1091    {
1092        if self.len() != that.len() {
1093            return false;
1094        }
1095        self == that || self.reflect_at(0) == that
1096    }
1097
1098    fn is_reversion_of(&self, that: &[T]) -> bool
1099    where
1100        T: PartialEq,
1101    {
1102        if self.len() != that.len() {
1103            return false;
1104        }
1105        self == that || self.iter().rev().eq(that.iter())
1106    }
1107
1108    fn is_rotation_or_reflection_of(&self, that: &[T]) -> bool
1109    where
1110        T: PartialEq + Clone,
1111    {
1112        if self.len() != that.len() {
1113            return false;
1114        }
1115        contains_as_rotation(self, that) || contains_as_rotation(&self.reflect_at(0), that)
1116    }
1117
1118    fn rotation_offset(&self, that: &[T]) -> Option<usize>
1119    where
1120        T: PartialEq,
1121    {
1122        if self.len() != that.len() {
1123            return None;
1124        }
1125        if self.is_empty() {
1126            return Some(0);
1127        }
1128        let n = self.len();
1129        (0..n).find(|&i| (0..n).all(|j| self[(i + j) % n] == that[j]))
1130    }
1131
1132    fn hamming_distance(&self, that: &[T]) -> usize
1133    where
1134        T: PartialEq,
1135    {
1136        assert_eq!(self.len(), that.len(), "sequences must have the same size");
1137        self.iter().zip(that.iter()).filter(|(a, b)| a != b).count()
1138    }
1139
1140    fn min_rotational_hamming_distance(&self, that: &[T]) -> usize
1141    where
1142        T: PartialEq + Clone,
1143    {
1144        assert_eq!(self.len(), that.len(), "sequences must have the same size");
1145        if self.is_empty() {
1146            return 0;
1147        }
1148        let n = self.len();
1149        (0..n)
1150            .map(|rot| (0..n).filter(|&j| self[(rot + j) % n] != that[j]).count())
1151            .min()
1152            .unwrap()
1153    }
1154
1155    // ── Necklace ───────────────────────────────────────────────────────
1156
1157    fn canonical_index(&self) -> usize
1158    where
1159        T: Ord,
1160    {
1161        if self.len() <= 1 {
1162            0
1163        } else {
1164            booth_least_rotation(self)
1165        }
1166    }
1167
1168    fn canonical(&self) -> Vec<T>
1169    where
1170        T: Clone + Ord,
1171    {
1172        #[allow(clippy::cast_possible_wrap)]
1173        self.start_at(self.canonical_index() as isize)
1174    }
1175
1176    fn bracelet(&self) -> Vec<T>
1177    where
1178        T: Clone + Ord,
1179    {
1180        let a = self.canonical();
1181        let b = self.reflect_at(0).canonical();
1182        if a <= b {
1183            a
1184        } else {
1185            b
1186        }
1187    }
1188
1189    // ── Symmetry ───────────────────────────────────────────────────────
1190
1191    fn rotational_symmetry(&self) -> usize
1192    where
1193        T: PartialEq,
1194    {
1195        let n = self.len();
1196        if n < 2 {
1197            return 1;
1198        }
1199        let smallest_period = (1..=n)
1200            .find(|&shift| n % shift == 0 && (0..n - shift).all(|i| self[i] == self[i + shift]));
1201        n / smallest_period.unwrap_or(n)
1202    }
1203
1204    fn symmetry_indices(&self) -> Vec<usize>
1205    where
1206        T: PartialEq,
1207    {
1208        let n = self.len();
1209        if n == 0 {
1210            return vec![];
1211        }
1212        let reversed: Vec<&T> = self.iter().rev().collect();
1213        (0..n)
1214            .filter(|&shift| (0..n).all(|i| self[i] == *reversed[(i + shift) % n]))
1215            .collect()
1216    }
1217
1218    fn reflectional_symmetry_axes(&self) -> Vec<(AxisLocation, AxisLocation)>
1219    where
1220        T: PartialEq,
1221    {
1222        let n = self.len();
1223        if n == 0 {
1224            return vec![];
1225        }
1226
1227        #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
1228        self.symmetry_indices()
1229            .into_iter()
1230            .map(|shift| {
1231                let raw_k = (n as isize - 1 - shift as isize) % n as isize;
1232                let k = if raw_k < 0 {
1233                    (raw_k + n as isize) as usize
1234                } else {
1235                    raw_k as usize
1236                };
1237
1238                if n % 2 != 0 {
1239                    // Odd n: one vertex fixed point, opposite edge
1240                    let v = (k * (n + 1) / 2) % n;
1241                    let opp = (v + n / 2) % n;
1242                    (AxisLocation::Vertex(v), AxisLocation::edge(opp, n))
1243                } else if k % 2 == 0 {
1244                    // Even n, even k: two vertex fixed points
1245                    let v1 = k / 2;
1246                    let v2 = (v1 + n / 2) % n;
1247                    (AxisLocation::Vertex(v1), AxisLocation::Vertex(v2))
1248                } else {
1249                    // Even n, odd k: two edge midpoints
1250                    let e1 = (k - 1) / 2;
1251                    let e2 = (e1 + n / 2) % n;
1252                    (AxisLocation::edge(e1, n), AxisLocation::edge(e2, n))
1253                }
1254            })
1255            .collect()
1256    }
1257
1258    fn symmetry(&self) -> usize
1259    where
1260        T: PartialEq,
1261    {
1262        self.symmetry_indices().len()
1263    }
1264}
1265
1266// ============================================================================
1267// Private helpers
1268// ============================================================================
1269
1270/// Tests whether `that` appears as a rotation of `ring`, using the
1271/// "concatenate and search" technique: any rotation of `ring` is a contiguous
1272/// substring of `ring ++ ring[..n-1]`.
1273fn contains_as_rotation<T: PartialEq>(ring: &[T], that: &[T]) -> bool {
1274    if ring.is_empty() {
1275        return true;
1276    }
1277    let n = ring.len();
1278    // Check if `that` appears as a contiguous substring in ring++ring[..n-1]
1279    // without allocating, by using circular indexing.
1280    let doubled_len = 2 * n - 1;
1281    (0..n).any(|start| {
1282        (0..n).all(|j| {
1283            let idx = (start + j) % doubled_len;
1284            let elem = if idx < n { &ring[idx] } else { &ring[idx - n] };
1285            *elem == that[j]
1286        })
1287    })
1288}
1289
1290/// Booth's O(n) algorithm for finding the starting index of the
1291/// lexicographically smallest rotation.
1292#[allow(
1293    clippy::cast_sign_loss,
1294    clippy::cast_possible_wrap,
1295    clippy::many_single_char_names
1296)]
1297fn booth_least_rotation<T: Ord>(s: &[T]) -> usize {
1298    let n = s.len();
1299    let len = 2 * n;
1300    let mut f: Vec<isize> = vec![-1; len];
1301    let mut k: usize = 0;
1302    let at = |idx: usize| &s[idx % n];
1303
1304    for j in 1..len {
1305        let sj = at(j);
1306        let mut i = f[j - k - 1];
1307        while i != -1 && at(k + i as usize + 1) != sj {
1308            if sj < at(k + i as usize + 1) {
1309                k = j - i as usize - 1;
1310            }
1311            i = f[i as usize];
1312        }
1313        if i == -1 && at(k) != sj {
1314            if sj < at(k) {
1315                k = j;
1316            }
1317            f[j - k] = -1;
1318        } else {
1319            f[j - k] = i + 1;
1320        }
1321    }
1322    k
1323}
1324
1325// ============================================================================
1326// Tests
1327// ============================================================================
1328
1329#[cfg(test)]
1330mod tests {
1331    use super::*;
1332
1333    // ── Indexing ────────────────────────────────────────────────────────
1334
1335    #[test]
1336    fn index_from_positive() {
1337        assert_eq!([10, 20, 30].index_from(0), 0);
1338        assert_eq!([10, 20, 30].index_from(2), 2);
1339        assert_eq!([10, 20, 30].index_from(3), 0);
1340        assert_eq!([10, 20, 30].index_from(7), 1);
1341    }
1342
1343    #[test]
1344    fn index_from_negative() {
1345        assert_eq!([10, 20, 30].index_from(-1), 2);
1346        assert_eq!([10, 20, 30].index_from(-3), 0);
1347        assert_eq!([10, 20, 30].index_from(-4), 2);
1348    }
1349
1350    #[test]
1351    #[should_panic]
1352    fn index_from_empty_panics() {
1353        let empty: &[i32] = &[];
1354        let _ = empty.index_from(0);
1355    }
1356
1357    #[test]
1358    fn apply_o_wraps() {
1359        assert_eq!(*[10, 20, 30].apply_o(0), 10);
1360        assert_eq!(*[10, 20, 30].apply_o(3), 10);
1361        assert_eq!(*[10, 20, 30].apply_o(-1), 30);
1362    }
1363
1364    #[test]
1365    #[should_panic]
1366    fn apply_o_empty_panics() {
1367        let empty: &[i32] = &[];
1368        let _ = empty.apply_o(0);
1369    }
1370
1371    #[test]
1372    fn apply_o_single_element() {
1373        assert_eq!(*[42].apply_o(0), 42);
1374        assert_eq!(*[42].apply_o(100), 42);
1375        assert_eq!(*[42].apply_o(-99), 42);
1376    }
1377
1378    // ── Transforming ───────────────────────────────────────────────────
1379
1380    #[test]
1381    fn rotate_right_basic() {
1382        assert_eq!([0, 1, 2].rotate_right(1), vec![2, 0, 1]);
1383        assert_eq!([0, 1, 2].rotate_right(2), vec![1, 2, 0]);
1384        assert_eq!([0, 1, 2].rotate_right(3), vec![0, 1, 2]);
1385    }
1386
1387    #[test]
1388    fn rotate_right_negative() {
1389        assert_eq!([0, 1, 2].rotate_right(-1), vec![1, 2, 0]);
1390    }
1391
1392    #[test]
1393    fn rotate_right_empty() {
1394        let empty: &[i32] = &[];
1395        assert_eq!(empty.rotate_right(5), Vec::<i32>::new());
1396    }
1397
1398    #[test]
1399    fn rotate_left_basic() {
1400        assert_eq!([0, 1, 2].rotate_left(1), vec![1, 2, 0]);
1401    }
1402
1403    #[test]
1404    fn start_at_basic() {
1405        assert_eq!([0, 1, 2].start_at(1), vec![1, 2, 0]);
1406        assert_eq!([0, 1, 2].start_at(-1), vec![2, 0, 1]);
1407    }
1408
1409    #[test]
1410    fn reflect_at_basic() {
1411        assert_eq!([0, 1, 2].reflect_at(0), vec![0, 2, 1]);
1412        assert_eq!([0, 1, 2].reflect_at(1), vec![1, 0, 2]);
1413    }
1414
1415    #[test]
1416    fn reflect_at_empty() {
1417        let empty: &[i32] = &[];
1418        assert_eq!(empty.reflect_at(0), Vec::<i32>::new());
1419    }
1420
1421    #[test]
1422    fn reflect_at_single() {
1423        assert_eq!([42].reflect_at(0), vec![42]);
1424    }
1425
1426    // ── Slicing ────────────────────────────────────────────────────────
1427
1428    #[test]
1429    fn segment_length_basic() {
1430        assert_eq!([0, 1, 2].segment_length(|x| x % 2 == 0, 2), 2);
1431        assert_eq!([0, 1, 2].segment_length(|_| true, 0), 3);
1432        assert_eq!([0, 1, 2].segment_length(|_| false, 0), 0);
1433    }
1434
1435    #[test]
1436    fn segment_length_empty() {
1437        let empty: &[i32] = &[];
1438        assert_eq!(empty.segment_length(|_| true, 0), 0);
1439    }
1440
1441    #[test]
1442    fn take_while_basic() {
1443        assert_eq!([0, 1, 2, 3, 4].take_while(|&x| x < 3, 1), vec![1, 2]);
1444    }
1445
1446    #[test]
1447    fn drop_while_basic() {
1448        assert_eq!([0, 1, 2, 3, 4].drop_while(|&x| x < 3, 1), vec![3, 4, 0]);
1449    }
1450
1451    #[test]
1452    fn span_basic() {
1453        let (a, b) = [0, 1, 2, 3, 4].span(|&x| x < 3, 1);
1454        assert_eq!(a, vec![1, 2]);
1455        assert_eq!(b, vec![3, 4, 0]);
1456    }
1457
1458    #[test]
1459    fn span_all_pass() {
1460        let (a, b) = [1, 2, 3].span(|_| true, 0);
1461        assert_eq!(a, vec![1, 2, 3]);
1462        assert!(b.is_empty());
1463    }
1464
1465    #[test]
1466    fn span_none_pass() {
1467        let (a, b) = [1, 2, 3].span(|_| false, 0);
1468        assert!(a.is_empty());
1469        assert_eq!(b, vec![1, 2, 3]);
1470    }
1471
1472    #[test]
1473    fn slice_o_basic() {
1474        assert_eq!([0, 1, 2].slice_o(1, 3), vec![1, 2]);
1475    }
1476
1477    #[test]
1478    fn slice_o_wrapping() {
1479        assert_eq!([0, 1, 2].slice_o(-1, 4), vec![2, 0, 1, 2, 0]);
1480    }
1481
1482    #[test]
1483    fn slice_o_empty_result() {
1484        assert_eq!([0, 1, 2].slice_o(3, 3), Vec::<i32>::new());
1485        assert_eq!([0, 1, 2].slice_o(5, 2), Vec::<i32>::new());
1486    }
1487
1488    #[test]
1489    fn slice_o_empty_ring() {
1490        let empty: &[i32] = &[];
1491        assert_eq!(empty.slice_o(0, 5), Vec::<i32>::new());
1492    }
1493
1494    #[test]
1495    fn contains_slice_basic() {
1496        assert!([0, 1, 2].contains_slice(&[2, 0]));
1497        assert!([0, 1, 2].contains_slice(&[2, 0, 1, 2, 0]));
1498        assert!(![0, 1, 2].contains_slice(&[1, 0]));
1499    }
1500
1501    #[test]
1502    fn contains_slice_empty_slice() {
1503        assert!([0, 1, 2].contains_slice(&[]));
1504    }
1505
1506    #[test]
1507    fn contains_slice_empty_ring() {
1508        let empty: &[i32] = &[];
1509        assert!(!empty.contains_slice(&[1]));
1510        assert!(empty.contains_slice(&[]));
1511    }
1512
1513    #[test]
1514    fn index_of_slice_basic() {
1515        assert_eq!([0, 1, 2].index_of_slice(&[2, 0, 1, 2, 0], 0), Some(2));
1516        assert_eq!([0, 1, 2].index_of_slice(&[0, 1], 0), Some(0));
1517        assert_eq!([0, 1, 2].index_of_slice(&[9], 0), None);
1518    }
1519
1520    #[test]
1521    fn index_of_slice_with_from() {
1522        assert_eq!([0, 1, 2, 0, 1, 2].index_of_slice(&[0, 1], 1), Some(3));
1523    }
1524
1525    #[test]
1526    fn last_index_of_slice_basic() {
1527        assert_eq!([0, 1, 2, 0, 1, 2].last_index_of_slice(&[2, 0], -1), Some(5));
1528    }
1529
1530    // ── Iterating ──────────────────────────────────────────────────────
1531
1532    #[test]
1533    fn circular_windows_basic() {
1534        let windows: Vec<_> = [0, 1, 2].circular_windows(2, 1).collect();
1535        assert_eq!(windows, vec![vec![0, 1], vec![1, 2], vec![2, 0]]);
1536    }
1537
1538    #[test]
1539    fn circular_windows_empty() {
1540        let windows: Vec<Vec<i32>> = ([] as [i32; 0]).circular_windows(2, 1).collect();
1541        assert!(windows.is_empty());
1542    }
1543
1544    #[test]
1545    fn circular_windows_exact_size() {
1546        let iter = [0, 1, 2].circular_windows(2, 1);
1547        assert_eq!(iter.len(), 3);
1548    }
1549
1550    #[test]
1551    #[should_panic]
1552    fn circular_windows_zero_size_panics() {
1553        let _ = [0, 1, 2].circular_windows(0, 1);
1554    }
1555
1556    #[test]
1557    #[should_panic]
1558    fn circular_windows_zero_step_panics() {
1559        let _ = [0, 1, 2].circular_windows(1, 0);
1560    }
1561
1562    #[test]
1563    fn circular_chunks_basic() {
1564        let groups: Vec<_> = [0, 1, 2, 3, 4].circular_chunks(2).collect();
1565        assert_eq!(
1566            groups,
1567            vec![vec![0, 1], vec![2, 3], vec![4, 0], vec![1, 2], vec![3, 4]]
1568        );
1569    }
1570
1571    #[test]
1572    fn circular_chunks_evenly_divisible() {
1573        let groups: Vec<_> = [0, 1, 2, 3].circular_chunks(2).collect();
1574        assert_eq!(groups, vec![vec![0, 1], vec![2, 3], vec![0, 1], vec![2, 3]]);
1575    }
1576
1577    #[test]
1578    fn circular_enumerate_basic() {
1579        assert_eq!(
1580            ['a', 'b', 'c'].circular_enumerate(1),
1581            vec![('b', 1), ('c', 2), ('a', 0)]
1582        );
1583    }
1584
1585    #[test]
1586    fn circular_enumerate_empty() {
1587        let empty: &[i32] = &[];
1588        assert!(empty.circular_enumerate(0).is_empty());
1589    }
1590
1591    #[test]
1592    fn rotations_basic() {
1593        let rots: Vec<_> = [0, 1, 2].rotations().collect();
1594        assert_eq!(rots, vec![vec![0, 1, 2], vec![1, 2, 0], vec![2, 0, 1]]);
1595    }
1596
1597    #[test]
1598    fn rotations_empty() {
1599        let empty: &[i32] = &[];
1600        let rots: Vec<_> = empty.rotations().collect();
1601        assert_eq!(rots, vec![Vec::<i32>::new()]);
1602    }
1603
1604    #[test]
1605    fn rotations_exact_size() {
1606        assert_eq!([0, 1, 2].rotations().len(), 3);
1607        let empty: &[i32] = &[];
1608        assert_eq!(empty.rotations().len(), 1);
1609    }
1610
1611    #[test]
1612    fn reflections_basic() {
1613        let refs: Vec<_> = [0, 1, 2].reflections().collect();
1614        assert_eq!(refs, vec![vec![0, 1, 2], vec![0, 2, 1]]);
1615    }
1616
1617    #[test]
1618    fn reflections_empty() {
1619        let empty: &[i32] = &[];
1620        let refs: Vec<_> = empty.reflections().collect();
1621        assert_eq!(refs, vec![Vec::<i32>::new()]);
1622    }
1623
1624    #[test]
1625    fn reversions_basic() {
1626        let revs: Vec<_> = [0, 1, 2].reversions().collect();
1627        assert_eq!(revs, vec![vec![0, 1, 2], vec![2, 1, 0]]);
1628    }
1629
1630    #[test]
1631    fn rotations_and_reflections_basic() {
1632        let all: Vec<_> = [0, 1, 2].rotations_and_reflections().collect();
1633        assert_eq!(
1634            all,
1635            vec![
1636                vec![0, 1, 2],
1637                vec![1, 2, 0],
1638                vec![2, 0, 1],
1639                vec![0, 2, 1],
1640                vec![2, 1, 0],
1641                vec![1, 0, 2],
1642            ]
1643        );
1644    }
1645
1646    #[test]
1647    fn rotations_and_reflections_empty() {
1648        let empty: &[i32] = &[];
1649        let all: Vec<_> = empty.rotations_and_reflections().collect();
1650        assert_eq!(all, vec![Vec::<i32>::new()]);
1651    }
1652
1653    #[test]
1654    fn rotations_and_reflections_exact_size() {
1655        assert_eq!([0, 1, 2].rotations_and_reflections().len(), 6);
1656    }
1657
1658    // ── Comparing ──────────────────────────────────────────────────────
1659
1660    #[test]
1661    fn is_rotation_of_true() {
1662        assert!([0, 1, 2].is_rotation_of(&[1, 2, 0]));
1663        assert!([0, 1, 2].is_rotation_of(&[0, 1, 2]));
1664    }
1665
1666    #[test]
1667    fn is_rotation_of_false() {
1668        assert!(![0, 1, 2].is_rotation_of(&[0, 2, 1]));
1669        assert!(![0, 1, 2].is_rotation_of(&[0, 1]));
1670    }
1671
1672    #[test]
1673    fn is_rotation_of_empty() {
1674        let empty: &[i32] = &[];
1675        assert!(empty.is_rotation_of(&[]));
1676    }
1677
1678    #[test]
1679    fn is_reflection_of_true() {
1680        assert!([0, 1, 2].is_reflection_of(&[0, 2, 1]));
1681        assert!([0, 1, 2].is_reflection_of(&[0, 1, 2])); // identity
1682    }
1683
1684    #[test]
1685    fn is_reflection_of_false() {
1686        assert!(![0, 1, 2].is_reflection_of(&[1, 0, 2]));
1687    }
1688
1689    #[test]
1690    fn is_reversion_of_true() {
1691        assert!([0, 1, 2].is_reversion_of(&[2, 1, 0]));
1692        assert!([0, 1, 2].is_reversion_of(&[0, 1, 2])); // identity
1693    }
1694
1695    #[test]
1696    fn is_reversion_of_false() {
1697        assert!(![0, 1, 2].is_reversion_of(&[0, 2, 1]));
1698    }
1699
1700    #[test]
1701    fn is_rotation_or_reflection_of_true() {
1702        assert!([0, 1, 2].is_rotation_or_reflection_of(&[2, 0, 1])); // rotation
1703        assert!([0, 1, 2].is_rotation_or_reflection_of(&[0, 2, 1])); // reflection
1704        assert!([0, 1, 2].is_rotation_or_reflection_of(&[1, 0, 2])); // rotated reflection
1705    }
1706
1707    #[test]
1708    fn align_to_found() {
1709        assert_eq!([0, 1, 2].rotation_offset(&[2, 0, 1]), Some(2));
1710        assert_eq!([0, 1, 2].rotation_offset(&[0, 1, 2]), Some(0));
1711    }
1712
1713    #[test]
1714    fn align_to_not_found() {
1715        assert_eq!([0, 1, 2].rotation_offset(&[0, 2, 1]), None);
1716    }
1717
1718    #[test]
1719    fn align_to_different_sizes() {
1720        assert_eq!([0, 1, 2].rotation_offset(&[0, 1]), None);
1721    }
1722
1723    #[test]
1724    fn align_to_empty() {
1725        let empty: &[i32] = &[];
1726        assert_eq!(empty.rotation_offset(&[]), Some(0));
1727    }
1728
1729    #[test]
1730    fn hamming_distance_basic() {
1731        assert_eq!([1, 0, 1, 1].hamming_distance(&[1, 1, 0, 1]), 2);
1732        assert_eq!([1, 2, 3].hamming_distance(&[1, 2, 3]), 0);
1733    }
1734
1735    #[test]
1736    #[should_panic(expected = "sequences must have the same size")]
1737    fn hamming_distance_different_sizes_panics() {
1738        let _ = [1, 2, 3].hamming_distance(&[1, 2]);
1739    }
1740
1741    #[test]
1742    fn min_rotational_hamming_distance_basic() {
1743        assert_eq!(
1744            [1, 0, 1, 1].min_rotational_hamming_distance(&[1, 1, 0, 1]),
1745            0
1746        );
1747    }
1748
1749    #[test]
1750    fn min_rotational_hamming_distance_nonzero() {
1751        assert_eq!(
1752            [0, 0, 0, 1].min_rotational_hamming_distance(&[1, 1, 0, 0]),
1753            1
1754        );
1755    }
1756
1757    #[test]
1758    fn min_rotational_hamming_distance_empty() {
1759        let empty: &[i32] = &[];
1760        assert_eq!(empty.min_rotational_hamming_distance(&[]), 0);
1761    }
1762
1763    // ── Necklace ───────────────────────────────────────────────────────
1764
1765    #[test]
1766    fn canonical_index_basic() {
1767        assert_eq!([2, 0, 1].canonical_index(), 1);
1768        assert_eq!([0, 1, 2].canonical_index(), 0);
1769    }
1770
1771    #[test]
1772    fn canonical_index_empty_and_single() {
1773        let empty: &[i32] = &[];
1774        assert_eq!(empty.canonical_index(), 0);
1775        assert_eq!([42].canonical_index(), 0);
1776    }
1777
1778    #[test]
1779    fn canonical_basic() {
1780        assert_eq!([2, 0, 1].canonical(), vec![0, 1, 2]);
1781        assert_eq!([0, 1, 2].canonical(), vec![0, 1, 2]);
1782    }
1783
1784    #[test]
1785    fn canonical_all_equal() {
1786        assert_eq!([5, 5, 5].canonical(), vec![5, 5, 5]);
1787    }
1788
1789    #[test]
1790    fn canonical_rotations_are_equal() {
1791        let ring = [3, 1, 4, 1, 5];
1792        let canon = ring.canonical();
1793        for rot in ring.rotations() {
1794            assert_eq!(rot.canonical(), canon);
1795        }
1796    }
1797
1798    #[test]
1799    fn bracelet_basic() {
1800        assert_eq!([2, 1, 0].bracelet(), vec![0, 1, 2]);
1801        assert_eq!([0, 1, 2].bracelet(), vec![0, 1, 2]);
1802    }
1803
1804    #[test]
1805    fn bracelet_reflection_equivalence() {
1806        let a = [0, 1, 2];
1807        let b = [0, 2, 1];
1808        assert_eq!(a.bracelet(), b.bracelet());
1809    }
1810
1811    // ── Symmetry ───────────────────────────────────────────────────────
1812
1813    #[test]
1814    fn rotational_symmetry_basic() {
1815        assert_eq!([0, 1, 2, 0, 1, 2].rotational_symmetry(), 2);
1816        assert_eq!([0, 1, 2].rotational_symmetry(), 1);
1817        assert_eq!([5, 5, 5].rotational_symmetry(), 3);
1818    }
1819
1820    #[test]
1821    fn rotational_symmetry_small() {
1822        let empty: &[i32] = &[];
1823        assert_eq!(empty.rotational_symmetry(), 1);
1824        assert_eq!([42].rotational_symmetry(), 1);
1825        assert_eq!([1, 1].rotational_symmetry(), 2);
1826        assert_eq!([1, 2].rotational_symmetry(), 1);
1827    }
1828
1829    #[test]
1830    fn symmetry_indices_basic() {
1831        assert_eq!(
1832            [2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2].symmetry_indices(),
1833            vec![0, 3, 6, 9]
1834        );
1835    }
1836
1837    #[test]
1838    fn symmetry_indices_none() {
1839        assert!([0, 1, 2].symmetry_indices().is_empty());
1840    }
1841
1842    #[test]
1843    fn symmetry_indices_palindrome() {
1844        assert_eq!([0, 1, 0].symmetry_indices(), vec![0]);
1845    }
1846
1847    #[test]
1848    fn symmetry_basic() {
1849        assert_eq!([2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2].symmetry(), 4);
1850        assert_eq!([0, 1, 2].symmetry(), 0);
1851    }
1852
1853    #[test]
1854    fn symmetry_empty() {
1855        let empty: &[i32] = &[];
1856        assert_eq!(empty.symmetry(), 0);
1857    }
1858
1859    #[test]
1860    fn reflectional_symmetry_axes_odd() {
1861        let axes = [0, 1, 0].reflectional_symmetry_axes();
1862        assert_eq!(axes.len(), 1);
1863        assert_eq!(axes[0], (AxisLocation::Vertex(1), AxisLocation::Edge(2, 0)));
1864    }
1865
1866    #[test]
1867    fn reflectional_symmetry_axes_even_vertices() {
1868        // [0, 1, 1, 0] is symmetric — axis through vertices 0 and 2
1869        let axes = [0, 1, 1, 0].reflectional_symmetry_axes();
1870        assert!(!axes.is_empty());
1871    }
1872
1873    #[test]
1874    fn reflectional_symmetry_axes_empty() {
1875        let empty: &[i32] = &[];
1876        assert!(empty.reflectional_symmetry_axes().is_empty());
1877    }
1878
1879    // ── AxisLocation ───────────────────────────────────────────────────
1880
1881    #[test]
1882    fn axis_location_edge_constructor() {
1883        assert_eq!(AxisLocation::edge(2, 3), AxisLocation::Edge(2, 0));
1884        assert_eq!(AxisLocation::edge(0, 4), AxisLocation::Edge(0, 1));
1885        assert_eq!(AxisLocation::edge(3, 4), AxisLocation::Edge(3, 0));
1886    }
1887
1888    #[test]
1889    #[should_panic(expected = "ring size must be positive")]
1890    fn axis_location_edge_zero_size_panics() {
1891        let _ = AxisLocation::edge(0, 0);
1892    }
1893
1894    // ── Vec and array interop ──────────────────────────────────────────
1895
1896    #[test]
1897    fn works_on_vec() {
1898        let v = vec![0, 1, 2];
1899        assert_eq!(v.rotate_right(1), vec![2, 0, 1]);
1900        assert!(v.is_rotation_of(&[1, 2, 0]));
1901    }
1902
1903    #[test]
1904    fn works_on_boxed_slice() {
1905        let b: Box<[i32]> = vec![0, 1, 2].into_boxed_slice();
1906        assert_eq!(b.rotate_right(1), vec![2, 0, 1]);
1907    }
1908
1909    #[test]
1910    fn chained_operations() {
1911        // rotate_right returns Vec<T>, which derefs to [T], so we can chain
1912        let result = [0, 1, 2].rotate_right(1).reflect_at(0);
1913        // rotate_right(1) -> [2, 0, 1]
1914        // reflect_at(0) of [2, 0, 1] -> start_at(1).reverse() = [0, 1, 2].reverse() = [2, 1, 0]
1915        assert_eq!(result, vec![2, 1, 0]);
1916    }
1917
1918    // ── Booth's algorithm ──────────────────────────────────────────────
1919
1920    #[test]
1921    fn booth_all_same() {
1922        assert_eq!(booth_least_rotation(&[5, 5, 5, 5]), 0);
1923    }
1924
1925    #[test]
1926    fn booth_sorted() {
1927        assert_eq!(booth_least_rotation(&[0, 1, 2, 3]), 0);
1928    }
1929
1930    #[test]
1931    fn booth_rotated() {
1932        assert_eq!(booth_least_rotation(&[2, 3, 0, 1]), 2);
1933    }
1934
1935    #[test]
1936    fn booth_two_elements() {
1937        assert_eq!(booth_least_rotation(&[1, 0]), 1);
1938    }
1939
1940    // ── Property-style tests ───────────────────────────────────────────
1941
1942    #[test]
1943    fn rotate_right_then_left_is_identity() {
1944        let ring = [3, 1, 4, 1, 5, 9];
1945        for step in -10..=10 {
1946            assert_eq!(
1947                ring.rotate_right(step).rotate_left(step),
1948                ring.to_vec(),
1949                "failed for step = {step}"
1950            );
1951        }
1952    }
1953
1954    #[test]
1955    fn start_at_then_apply_o_recovers_element() {
1956        let ring = [10, 20, 30, 40, 50];
1957        for i in -10..10 {
1958            let rotated = ring.start_at(i);
1959            assert_eq!(rotated[0], *ring.apply_o(i));
1960        }
1961    }
1962
1963    #[test]
1964    fn all_rotations_have_same_canonical() {
1965        let ring = [5, 3, 1, 4, 2];
1966        let canon = ring.canonical();
1967        for rot in ring.rotations() {
1968            assert_eq!(rot.canonical(), canon);
1969        }
1970    }
1971
1972    #[test]
1973    fn all_rotations_and_reflections_have_same_bracelet() {
1974        let ring = [5, 3, 1, 4, 2];
1975        let brace = ring.bracelet();
1976        for variant in ring.rotations_and_reflections() {
1977            assert_eq!(variant.bracelet(), brace);
1978        }
1979    }
1980
1981    #[test]
1982    fn is_rotation_of_all_rotations() {
1983        let ring = [1, 2, 3, 4];
1984        for rot in ring.rotations() {
1985            assert!(ring.is_rotation_of(&rot));
1986        }
1987    }
1988
1989    #[test]
1990    fn reflect_at_is_involution() {
1991        let ring = [0, 1, 2, 3];
1992        // Applying reflect_at(0) twice should return to the original
1993        let once = ring.reflect_at(0);
1994        let twice = once.reflect_at(0);
1995        assert_eq!(twice, ring.to_vec());
1996    }
1997
1998    #[test]
1999    fn symmetry_count_divides_twice_size_for_nonempty() {
2000        // For a non-empty ring of size n, the number of reflectional
2001        // symmetries divides n.
2002        for ring in [
2003            vec![0, 1, 2],
2004            vec![1, 1, 1],
2005            vec![0, 1, 0, 1],
2006            vec![0, 1, 2, 3],
2007            vec![2, 1, 2, 2, 1, 2, 2, 1, 2, 2, 1, 2],
2008        ] {
2009            let n = ring.len();
2010            let s = ring.symmetry();
2011            if s > 0 {
2012                assert_eq!(
2013                    n % s,
2014                    0,
2015                    "symmetry {s} does not divide ring size {n} for {ring:?}"
2016                );
2017            }
2018        }
2019    }
2020}