Skip to main content

ring_seq/
circular.rs

1//! The [`Circular`] wrapper and its element iterator.
2//!
3//! Reach the wrapper through [`AsCircular::circular`] on any slice (or
4//! anything that derefs to one — [`Vec<T>`](alloc::vec::Vec), arrays,
5//! [`Box<[T]>`](alloc::boxed::Box)). The wrapper is the single home for
6//! every circular operation; all transforms it offers are lazy and
7//! allocation-free.
8
9use core::iter::FusedIterator;
10
11#[cfg(feature = "alloc")]
12use alloc::vec::Vec;
13
14#[cfg(feature = "alloc")]
15use crate::AxisLocation;
16
17/// A borrowed view of a slice as a circular sequence.
18///
19/// Carries a `(slice, offset, reflected)` triple. Every operation routes
20/// through a single index-mapping function, so the algebra stays consistent
21/// however the view was constructed.
22///
23/// The type is `Copy` and trivially cheap to pass by value.
24///
25/// # Examples
26///
27/// ```
28/// use ring_seq::AsCircular;
29///
30/// let ring = [10, 20, 30];
31/// let c = ring.circular();
32/// assert_eq!(c.len(), 3);
33/// assert_eq!(*c.apply(4), 20); // wraps
34/// ```
35pub struct Circular<'a, T> {
36    ring: &'a [T],
37    offset: usize,
38    reflected: bool,
39}
40
41// Hand-implemented so they don't pick up a phantom `T: Clone` bound from
42// the derive macro. The wrapper is value-typed and trivially copyable for
43// every `T`.
44impl<T> Clone for Circular<'_, T> {
45    fn clone(&self) -> Self {
46        *self
47    }
48}
49impl<T> Copy for Circular<'_, T> {}
50
51impl<T: core::fmt::Debug> core::fmt::Debug for Circular<'_, T> {
52    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
53        f.debug_struct("Circular")
54            .field("ring", &self.ring)
55            .field("offset", &self.offset)
56            .field("reflected", &self.reflected)
57            .finish()
58    }
59}
60
61impl<'a, T> Circular<'a, T> {
62    /// Constructs a fresh view over `ring` with zero offset and no reflection.
63    #[inline]
64    pub(crate) fn new(ring: &'a [T]) -> Self {
65        Circular {
66            ring,
67            offset: 0,
68            reflected: false,
69        }
70    }
71
72    /// Number of elements in the underlying ring.
73    #[inline]
74    #[must_use]
75    pub fn len(self) -> usize {
76        self.ring.len()
77    }
78
79    /// Returns `true` if the ring has no elements.
80    #[inline]
81    #[must_use]
82    pub fn is_empty(self) -> bool {
83        self.ring.is_empty()
84    }
85
86    /// Single source of truth: map a position within this view to an index
87    /// into the underlying slice.
88    ///
89    /// Caller must ensure `len() > 0`.
90    #[inline]
91    fn map_index(self, pos: usize) -> usize {
92        let n = self.ring.len();
93        debug_assert!(n > 0);
94        let p = pos % n;
95        if self.reflected {
96            (self.offset + n - p) % n
97        } else {
98            (self.offset + p) % n
99        }
100    }
101
102    /// Returns the element at the (possibly negative, possibly out-of-bounds)
103    /// circular index `i`.
104    ///
105    /// # Panics
106    ///
107    /// Panics if the ring is empty.
108    ///
109    /// # Examples
110    ///
111    /// ```
112    /// use ring_seq::AsCircular;
113    ///
114    /// let r = [10, 20, 30].circular();
115    /// assert_eq!(*r.apply(0), 10);
116    /// assert_eq!(*r.apply(3), 10);    // wraps forward
117    /// assert_eq!(*r.apply(-1), 30);   // wraps backward
118    /// ```
119    #[inline]
120    #[must_use]
121    pub fn apply(self, i: isize) -> &'a T {
122        let n = self.ring.len();
123        assert!(n > 0, "cannot index into an empty ring");
124        let pos = i.rem_euclid(n as isize) as usize;
125        &self.ring[self.map_index(pos)]
126    }
127
128    /// Returns the element at the circular index `i`, or `None` if the
129    /// ring is empty.
130    ///
131    /// The non-panicking companion to [`apply`](Self::apply).
132    ///
133    /// # Examples
134    ///
135    /// ```
136    /// use ring_seq::AsCircular;
137    ///
138    /// let r = [10, 20, 30].circular();
139    /// assert_eq!(r.get(4), Some(&20));
140    /// assert_eq!(r.get(-1), Some(&30));
141    ///
142    /// let empty: [i32; 0] = [];
143    /// assert_eq!(empty.circular().get(0), None);
144    /// ```
145    #[inline]
146    #[must_use]
147    pub fn get(self, i: isize) -> Option<&'a T> {
148        if self.ring.is_empty() {
149            None
150        } else {
151            Some(self.apply(i))
152        }
153    }
154
155    /// Returns an iterator yielding the elements of this view in order.
156    ///
157    /// The iterator is `ExactSizeIterator`, `DoubleEndedIterator`, and
158    /// `FusedIterator`.
159    ///
160    /// # Examples
161    ///
162    /// ```
163    /// use ring_seq::AsCircular;
164    ///
165    /// let r = [1, 2, 3].circular();
166    /// let collected: Vec<_> = r.iter().copied().collect();
167    /// assert_eq!(collected, vec![1, 2, 3]);
168    /// ```
169    #[inline]
170    #[must_use]
171    pub fn iter(self) -> CircularIter<'a, T> {
172        CircularIter {
173            view: self,
174            pos: 0,
175            remaining: self.ring.len(),
176        }
177    }
178
179    // -----------------------------------------------------------------------
180    // Reindexed views — return a new Circular, do not allocate
181    // -----------------------------------------------------------------------
182
183    /// Returns a view whose position 0 is the element at circular index `i`.
184    ///
185    /// Equivalent to [`rotate_left(i)`](Circular::rotate_left). All other
186    /// reindexed-view methods are defined in terms of this one.
187    ///
188    /// # Examples
189    ///
190    /// ```
191    /// use ring_seq::AsCircular;
192    ///
193    /// let r = [10, 20, 30, 40].circular();
194    /// let v: Vec<_> = r.start_at(2).iter().copied().collect();
195    /// assert_eq!(v, vec![30, 40, 10, 20]);
196    /// ```
197    #[must_use]
198    pub fn start_at(self, i: isize) -> Circular<'a, T> {
199        let n = self.ring.len();
200        if n == 0 {
201            return self;
202        }
203        let i_pos = i.rem_euclid(n as isize) as usize;
204        let new_offset = if self.reflected {
205            (self.offset + n - i_pos) % n
206        } else {
207            (self.offset + i_pos) % n
208        };
209        Circular {
210            ring: self.ring,
211            offset: new_offset,
212            reflected: self.reflected,
213        }
214    }
215
216    /// Shifts the view left by `step` positions. Position 0 of the result
217    /// is what was at position `step` of `self`.
218    ///
219    /// `step` may be negative (delegates to [`rotate_right`](Self::rotate_right))
220    /// and may exceed `len` (wraps).
221    ///
222    /// # Examples
223    ///
224    /// ```
225    /// use ring_seq::AsCircular;
226    ///
227    /// let r = [1, 2, 3, 4].circular();
228    /// let v: Vec<_> = r.rotate_left(1).iter().copied().collect();
229    /// assert_eq!(v, vec![2, 3, 4, 1]);
230    /// ```
231    #[inline]
232    #[must_use]
233    pub fn rotate_left(self, step: isize) -> Circular<'a, T> {
234        self.start_at(step)
235    }
236
237    /// Shifts the view right by `step` positions. Position 0 of the result
238    /// is what was at position `-step` of `self`.
239    ///
240    /// `step` may be negative (delegates to [`rotate_left`](Self::rotate_left))
241    /// and may exceed `len` (wraps).
242    ///
243    /// # Examples
244    ///
245    /// ```
246    /// use ring_seq::AsCircular;
247    ///
248    /// let r = [1, 2, 3, 4].circular();
249    /// let v: Vec<_> = r.rotate_right(1).iter().copied().collect();
250    /// assert_eq!(v, vec![4, 1, 2, 3]);
251    /// ```
252    #[inline]
253    #[must_use]
254    pub fn rotate_right(self, step: isize) -> Circular<'a, T> {
255        self.start_at(-step)
256    }
257
258    /// Reflects the view around position `i`.
259    ///
260    /// Position 0 of the result is the element at position `i`; subsequent
261    /// positions walk backward from there. Flips the `reflected` bit of the
262    /// view (a second reflection at the same index recovers the original).
263    ///
264    /// # Examples
265    ///
266    /// ```
267    /// use ring_seq::AsCircular;
268    ///
269    /// let r = ['a', 'b', 'c', 'd'].circular();
270    /// let v: Vec<_> = r.reflect_at(0).iter().copied().collect();
271    /// assert_eq!(v, vec!['a', 'd', 'c', 'b']);
272    /// ```
273    #[must_use]
274    pub fn reflect_at(self, i: isize) -> Circular<'a, T> {
275        let n = self.ring.len();
276        if n == 0 {
277            return self;
278        }
279        let i_pos = i.rem_euclid(n as isize) as usize;
280        let new_offset = if self.reflected {
281            (self.offset + n - i_pos) % n
282        } else {
283            (self.offset + i_pos) % n
284        };
285        Circular {
286            ring: self.ring,
287            offset: new_offset,
288            reflected: !self.reflected,
289        }
290    }
291
292    // -----------------------------------------------------------------------
293    // Bounded iteration — return an iterator, do not allocate
294    // -----------------------------------------------------------------------
295
296    /// Returns an iterator over the circular range `[from, to)`.
297    ///
298    /// The range is taken in the view's forward direction starting at the
299    /// element with circular index `from`. Yields `max(to - from, 0)`
300    /// elements, wrapping around the ring as many times as needed; an
301    /// empty range produces an empty iterator.
302    ///
303    /// # Examples
304    ///
305    /// ```
306    /// use ring_seq::AsCircular;
307    ///
308    /// let r = [0, 1, 2, 3, 4].circular();
309    /// let v: Vec<_> = r.slice(2, 7).copied().collect();
310    /// assert_eq!(v, vec![2, 3, 4, 0, 1]);
311    /// ```
312    #[must_use]
313    pub fn slice(self, from: isize, to: isize) -> CircularIter<'a, T> {
314        if self.ring.is_empty() || to <= from {
315            return CircularIter {
316                view: self,
317                pos: 0,
318                remaining: 0,
319            };
320        }
321        // `to > from` holds here, so the mathematical difference fits in
322        // usize even when `to - from` would overflow isize.
323        let count = to.wrapping_sub(from) as usize;
324        let started = self.start_at(from);
325        CircularIter {
326            view: started,
327            pos: 0,
328            remaining: count,
329        }
330    }
331
332    /// Returns an iterator yielding elements from circular index `from`
333    /// while `pred` holds, stopping at the first element that fails (or
334    /// after one full lap).
335    ///
336    /// # Examples
337    ///
338    /// ```
339    /// use ring_seq::AsCircular;
340    ///
341    /// let r = [1, 2, 3, 4, 5].circular();
342    /// let v: Vec<_> = r.take_while(|&x| x < 4, 0).copied().collect();
343    /// assert_eq!(v, vec![1, 2, 3]);
344    /// ```
345    pub fn take_while<P>(self, mut pred: P, from: isize) -> impl Iterator<Item = &'a T>
346    where
347        P: FnMut(&T) -> bool,
348    {
349        self.start_at(from).iter().take_while(move |x| pred(*x))
350    }
351
352    /// Returns an iterator that skips elements from circular index `from`
353    /// while `pred` holds, then yields the rest of the lap (one full
354    /// rotation maximum).
355    ///
356    /// # Examples
357    ///
358    /// ```
359    /// use ring_seq::AsCircular;
360    ///
361    /// let r = [1, 2, 3, 4, 5].circular();
362    /// let v: Vec<_> = r.drop_while(|&x| x < 4, 0).copied().collect();
363    /// assert_eq!(v, vec![4, 5]);
364    /// ```
365    pub fn drop_while<P>(self, mut pred: P, from: isize) -> impl Iterator<Item = &'a T>
366    where
367        P: FnMut(&T) -> bool,
368    {
369        self.start_at(from).iter().skip_while(move |x| pred(*x))
370    }
371
372    /// Returns an iterator yielding `(element, ring_index)` pairs starting
373    /// at circular index `from` and going around once.
374    ///
375    /// Unlike [`Iterator::enumerate`], the second element of each pair is
376    /// the element's index in the underlying ring (already wrapped to
377    /// `[0, len)`), not the iteration count.
378    ///
379    /// # Examples
380    ///
381    /// ```
382    /// use ring_seq::AsCircular;
383    ///
384    /// let r = ['a', 'b', 'c'].circular();
385    /// let v: Vec<_> = r.enumerate(1).map(|(&c, i)| (c, i)).collect();
386    /// assert_eq!(v, vec![('b', 1), ('c', 2), ('a', 0)]);
387    /// ```
388    #[must_use]
389    pub fn enumerate(self, from: isize) -> Enumerate<'a, T> {
390        let started = if self.ring.is_empty() {
391            self
392        } else {
393            self.start_at(from)
394        };
395        Enumerate {
396            view: started,
397            pos: 0,
398            remaining: started.ring.len(),
399        }
400    }
401
402    // -----------------------------------------------------------------------
403    // Iterators of views — yield Circular<'a, T>
404    // -----------------------------------------------------------------------
405
406    /// Returns an iterator yielding each rotation of this view as a fresh
407    /// [`Circular`].
408    ///
409    /// For a non-empty ring of length `n` the iterator yields `n` items;
410    /// for an empty ring it yields one (empty) item. The `reflected` bit
411    /// of `self` is preserved by every yielded rotation.
412    ///
413    /// # Examples
414    ///
415    /// ```
416    /// use ring_seq::AsCircular;
417    ///
418    /// let r = [1, 2, 3].circular();
419    /// let starts: Vec<i32> = r.rotations().map(|v| *v.apply(0)).collect();
420    /// assert_eq!(starts, vec![1, 2, 3]);
421    /// ```
422    #[must_use]
423    pub fn rotations(self) -> Rotations<'a, T> {
424        let total = if self.ring.is_empty() {
425            1
426        } else {
427            self.ring.len()
428        };
429        Rotations {
430            base: self,
431            index: 0,
432            total,
433        }
434    }
435
436    /// Returns an iterator yielding the view and its reflection at
437    /// position 0.
438    ///
439    /// For a non-empty ring the iterator yields two items; for an empty
440    /// ring it yields one. The first item is always `self`.
441    ///
442    /// # Examples
443    ///
444    /// ```
445    /// use ring_seq::AsCircular;
446    ///
447    /// let r = [1, 2, 3, 4].circular();
448    /// let count = r.reflections().count();
449    /// assert_eq!(count, 2);
450    /// ```
451    #[must_use]
452    pub fn reflections(self) -> Reflections<'a, T> {
453        Reflections {
454            base: self,
455            state: 0,
456        }
457    }
458
459    /// Returns an iterator yielding the view and its reverse.
460    ///
461    /// "Reverse" here means walking the ring in the opposite direction
462    /// starting from position `len - 1` — equivalent to
463    /// `reflect_at(-1)`.
464    ///
465    /// For a non-empty ring the iterator yields two items; for an empty
466    /// ring it yields one.
467    ///
468    /// # Examples
469    ///
470    /// ```
471    /// use ring_seq::AsCircular;
472    ///
473    /// let r = [1, 2, 3].circular();
474    /// let last: Vec<_> = r.reversions()
475    ///     .map(|v| v.iter().copied().collect::<Vec<_>>())
476    ///     .collect();
477    /// assert_eq!(last, vec![vec![1, 2, 3], vec![3, 2, 1]]);
478    /// ```
479    #[must_use]
480    pub fn reversions(self) -> Reversions<'a, T> {
481        Reversions {
482            base: self,
483            state: 0,
484        }
485    }
486
487    /// Returns an iterator yielding every rotation of this view followed
488    /// by every rotation of its reflection at position 0.
489    ///
490    /// For a non-empty ring of length `n` the iterator yields `2n` items;
491    /// for an empty ring it yields one.
492    ///
493    /// # Examples
494    ///
495    /// ```
496    /// use ring_seq::AsCircular;
497    ///
498    /// let r = [1, 2, 3].circular();
499    /// assert_eq!(r.rotations_and_reflections().count(), 6);
500    /// ```
501    #[must_use]
502    pub fn rotations_and_reflections(self) -> RotationsAndReflections<'a, T> {
503        let n = self.ring.len();
504        let total = if n == 0 { 1 } else { 2 * n };
505        let reflected = if n == 0 { self } else { self.reflect_at(0) };
506        RotationsAndReflections {
507            base: self,
508            reflected,
509            index: 0,
510            total,
511            n,
512        }
513    }
514
515    /// Returns an iterator yielding every circular window of length `size`
516    /// (step 1) as a [`CircularIter`].
517    ///
518    /// For a non-empty ring of length `n` the iterator yields `n` windows;
519    /// for an empty ring it yields no windows.
520    ///
521    /// # Panics
522    ///
523    /// Panics if `size` is zero.
524    ///
525    /// # Examples
526    ///
527    /// ```
528    /// use ring_seq::AsCircular;
529    ///
530    /// let r = [1, 2, 3, 4].circular();
531    /// let windows: Vec<Vec<i32>> = r
532    ///     .windows(2)
533    ///     .map(|w| w.copied().collect())
534    ///     .collect();
535    /// assert_eq!(windows, vec![vec![1, 2], vec![2, 3], vec![3, 4], vec![4, 1]]);
536    /// ```
537    #[must_use]
538    pub fn windows(self, size: usize) -> Windows<'a, T> {
539        assert!(size > 0, "window size must be positive");
540        let total = if self.ring.is_empty() {
541            0
542        } else {
543            self.ring.len()
544        };
545        Windows {
546            base: self,
547            size,
548            step: 1,
549            index: 0,
550            total,
551        }
552    }
553
554    // -----------------------------------------------------------------------
555    // Indexing
556    // -----------------------------------------------------------------------
557
558    /// Normalizes a circular index to `[0, len)`.
559    ///
560    /// Uses Euclidean remainder so that negative indices wrap correctly.
561    ///
562    /// # Panics
563    ///
564    /// Panics if the ring is empty.
565    ///
566    /// # Examples
567    ///
568    /// ```
569    /// use ring_seq::AsCircular;
570    ///
571    /// let r = [10, 20, 30].circular();
572    /// assert_eq!(r.index_from(0), 0);
573    /// assert_eq!(r.index_from(4), 1);
574    /// assert_eq!(r.index_from(-1), 2);
575    /// ```
576    #[must_use]
577    pub fn index_from(self, i: isize) -> usize {
578        let n = self.ring.len();
579        assert!(n > 0, "cannot normalize against an empty ring");
580        let pos = i.rem_euclid(n as isize) as usize;
581        self.map_index(pos)
582    }
583
584    // -----------------------------------------------------------------------
585    // Comparison — alloc-free
586    // -----------------------------------------------------------------------
587
588    /// Returns `true` if `other` is some rotation of this view.
589    #[must_use]
590    pub fn is_rotation_of(self, other: &[T]) -> bool
591    where
592        T: PartialEq,
593    {
594        if self.len() != other.len() {
595            return false;
596        }
597        if self.ring.is_empty() {
598            return true;
599        }
600        self.rotations().any(|rot| rot.iter().eq(other.iter()))
601    }
602
603    /// Returns `true` if `other` equals this view or its reflection at
604    /// position 0.
605    #[must_use]
606    pub fn is_reflection_of(self, other: &[T]) -> bool
607    where
608        T: PartialEq,
609    {
610        if self.len() != other.len() {
611            return false;
612        }
613        self.iter().eq(other.iter()) || self.reflect_at(0).iter().eq(other.iter())
614    }
615
616    /// Returns `true` if `other` equals this view or its reverse.
617    #[must_use]
618    pub fn is_reversion_of(self, other: &[T]) -> bool
619    where
620        T: PartialEq,
621    {
622        if self.len() != other.len() {
623            return false;
624        }
625        self.iter().eq(other.iter()) || self.reflect_at(-1).iter().eq(other.iter())
626    }
627
628    /// Returns `true` if `other` is any rotation of this view or of its
629    /// reflection at position 0.
630    #[must_use]
631    pub fn is_rotation_or_reflection_of(self, other: &[T]) -> bool
632    where
633        T: PartialEq,
634    {
635        if self.len() != other.len() {
636            return false;
637        }
638        self.rotations_and_reflections()
639            .any(|v| v.iter().eq(other.iter()))
640    }
641
642    /// Returns the starting offset at which `other` matches this view as a
643    /// rotation, or `None` if no such offset exists.
644    #[must_use]
645    pub fn rotation_offset(self, other: &[T]) -> Option<usize>
646    where
647        T: PartialEq,
648    {
649        if self.len() != other.len() {
650            return None;
651        }
652        if self.ring.is_empty() {
653            return Some(0);
654        }
655        let n = self.ring.len();
656        (0..n).find(|&i| self.start_at(i as isize).iter().eq(other.iter()))
657    }
658
659    /// Counts positions where this view and `other` differ.
660    ///
661    /// # Panics
662    ///
663    /// Panics if the lengths differ.
664    #[must_use]
665    pub fn hamming_distance(self, other: &[T]) -> usize
666    where
667        T: PartialEq,
668    {
669        assert_eq!(self.len(), other.len(), "sequences must have the same size");
670        self.iter()
671            .zip(other.iter())
672            .filter(|(a, b)| a != b)
673            .count()
674    }
675
676    /// Minimum Hamming distance over all rotations of this view against
677    /// `other`.
678    ///
679    /// # Panics
680    ///
681    /// Panics if the lengths differ.
682    #[must_use]
683    pub fn min_rotational_hamming_distance(self, other: &[T]) -> usize
684    where
685        T: PartialEq,
686    {
687        assert_eq!(self.len(), other.len(), "sequences must have the same size");
688        let n = self.ring.len();
689        if n == 0 {
690            return 0;
691        }
692        let mut best = usize::MAX;
693        for rot in self.rotations() {
694            // Abandon a rotation as soon as it cannot beat the best so far.
695            let mut count = 0;
696            for (a, b) in rot.iter().zip(other.iter()) {
697                if a != b {
698                    count += 1;
699                    if count >= best {
700                        break;
701                    }
702                }
703            }
704            if count < best {
705                best = count;
706                if best == 0 {
707                    break;
708                }
709            }
710        }
711        best
712    }
713
714    /// Returns `true` if `needle` appears as a contiguous (possibly
715    /// wrapping) substring of this view.
716    ///
717    /// An empty `needle` is always contained.
718    #[must_use]
719    pub fn contains_slice(self, needle: &[T]) -> bool
720    where
721        T: PartialEq,
722    {
723        if needle.is_empty() {
724            return true;
725        }
726        if self.ring.is_empty() {
727            return false;
728        }
729        let n = self.ring.len();
730        let m = needle.len();
731        (0..n).any(|start| (0..m).all(|j| *self.apply((start + j) as isize) == needle[j]))
732    }
733
734    /// Returns the view position (normalized to `[0, len)`) at which
735    /// `needle` first appears, or `None` if it does not. Searches forward
736    /// from position `from`, wrapping around the seam.
737    ///
738    /// An empty `needle` matches immediately at `from` (normalized).
739    #[must_use]
740    pub fn index_of_slice(self, needle: &[T], from: isize) -> Option<usize>
741    where
742        T: PartialEq,
743    {
744        if self.ring.is_empty() {
745            return if needle.is_empty() { Some(0) } else { None };
746        }
747        let n = self.ring.len();
748        let start = from.rem_euclid(n as isize) as usize;
749        if needle.is_empty() {
750            return Some(start);
751        }
752        let m = needle.len();
753        (0..n)
754            .map(|k| (start + k) % n)
755            .find(|&i| (0..m).all(|j| *self.apply((i + j) as isize) == needle[j]))
756    }
757
758    // -----------------------------------------------------------------------
759    // Necklace — alloc-free
760    // -----------------------------------------------------------------------
761
762    /// Returns the starting offset of the lexicographically smallest
763    /// rotation of this view. Among tied (periodic) rotations, the
764    /// smallest offset is returned.
765    ///
766    /// Runs in O(n) time and O(1) space (the classic two-pointer
767    /// minimal-rotation algorithm), so it is available without the
768    /// `alloc` feature. Single-element and empty views return `0`.
769    ///
770    /// # Examples
771    ///
772    /// ```
773    /// use ring_seq::AsCircular;
774    ///
775    /// assert_eq!([3, 1, 2].circular().canonical_index(), 1);
776    /// ```
777    #[must_use]
778    pub fn canonical_index(self) -> usize
779    where
780        T: Ord,
781    {
782        let n = self.ring.len();
783        if n <= 1 {
784            return 0;
785        }
786        // Two candidate start offsets `i < j`; `k` is the length of their
787        // common prefix so far. Each comparison either extends the prefix
788        // or eliminates `k + 1` offsets for one candidate, so the loop is
789        // O(n) overall.
790        let at = |idx: usize| &self.ring[self.map_index(idx)];
791        let (mut i, mut j, mut k) = (0usize, 1usize, 0usize);
792        while i < n && j < n && k < n {
793            let a = at(i + k);
794            let b = at(j + k);
795            if a == b {
796                k += 1;
797                continue;
798            }
799            if a > b {
800                i += k + 1;
801            } else {
802                j += k + 1;
803            }
804            if i == j {
805                j += 1;
806            }
807            k = 0;
808        }
809        core::cmp::min(i, j)
810    }
811
812    // -----------------------------------------------------------------------
813    // Symmetry — alloc-free counts
814    // -----------------------------------------------------------------------
815
816    /// Returns the rotational symmetry order: `n / smallest_period`, where
817    /// the smallest period is the smallest divisor `d` of `n` such that
818    /// `apply(i) == apply(i + d)` for all `i`. Empty and single-element
819    /// rings have rotational symmetry 1.
820    #[must_use]
821    pub fn rotational_symmetry(self) -> usize
822    where
823        T: PartialEq,
824    {
825        let n = self.ring.len();
826        if n < 2 {
827            return 1;
828        }
829        let smallest = (1..=n).find(|&d| {
830            n % d == 0
831                && (0..n - d).all(|i| *self.apply(i as isize) == *self.apply((i + d) as isize))
832        });
833        n / smallest.unwrap_or(n)
834    }
835
836    /// Returns the number of reflectional symmetry axes — the count of
837    /// shifts at which this view equals its own reverse rotated by that
838    /// shift (without allocating the indices themselves).
839    ///
840    /// Use [`symmetry_indices`](Self::symmetry_indices) (alloc-gated) if
841    /// you need the actual indices.
842    #[must_use]
843    pub fn symmetry(self) -> usize
844    where
845        T: PartialEq,
846    {
847        let n = self.ring.len();
848        if n == 0 {
849            return 0;
850        }
851        let reversed = self.reflect_at(-1);
852        (0..n)
853            .filter(|&shift| {
854                (0..n).all(|i| *self.apply(i as isize) == *reversed.apply((i + shift) as isize))
855            })
856            .count()
857    }
858
859    /// Returns an iterator yielding `ceil(len / size)` non-overlapping
860    /// circular chunks of length `size` as [`CircularIter`]s.
861    ///
862    /// When `size` does not divide `len`, the final chunk wraps across
863    /// the seam so every chunk has exactly `size` elements. An empty ring
864    /// yields no chunks.
865    ///
866    /// # Panics
867    ///
868    /// Panics if `size` is zero.
869    ///
870    /// # Examples
871    ///
872    /// ```
873    /// use ring_seq::AsCircular;
874    ///
875    /// let r = [1, 2, 3, 4, 5].circular();
876    /// let chunks: Vec<Vec<i32>> = r
877    ///     .chunks(2)
878    ///     .map(|c| c.copied().collect())
879    ///     .collect();
880    /// // ceil(5/2) = 3 chunks; the last one wraps
881    /// assert_eq!(chunks, vec![vec![1, 2], vec![3, 4], vec![5, 1]]);
882    /// ```
883    #[must_use]
884    pub fn chunks(self, size: usize) -> Chunks<'a, T> {
885        assert!(size > 0, "chunk size must be positive");
886        let n = self.ring.len();
887        let total = if n == 0 { 0 } else { (n + size - 1) / size };
888        Windows {
889            base: self,
890            size,
891            step: size,
892            index: 0,
893            total,
894        }
895    }
896}
897
898// ---------------------------------------------------------------------------
899// CircularIter
900// ---------------------------------------------------------------------------
901
902/// Iterator over the elements of a [`Circular`] view.
903///
904/// Created by [`Circular::iter`]. Yields `&'a T` and is
905/// [`ExactSizeIterator`] + [`DoubleEndedIterator`] + [`FusedIterator`].
906#[derive(Debug)]
907pub struct CircularIter<'a, T> {
908    view: Circular<'a, T>,
909    pos: usize,
910    remaining: usize,
911}
912
913impl<T> Clone for CircularIter<'_, T> {
914    fn clone(&self) -> Self {
915        CircularIter {
916            view: self.view,
917            pos: self.pos,
918            remaining: self.remaining,
919        }
920    }
921}
922
923impl<'a, T> Iterator for CircularIter<'a, T> {
924    type Item = &'a T;
925
926    #[inline]
927    fn next(&mut self) -> Option<&'a T> {
928        if self.remaining == 0 {
929            return None;
930        }
931        let item = &self.view.ring[self.view.map_index(self.pos)];
932        self.pos += 1;
933        self.remaining -= 1;
934        Some(item)
935    }
936
937    #[inline]
938    fn size_hint(&self) -> (usize, Option<usize>) {
939        (self.remaining, Some(self.remaining))
940    }
941
942    #[inline]
943    fn nth(&mut self, n: usize) -> Option<&'a T> {
944        if n >= self.remaining {
945            self.pos += self.remaining;
946            self.remaining = 0;
947            return None;
948        }
949        self.pos += n;
950        self.remaining -= n;
951        self.next()
952    }
953
954    #[inline]
955    fn count(self) -> usize {
956        self.remaining
957    }
958
959    #[inline]
960    fn last(mut self) -> Option<&'a T> {
961        self.next_back()
962    }
963}
964
965impl<'a, T> DoubleEndedIterator for CircularIter<'a, T> {
966    #[inline]
967    fn next_back(&mut self) -> Option<&'a T> {
968        if self.remaining == 0 {
969            return None;
970        }
971        self.remaining -= 1;
972        Some(&self.view.ring[self.view.map_index(self.pos + self.remaining)])
973    }
974}
975
976impl<T> ExactSizeIterator for CircularIter<'_, T> {}
977impl<T> FusedIterator for CircularIter<'_, T> {}
978
979/// `for x in ring.circular()` walks the view once, in order.
980impl<'a, T> IntoIterator for Circular<'a, T> {
981    type Item = &'a T;
982    type IntoIter = CircularIter<'a, T>;
983
984    #[inline]
985    fn into_iter(self) -> CircularIter<'a, T> {
986        self.iter()
987    }
988}
989
990impl<'a, T> IntoIterator for &Circular<'a, T> {
991    type Item = &'a T;
992    type IntoIter = CircularIter<'a, T>;
993
994    #[inline]
995    fn into_iter(self) -> CircularIter<'a, T> {
996        self.iter()
997    }
998}
999
1000/// `ring.circular()[i]` is [`apply`](Circular::apply): the index wraps in
1001/// both directions.
1002///
1003/// # Panics
1004///
1005/// Panics if the ring is empty.
1006impl<T> core::ops::Index<isize> for Circular<'_, T> {
1007    type Output = T;
1008
1009    #[inline]
1010    fn index(&self, i: isize) -> &T {
1011        self.apply(i)
1012    }
1013}
1014
1015// ---------------------------------------------------------------------------
1016// Enumerate — yields (&T, ring_index)
1017// ---------------------------------------------------------------------------
1018
1019/// Iterator over `(element, ring_index)` pairs of a [`Circular`] view.
1020///
1021/// Created by [`Circular::enumerate`]. Yields `(&'a T, usize)` where the
1022/// `usize` is the position in the underlying ring (already wrapped to
1023/// `[0, len)`).
1024#[derive(Debug)]
1025pub struct Enumerate<'a, T> {
1026    view: Circular<'a, T>,
1027    pos: usize,
1028    remaining: usize,
1029}
1030
1031impl<T> Clone for Enumerate<'_, T> {
1032    fn clone(&self) -> Self {
1033        Enumerate {
1034            view: self.view,
1035            pos: self.pos,
1036            remaining: self.remaining,
1037        }
1038    }
1039}
1040
1041impl<'a, T> Iterator for Enumerate<'a, T> {
1042    type Item = (&'a T, usize);
1043
1044    #[inline]
1045    fn next(&mut self) -> Option<Self::Item> {
1046        if self.remaining == 0 {
1047            return None;
1048        }
1049        let idx = self.view.map_index(self.pos);
1050        let item = &self.view.ring[idx];
1051        self.pos += 1;
1052        self.remaining -= 1;
1053        Some((item, idx))
1054    }
1055
1056    #[inline]
1057    fn size_hint(&self) -> (usize, Option<usize>) {
1058        (self.remaining, Some(self.remaining))
1059    }
1060}
1061
1062impl<T> ExactSizeIterator for Enumerate<'_, T> {}
1063impl<T> FusedIterator for Enumerate<'_, T> {}
1064
1065// ---------------------------------------------------------------------------
1066// Rotations — yields each rotation as a Circular view
1067// ---------------------------------------------------------------------------
1068
1069/// Iterator over the rotations of a [`Circular`] view.
1070///
1071/// Created by [`Circular::rotations`]. Yields one [`Circular`] per
1072/// starting position; preserves the `reflected` bit of the source view.
1073#[derive(Debug)]
1074pub struct Rotations<'a, T> {
1075    base: Circular<'a, T>,
1076    index: usize,
1077    total: usize,
1078}
1079
1080impl<T> Clone for Rotations<'_, T> {
1081    fn clone(&self) -> Self {
1082        Rotations {
1083            base: self.base,
1084            index: self.index,
1085            total: self.total,
1086        }
1087    }
1088}
1089
1090impl<'a, T> Iterator for Rotations<'a, T> {
1091    type Item = Circular<'a, T>;
1092
1093    fn next(&mut self) -> Option<Self::Item> {
1094        if self.index >= self.total {
1095            return None;
1096        }
1097        let item = if self.base.ring.is_empty() {
1098            self.base
1099        } else {
1100            self.base.start_at(self.index as isize)
1101        };
1102        self.index += 1;
1103        Some(item)
1104    }
1105
1106    fn size_hint(&self) -> (usize, Option<usize>) {
1107        let r = self.total - self.index;
1108        (r, Some(r))
1109    }
1110}
1111
1112impl<T> ExactSizeIterator for Rotations<'_, T> {}
1113impl<T> FusedIterator for Rotations<'_, T> {}
1114
1115// ---------------------------------------------------------------------------
1116// Reflections — yields the view and its reflection at position 0
1117// ---------------------------------------------------------------------------
1118
1119/// Iterator yielding the source [`Circular`] and its [`reflect_at(0)`].
1120///
1121/// Created by [`Circular::reflections`].
1122///
1123/// [`reflect_at(0)`]: Circular::reflect_at
1124#[derive(Debug)]
1125pub struct Reflections<'a, T> {
1126    base: Circular<'a, T>,
1127    state: u8,
1128}
1129
1130impl<T> Clone for Reflections<'_, T> {
1131    fn clone(&self) -> Self {
1132        Reflections {
1133            base: self.base,
1134            state: self.state,
1135        }
1136    }
1137}
1138
1139impl<'a, T> Iterator for Reflections<'a, T> {
1140    type Item = Circular<'a, T>;
1141
1142    fn next(&mut self) -> Option<Self::Item> {
1143        match self.state {
1144            0 => {
1145                self.state = if self.base.ring.is_empty() { 2 } else { 1 };
1146                Some(self.base)
1147            }
1148            1 => {
1149                self.state = 2;
1150                Some(self.base.reflect_at(0))
1151            }
1152            _ => None,
1153        }
1154    }
1155
1156    fn size_hint(&self) -> (usize, Option<usize>) {
1157        let r = match self.state {
1158            0 => {
1159                if self.base.ring.is_empty() {
1160                    1
1161                } else {
1162                    2
1163                }
1164            }
1165            1 => 1,
1166            _ => 0,
1167        };
1168        (r, Some(r))
1169    }
1170}
1171
1172impl<T> ExactSizeIterator for Reflections<'_, T> {}
1173impl<T> FusedIterator for Reflections<'_, T> {}
1174
1175// ---------------------------------------------------------------------------
1176// Reversions — yields the view and its reverse
1177// ---------------------------------------------------------------------------
1178
1179/// Iterator yielding the source [`Circular`] and its reverse
1180/// (`reflect_at(-1)`).
1181///
1182/// Created by [`Circular::reversions`].
1183#[derive(Debug)]
1184pub struct Reversions<'a, T> {
1185    base: Circular<'a, T>,
1186    state: u8,
1187}
1188
1189impl<T> Clone for Reversions<'_, T> {
1190    fn clone(&self) -> Self {
1191        Reversions {
1192            base: self.base,
1193            state: self.state,
1194        }
1195    }
1196}
1197
1198impl<'a, T> Iterator for Reversions<'a, T> {
1199    type Item = Circular<'a, T>;
1200
1201    fn next(&mut self) -> Option<Self::Item> {
1202        match self.state {
1203            0 => {
1204                self.state = if self.base.ring.is_empty() { 2 } else { 1 };
1205                Some(self.base)
1206            }
1207            1 => {
1208                self.state = 2;
1209                Some(self.base.reflect_at(-1))
1210            }
1211            _ => None,
1212        }
1213    }
1214
1215    fn size_hint(&self) -> (usize, Option<usize>) {
1216        let r = match self.state {
1217            0 => {
1218                if self.base.ring.is_empty() {
1219                    1
1220                } else {
1221                    2
1222                }
1223            }
1224            1 => 1,
1225            _ => 0,
1226        };
1227        (r, Some(r))
1228    }
1229}
1230
1231impl<T> ExactSizeIterator for Reversions<'_, T> {}
1232impl<T> FusedIterator for Reversions<'_, T> {}
1233
1234// ---------------------------------------------------------------------------
1235// RotationsAndReflections — yields 2n views
1236// ---------------------------------------------------------------------------
1237
1238/// Iterator yielding every rotation of the source followed by every
1239/// rotation of its reflection at position 0.
1240///
1241/// Created by [`Circular::rotations_and_reflections`].
1242#[derive(Debug)]
1243pub struct RotationsAndReflections<'a, T> {
1244    base: Circular<'a, T>,
1245    reflected: Circular<'a, T>,
1246    index: usize,
1247    total: usize,
1248    n: usize,
1249}
1250
1251impl<T> Clone for RotationsAndReflections<'_, T> {
1252    fn clone(&self) -> Self {
1253        RotationsAndReflections {
1254            base: self.base,
1255            reflected: self.reflected,
1256            index: self.index,
1257            total: self.total,
1258            n: self.n,
1259        }
1260    }
1261}
1262
1263impl<'a, T> Iterator for RotationsAndReflections<'a, T> {
1264    type Item = Circular<'a, T>;
1265
1266    fn next(&mut self) -> Option<Self::Item> {
1267        if self.index >= self.total {
1268            return None;
1269        }
1270        if self.n == 0 {
1271            self.index += 1;
1272            return Some(self.base);
1273        }
1274        let item = if self.index < self.n {
1275            self.base.start_at(self.index as isize)
1276        } else {
1277            self.reflected.start_at((self.index - self.n) as isize)
1278        };
1279        self.index += 1;
1280        Some(item)
1281    }
1282
1283    fn size_hint(&self) -> (usize, Option<usize>) {
1284        let r = self.total - self.index;
1285        (r, Some(r))
1286    }
1287}
1288
1289impl<T> ExactSizeIterator for RotationsAndReflections<'_, T> {}
1290impl<T> FusedIterator for RotationsAndReflections<'_, T> {}
1291
1292// ---------------------------------------------------------------------------
1293// Windows / Chunks — yield CircularIter<'a, T>
1294// ---------------------------------------------------------------------------
1295
1296/// Iterator yielding circular windows (or chunks) of a [`Circular`] view
1297/// as [`CircularIter`]s.
1298///
1299/// Created by [`Circular::windows`] (step 1) and [`Circular::chunks`]
1300/// (step equal to the chunk size).
1301#[derive(Debug)]
1302pub struct Windows<'a, T> {
1303    base: Circular<'a, T>,
1304    size: usize,
1305    step: usize,
1306    index: usize,
1307    total: usize,
1308}
1309
1310impl<T> Clone for Windows<'_, T> {
1311    fn clone(&self) -> Self {
1312        Windows {
1313            base: self.base,
1314            size: self.size,
1315            step: self.step,
1316            index: self.index,
1317            total: self.total,
1318        }
1319    }
1320}
1321
1322impl<'a, T> Iterator for Windows<'a, T> {
1323    type Item = CircularIter<'a, T>;
1324
1325    fn next(&mut self) -> Option<Self::Item> {
1326        if self.index >= self.total {
1327            return None;
1328        }
1329        let start = (self.index * self.step) as isize;
1330        let view = self.base.start_at(start);
1331        let iter = CircularIter {
1332            view,
1333            pos: 0,
1334            remaining: self.size,
1335        };
1336        self.index += 1;
1337        Some(iter)
1338    }
1339
1340    fn size_hint(&self) -> (usize, Option<usize>) {
1341        let r = self.total - self.index;
1342        (r, Some(r))
1343    }
1344}
1345
1346impl<T> ExactSizeIterator for Windows<'_, T> {}
1347impl<T> FusedIterator for Windows<'_, T> {}
1348
1349/// Iterator returned by [`Circular::chunks`] — the same shape as
1350/// [`Windows`], stepping by the chunk size instead of 1.
1351pub type Chunks<'a, T> = Windows<'a, T>;
1352
1353// ---------------------------------------------------------------------------
1354// AsCircular
1355// ---------------------------------------------------------------------------
1356
1357/// Extension trait providing [`circular`](AsCircular::circular) on slices.
1358///
1359/// Implemented for `[T]`; reaches `Vec<T>`, arrays, and `Box<[T]>` via
1360/// deref coercion.
1361///
1362/// # Examples
1363///
1364/// ```
1365/// use ring_seq::AsCircular;
1366///
1367/// let v = vec![1, 2, 3];
1368/// let r = v.circular();
1369/// assert_eq!(*r.apply(5), 3);
1370/// ```
1371pub trait AsCircular<T> {
1372    /// Returns a [`Circular`] view of this slice.
1373    fn circular(&self) -> Circular<'_, T>;
1374}
1375
1376impl<T> AsCircular<T> for [T] {
1377    #[inline]
1378    fn circular(&self) -> Circular<'_, T> {
1379        Circular::new(self)
1380    }
1381}
1382
1383// ---------------------------------------------------------------------------
1384// Alloc-gated section: methods returning owned collections.
1385// ---------------------------------------------------------------------------
1386
1387#[cfg(feature = "alloc")]
1388impl<'a, T> Circular<'a, T> {
1389    /// Materializes this view as a `Vec<T>`. Requires `T: Clone`.
1390    #[must_use]
1391    pub fn to_vec(self) -> Vec<T>
1392    where
1393        T: Clone,
1394    {
1395        self.iter().cloned().collect()
1396    }
1397
1398    /// Returns the canonical (lexicographically smallest) rotation of
1399    /// this view as a `Vec<T>`. Requires `T: Clone + Ord`.
1400    #[must_use]
1401    pub fn canonical(self) -> Vec<T>
1402    where
1403        T: Clone + Ord,
1404    {
1405        let idx = self.canonical_index();
1406        self.start_at(idx as isize).to_vec()
1407    }
1408
1409    /// Returns the bracelet form: the lexicographically smallest among
1410    /// `canonical()` and `reflect_at(0).canonical()`. Requires
1411    /// `T: Clone + Ord`.
1412    #[must_use]
1413    pub fn bracelet(self) -> Vec<T>
1414    where
1415        T: Clone + Ord,
1416    {
1417        let a = self.canonical();
1418        let b = self.reflect_at(0).canonical();
1419        if a <= b {
1420            a
1421        } else {
1422            b
1423        }
1424    }
1425
1426    /// Returns the shifts `k` in `[0, n)` for which this view equals its
1427    /// own reverse rotated by `k`. Each shift corresponds to one axis of
1428    /// reflectional symmetry.
1429    #[must_use]
1430    pub fn symmetry_indices(self) -> Vec<usize>
1431    where
1432        T: PartialEq,
1433    {
1434        let n = self.ring.len();
1435        if n == 0 {
1436            return Vec::new();
1437        }
1438        let reversed = self.reflect_at(-1);
1439        (0..n)
1440            .filter(|&shift| {
1441                (0..n).all(|i| *self.apply(i as isize) == *reversed.apply((i + shift) as isize))
1442            })
1443            .collect()
1444    }
1445
1446    /// Returns the axes of reflectional symmetry as
1447    /// `(AxisLocation, AxisLocation)` pairs (each axis hits the ring in
1448    /// two locations).
1449    #[must_use]
1450    pub fn reflectional_symmetry_axes(self) -> Vec<(AxisLocation, AxisLocation)>
1451    where
1452        T: PartialEq,
1453    {
1454        let n = self.ring.len();
1455        if n == 0 {
1456            return Vec::new();
1457        }
1458
1459        #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
1460        self.symmetry_indices()
1461            .into_iter()
1462            .map(|shift| {
1463                let raw_k = (n as isize - 1 - shift as isize) % n as isize;
1464                let k = if raw_k < 0 {
1465                    (raw_k + n as isize) as usize
1466                } else {
1467                    raw_k as usize
1468                };
1469
1470                if n % 2 != 0 {
1471                    let v = (k * (n + 1) / 2) % n;
1472                    let opp = (v + n / 2) % n;
1473                    (AxisLocation::Vertex(v), AxisLocation::edge(opp, n))
1474                } else if k % 2 == 0 {
1475                    let v1 = k / 2;
1476                    let v2 = (v1 + n / 2) % n;
1477                    (AxisLocation::Vertex(v1), AxisLocation::Vertex(v2))
1478                } else {
1479                    let e1 = (k - 1) / 2;
1480                    let e2 = (e1 + n / 2) % n;
1481                    (AxisLocation::edge(e1, n), AxisLocation::edge(e2, n))
1482                }
1483            })
1484            .collect()
1485    }
1486}
1487
1488// ---------------------------------------------------------------------------
1489// Tests — pure core, no alloc needed
1490// ---------------------------------------------------------------------------
1491
1492#[cfg(test)]
1493mod tests {
1494    use super::*;
1495
1496    #[test]
1497    fn empty_ring_is_empty() {
1498        let empty: [i32; 0] = [];
1499        let c = empty.circular();
1500        assert_eq!(c.len(), 0);
1501        assert!(c.is_empty());
1502        assert_eq!(c.iter().count(), 0);
1503    }
1504
1505    #[test]
1506    fn len_and_is_empty() {
1507        let r = [1, 2, 3].circular();
1508        assert_eq!(r.len(), 3);
1509        assert!(!r.is_empty());
1510    }
1511
1512    #[test]
1513    fn apply_positive() {
1514        let r = [10, 20, 30].circular();
1515        assert_eq!(*r.apply(0), 10);
1516        assert_eq!(*r.apply(1), 20);
1517        assert_eq!(*r.apply(2), 30);
1518    }
1519
1520    #[test]
1521    fn apply_wraps_forward() {
1522        let r = [10, 20, 30].circular();
1523        assert_eq!(*r.apply(3), 10);
1524        assert_eq!(*r.apply(7), 20);
1525    }
1526
1527    #[test]
1528    fn apply_wraps_backward() {
1529        let r = [10, 20, 30].circular();
1530        assert_eq!(*r.apply(-1), 30);
1531        assert_eq!(*r.apply(-3), 10);
1532        assert_eq!(*r.apply(-4), 30);
1533    }
1534
1535    #[test]
1536    #[should_panic]
1537    fn apply_panics_on_empty() {
1538        let empty: [i32; 0] = [];
1539        let _ = empty.circular().apply(0);
1540    }
1541
1542    #[test]
1543    fn iter_yields_elements_in_order() {
1544        let r = [1, 2, 3].circular();
1545        let mut it = r.iter();
1546        assert_eq!(it.next(), Some(&1));
1547        assert_eq!(it.next(), Some(&2));
1548        assert_eq!(it.next(), Some(&3));
1549        assert_eq!(it.next(), None);
1550    }
1551
1552    #[test]
1553    fn iter_size_hint_and_exact_size() {
1554        let r = [1, 2, 3, 4].circular();
1555        let mut it = r.iter();
1556        assert_eq!(it.len(), 4);
1557        assert_eq!(it.size_hint(), (4, Some(4)));
1558        it.next();
1559        assert_eq!(it.len(), 3);
1560    }
1561
1562    #[test]
1563    fn iter_is_fused() {
1564        let r = [1, 2].circular();
1565        let mut it = r.iter();
1566        it.next();
1567        it.next();
1568        assert_eq!(it.next(), None);
1569        assert_eq!(it.next(), None); // stays None
1570    }
1571
1572    #[test]
1573    fn copy_semantics() {
1574        let r = [1, 2, 3].circular();
1575        let r2 = r; // copy
1576        assert_eq!(r.len(), 3);
1577        assert_eq!(r2.len(), 3);
1578    }
1579
1580    // T does not need to be Clone for Circular to be Copy.
1581    #[test]
1582    fn copy_works_without_t_clone() {
1583        #[allow(dead_code)]
1584        struct NoClone(i32);
1585        let arr = [NoClone(1), NoClone(2)];
1586        let r = arr.circular();
1587        let r2 = r; // would fail to compile if Copy carried a T: Clone bound
1588        assert_eq!(r.len(), 2);
1589        assert_eq!(r2.len(), 2);
1590    }
1591
1592    // ── Lazy view operations ────────────────────────────────────────────
1593
1594    fn into_array<const N: usize>(c: Circular<'_, i32>) -> [i32; N] {
1595        let mut out = [0; N];
1596        for (slot, &x) in out.iter_mut().zip(c.iter()) {
1597            *slot = x;
1598        }
1599        out
1600    }
1601
1602    #[test]
1603    fn start_at_basic() {
1604        let r = [10, 20, 30, 40].circular();
1605        assert_eq!(into_array::<4>(r.start_at(0)), [10, 20, 30, 40]);
1606        assert_eq!(into_array::<4>(r.start_at(2)), [30, 40, 10, 20]);
1607        assert_eq!(into_array::<4>(r.start_at(-1)), [40, 10, 20, 30]);
1608        assert_eq!(into_array::<4>(r.start_at(5)), [20, 30, 40, 10]);
1609    }
1610
1611    #[test]
1612    fn start_at_empty() {
1613        let empty: [i32; 0] = [];
1614        assert_eq!(empty.circular().start_at(3).len(), 0);
1615    }
1616
1617    #[test]
1618    fn rotate_right_basic() {
1619        let r = [1, 2, 3, 4].circular();
1620        assert_eq!(into_array::<4>(r.rotate_right(0)), [1, 2, 3, 4]);
1621        assert_eq!(into_array::<4>(r.rotate_right(1)), [4, 1, 2, 3]);
1622        assert_eq!(into_array::<4>(r.rotate_right(-1)), [2, 3, 4, 1]);
1623        assert_eq!(into_array::<4>(r.rotate_right(5)), [4, 1, 2, 3]);
1624    }
1625
1626    #[test]
1627    fn rotate_left_basic() {
1628        let r = [1, 2, 3, 4].circular();
1629        assert_eq!(into_array::<4>(r.rotate_left(1)), [2, 3, 4, 1]);
1630        assert_eq!(into_array::<4>(r.rotate_left(-1)), [4, 1, 2, 3]);
1631    }
1632
1633    #[test]
1634    fn rotate_right_then_left_is_identity() {
1635        let ring = [1, 2, 3, 4, 5, 6, 7];
1636        let r = ring.circular();
1637        for step in -10..=10 {
1638            assert_eq!(
1639                into_array::<7>(r.rotate_right(step).rotate_left(step)),
1640                ring,
1641                "failed for step {}",
1642                step,
1643            );
1644        }
1645    }
1646
1647    #[test]
1648    fn reflect_at_basic() {
1649        let r = [10, 20, 30, 40].circular();
1650        // reflect_at(0): pos 0 = ring[0]=10, pos 1 = ring[3]=40, pos 2 = ring[2]=30, pos 3 = ring[1]=20
1651        assert_eq!(into_array::<4>(r.reflect_at(0)), [10, 40, 30, 20]);
1652        // reflect_at(2): pos 0 = ring[2]=30, pos 1 = ring[1]=20, pos 2 = ring[0]=10, pos 3 = ring[3]=40
1653        assert_eq!(into_array::<4>(r.reflect_at(2)), [30, 20, 10, 40]);
1654    }
1655
1656    #[test]
1657    fn reflect_at_is_involution() {
1658        let ring = [1, 2, 3, 4, 5, 6, 7];
1659        let r = ring.circular();
1660        for i in -3..10 {
1661            assert_eq!(
1662                into_array::<7>(r.reflect_at(i).reflect_at(i)),
1663                ring,
1664                "failed for i {}",
1665                i
1666            );
1667        }
1668    }
1669
1670    #[test]
1671    fn rotate_then_reflect_commutes() {
1672        // rotate_right(k).reflect_at(0) == reflect_at(0).rotate_left(k)
1673        let ring = [1, 2, 3, 4, 5];
1674        let r = ring.circular();
1675        for k in -7..=7 {
1676            assert_eq!(
1677                into_array::<5>(r.rotate_right(k).reflect_at(0)),
1678                into_array::<5>(r.reflect_at(0).rotate_left(k)),
1679                "failed for k {}",
1680                k,
1681            );
1682        }
1683    }
1684
1685    // ── slice ──────────────────────────────────────────────────────────
1686
1687    #[test]
1688    fn slice_within_one_lap() {
1689        let r = [0, 1, 2, 3, 4].circular();
1690        let v: [i32; 3] = {
1691            let mut out = [0; 3];
1692            for (s, &x) in out.iter_mut().zip(r.slice(1, 4)) {
1693                *s = x;
1694            }
1695            out
1696        };
1697        assert_eq!(v, [1, 2, 3]);
1698    }
1699
1700    #[test]
1701    fn slice_wraps() {
1702        let r = [0, 1, 2, 3, 4].circular();
1703        let mut out = [0; 5];
1704        for (s, &x) in out.iter_mut().zip(r.slice(2, 7)) {
1705            *s = x;
1706        }
1707        assert_eq!(out, [2, 3, 4, 0, 1]);
1708    }
1709
1710    #[test]
1711    fn slice_negative_from() {
1712        let r = [0, 1, 2, 3, 4].circular();
1713        let mut out = [0; 3];
1714        for (s, &x) in out.iter_mut().zip(r.slice(-2, 1)) {
1715            *s = x;
1716        }
1717        assert_eq!(out, [3, 4, 0]);
1718    }
1719
1720    #[test]
1721    fn slice_empty_range() {
1722        let r = [0, 1, 2, 3, 4].circular();
1723        assert_eq!(r.slice(3, 3).count(), 0);
1724        assert_eq!(r.slice(5, 2).count(), 0);
1725    }
1726
1727    #[test]
1728    fn slice_empty_ring() {
1729        let empty: [i32; 0] = [];
1730        assert_eq!(empty.circular().slice(0, 4).count(), 0);
1731    }
1732
1733    // ── take_while / drop_while ────────────────────────────────────────
1734
1735    #[test]
1736    fn take_while_basic() {
1737        let r = [1, 2, 3, 4, 5].circular();
1738        let mut out = [0; 3];
1739        for (s, &x) in out.iter_mut().zip(r.take_while(|&x| x < 4, 0)) {
1740            *s = x;
1741        }
1742        assert_eq!(out, [1, 2, 3]);
1743    }
1744
1745    #[test]
1746    fn take_while_stops_after_one_lap() {
1747        // If the predicate is always true, take_while still terminates at n elements.
1748        let r = [1, 1, 1].circular();
1749        assert_eq!(r.take_while(|_| true, 0).count(), 3);
1750    }
1751
1752    #[test]
1753    fn drop_while_basic() {
1754        let r = [1, 2, 3, 4, 5].circular();
1755        let mut out = [0; 2];
1756        for (s, &x) in out.iter_mut().zip(r.drop_while(|&x| x < 4, 0)) {
1757            *s = x;
1758        }
1759        assert_eq!(out, [4, 5]);
1760    }
1761
1762    #[test]
1763    fn drop_while_all_dropped() {
1764        let r = [1, 2, 3].circular();
1765        assert_eq!(r.drop_while(|_| true, 0).count(), 0);
1766    }
1767
1768    // ── enumerate ──────────────────────────────────────────────────────
1769
1770    #[test]
1771    fn enumerate_yields_ring_indices() {
1772        let r = [10, 20, 30].circular();
1773        let mut out: [(i32, usize); 3] = [(0, 0); 3];
1774        for (s, (&x, i)) in out.iter_mut().zip(r.enumerate(1)) {
1775            *s = (x, i);
1776        }
1777        assert_eq!(out, [(20, 1), (30, 2), (10, 0)]);
1778    }
1779
1780    #[test]
1781    fn enumerate_empty() {
1782        let empty: [i32; 0] = [];
1783        assert_eq!(empty.circular().enumerate(0).count(), 0);
1784    }
1785
1786    #[test]
1787    fn enumerate_is_exact_size() {
1788        let r = [10, 20, 30, 40].circular();
1789        let it = r.enumerate(2);
1790        assert_eq!(it.len(), 4);
1791    }
1792
1793    // ── Iterators of views ─────────────────────────────────────────────
1794
1795    #[test]
1796    fn rotations_yields_n_views() {
1797        let ring = [1, 2, 3, 4];
1798        let r = ring.circular();
1799        let firsts: [i32; 4] = {
1800            let mut out = [0; 4];
1801            for (s, v) in out.iter_mut().zip(r.rotations()) {
1802                *s = *v.apply(0);
1803            }
1804            out
1805        };
1806        assert_eq!(firsts, [1, 2, 3, 4]);
1807    }
1808
1809    #[test]
1810    fn rotations_empty_yields_one() {
1811        let empty: [i32; 0] = [];
1812        assert_eq!(empty.circular().rotations().count(), 1);
1813    }
1814
1815    #[test]
1816    fn rotations_exact_size() {
1817        let r = [1, 2, 3, 4, 5].circular();
1818        let it = r.rotations();
1819        assert_eq!(it.len(), 5);
1820    }
1821
1822    #[test]
1823    fn rotations_preserves_reflected() {
1824        // Rotations of the reflected view should each be reflected too.
1825        let r = [1, 2, 3].circular().reflect_at(0);
1826        let firsts: [i32; 3] = {
1827            let mut out = [0; 3];
1828            for (s, v) in out.iter_mut().zip(r.rotations()) {
1829                *s = *v.apply(0);
1830            }
1831            out
1832        };
1833        // Reflected of [1,2,3] at 0 = [1, 3, 2]. Its rotations start at 1, 3, 2.
1834        assert_eq!(firsts, [1, 3, 2]);
1835    }
1836
1837    #[test]
1838    fn reflections_yields_two() {
1839        let r = [1, 2, 3, 4].circular();
1840        let count = r.reflections().count();
1841        assert_eq!(count, 2);
1842    }
1843
1844    #[test]
1845    fn reflections_second_is_reflect_at_zero() {
1846        let r = [1, 2, 3, 4].circular();
1847        let mut it = r.reflections();
1848        let first = it.next().unwrap();
1849        let second = it.next().unwrap();
1850        let first_v: [i32; 4] = into_array(first);
1851        let second_v: [i32; 4] = into_array(second);
1852        assert_eq!(first_v, [1, 2, 3, 4]);
1853        assert_eq!(second_v, [1, 4, 3, 2]);
1854    }
1855
1856    #[test]
1857    fn reflections_empty_yields_one() {
1858        let empty: [i32; 0] = [];
1859        assert_eq!(empty.circular().reflections().count(), 1);
1860    }
1861
1862    #[test]
1863    fn reversions_yields_two() {
1864        let r = [1, 2, 3].circular();
1865        assert_eq!(r.reversions().count(), 2);
1866    }
1867
1868    #[test]
1869    fn reversions_second_is_reverse() {
1870        let r = [1, 2, 3, 4].circular();
1871        let mut it = r.reversions();
1872        let first = it.next().unwrap();
1873        let second = it.next().unwrap();
1874        let first_v: [i32; 4] = into_array(first);
1875        let second_v: [i32; 4] = into_array(second);
1876        assert_eq!(first_v, [1, 2, 3, 4]);
1877        assert_eq!(second_v, [4, 3, 2, 1]);
1878    }
1879
1880    #[test]
1881    fn rotations_and_reflections_yields_2n() {
1882        let r = [1, 2, 3, 4].circular();
1883        assert_eq!(r.rotations_and_reflections().count(), 8);
1884    }
1885
1886    #[test]
1887    fn rotations_and_reflections_distinct_views() {
1888        // Distinct shapes for an aperiodic ring.
1889        let r = [1, 2, 3, 4].circular();
1890        let all: [[i32; 4]; 8] = {
1891            let mut out = [[0; 4]; 8];
1892            for (s, v) in out.iter_mut().zip(r.rotations_and_reflections()) {
1893                *s = into_array(v);
1894            }
1895            out
1896        };
1897        assert_eq!(all[0], [1, 2, 3, 4]);
1898        assert_eq!(all[1], [2, 3, 4, 1]);
1899        assert_eq!(all[2], [3, 4, 1, 2]);
1900        assert_eq!(all[3], [4, 1, 2, 3]);
1901        // Reflected half: rotations of [1, 4, 3, 2] (reflect_at(0) of [1,2,3,4])
1902        assert_eq!(all[4], [1, 4, 3, 2]);
1903        assert_eq!(all[5], [4, 3, 2, 1]);
1904        assert_eq!(all[6], [3, 2, 1, 4]);
1905        assert_eq!(all[7], [2, 1, 4, 3]);
1906    }
1907
1908    #[test]
1909    fn rotations_and_reflections_empty_yields_one() {
1910        let empty: [i32; 0] = [];
1911        assert_eq!(empty.circular().rotations_and_reflections().count(), 1);
1912    }
1913
1914    // ── Windows / Chunks ───────────────────────────────────────────────
1915
1916    fn collect_iter<const N: usize>(mut iter: CircularIter<'_, i32>) -> [i32; N] {
1917        let mut out = [0; N];
1918        for s in out.iter_mut() {
1919            *s = *iter.next().unwrap();
1920        }
1921        out
1922    }
1923
1924    #[test]
1925    fn windows_basic() {
1926        let r = [1, 2, 3, 4].circular();
1927        let mut it = r.windows(2);
1928        assert_eq!(collect_iter::<2>(it.next().unwrap()), [1, 2]);
1929        assert_eq!(collect_iter::<2>(it.next().unwrap()), [2, 3]);
1930        assert_eq!(collect_iter::<2>(it.next().unwrap()), [3, 4]);
1931        assert_eq!(collect_iter::<2>(it.next().unwrap()), [4, 1]); // wraps
1932        assert!(it.next().is_none());
1933    }
1934
1935    #[test]
1936    fn windows_count_equals_ring_len() {
1937        let r = [1, 2, 3, 4, 5].circular();
1938        assert_eq!(r.windows(2).count(), 5);
1939        assert_eq!(r.windows(3).count(), 5);
1940        assert_eq!(r.windows(5).count(), 5);
1941    }
1942
1943    #[test]
1944    fn windows_size_greater_than_ring() {
1945        let r = [1, 2, 3].circular();
1946        // Each window of size 5 wraps multiple times.
1947        let mut it = r.windows(5);
1948        assert_eq!(collect_iter::<5>(it.next().unwrap()), [1, 2, 3, 1, 2]);
1949        assert_eq!(collect_iter::<5>(it.next().unwrap()), [2, 3, 1, 2, 3]);
1950        assert_eq!(collect_iter::<5>(it.next().unwrap()), [3, 1, 2, 3, 1]);
1951        assert!(it.next().is_none());
1952    }
1953
1954    #[test]
1955    fn windows_empty_ring() {
1956        let empty: [i32; 0] = [];
1957        assert_eq!(empty.circular().windows(3).count(), 0);
1958    }
1959
1960    #[test]
1961    #[should_panic]
1962    fn windows_zero_size_panics() {
1963        let _ = [1, 2, 3].circular().windows(0);
1964    }
1965
1966    #[test]
1967    fn chunks_evenly_divisible() {
1968        let r = [1, 2, 3, 4].circular();
1969        let mut it = r.chunks(2);
1970        assert_eq!(collect_iter::<2>(it.next().unwrap()), [1, 2]);
1971        assert_eq!(collect_iter::<2>(it.next().unwrap()), [3, 4]);
1972        assert!(it.next().is_none());
1973    }
1974
1975    #[test]
1976    fn chunks_wrap_at_seam() {
1977        // ceil(5/2) = 3 chunks; last one wraps
1978        let r = [1, 2, 3, 4, 5].circular();
1979        let mut it = r.chunks(2);
1980        assert_eq!(collect_iter::<2>(it.next().unwrap()), [1, 2]);
1981        assert_eq!(collect_iter::<2>(it.next().unwrap()), [3, 4]);
1982        assert_eq!(collect_iter::<2>(it.next().unwrap()), [5, 1]);
1983        assert!(it.next().is_none());
1984    }
1985
1986    #[test]
1987    fn chunks_size_one() {
1988        let r = [1, 2, 3].circular();
1989        assert_eq!(r.chunks(1).count(), 3);
1990    }
1991
1992    #[test]
1993    fn chunks_size_greater_than_ring() {
1994        let r = [1, 2, 3].circular();
1995        // ceil(3/5) = 1 chunk
1996        let mut it = r.chunks(5);
1997        assert_eq!(collect_iter::<5>(it.next().unwrap()), [1, 2, 3, 1, 2]);
1998        assert!(it.next().is_none());
1999    }
2000
2001    #[test]
2002    fn chunks_empty_ring() {
2003        let empty: [i32; 0] = [];
2004        assert_eq!(empty.circular().chunks(2).count(), 0);
2005    }
2006
2007    #[test]
2008    #[should_panic]
2009    fn chunks_zero_size_panics() {
2010        let _ = [1, 2, 3].circular().chunks(0);
2011    }
2012
2013    // ── Chained operations ─────────────────────────────────────────────
2014
2015    #[test]
2016    fn chained_rotations_first_element() {
2017        let r = [3, 1, 2].circular();
2018        let firsts: [i32; 3] = {
2019            let mut out = [0; 3];
2020            for (s, v) in out.iter_mut().zip(r.rotations()) {
2021                *s = *v.apply(0);
2022            }
2023            out
2024        };
2025        assert_eq!(firsts, [3, 1, 2]);
2026    }
2027
2028    #[test]
2029    fn chained_windows_count_satisfying() {
2030        let r = [1, 2, 3, 4, 5].circular();
2031        // Count windows whose first element is even
2032        let n = r
2033            .windows(2)
2034            .filter(|w| {
2035                let mut clone = w.clone();
2036                clone.next().map_or(false, |x| x % 2 == 0)
2037            })
2038            .count();
2039        assert_eq!(n, 2); // windows starting at 2 and 4
2040    }
2041
2042    // ── Index normalization ────────────────────────────────────────────
2043
2044    #[test]
2045    fn index_from_basic() {
2046        let r = [10, 20, 30].circular();
2047        assert_eq!(r.index_from(0), 0);
2048        assert_eq!(r.index_from(2), 2);
2049        assert_eq!(r.index_from(3), 0);
2050        assert_eq!(r.index_from(7), 1);
2051        assert_eq!(r.index_from(-1), 2);
2052        assert_eq!(r.index_from(-4), 2);
2053    }
2054
2055    #[test]
2056    #[should_panic]
2057    fn index_from_empty_panics() {
2058        let empty: [i32; 0] = [];
2059        let _ = empty.circular().index_from(0);
2060    }
2061
2062    // ── Comparison predicates ──────────────────────────────────────────
2063
2064    #[test]
2065    fn is_rotation_of_true() {
2066        let a = [1, 2, 3, 4];
2067        let b = [3, 4, 1, 2];
2068        assert!(a.circular().is_rotation_of(&b));
2069    }
2070
2071    #[test]
2072    fn is_rotation_of_false() {
2073        let a = [1, 2, 3, 4];
2074        let b = [4, 3, 2, 1];
2075        assert!(!a.circular().is_rotation_of(&b));
2076    }
2077
2078    #[test]
2079    fn is_rotation_of_empty() {
2080        let a: [i32; 0] = [];
2081        let b: [i32; 0] = [];
2082        assert!(a.circular().is_rotation_of(&b));
2083    }
2084
2085    #[test]
2086    fn is_rotation_of_length_mismatch() {
2087        assert!(![1, 2].circular().is_rotation_of(&[1, 2, 3]));
2088    }
2089
2090    #[test]
2091    fn is_reflection_of_basic() {
2092        let a = [1, 2, 3, 4];
2093        assert!(a.circular().is_reflection_of(&[1, 4, 3, 2]));
2094        assert!(a.circular().is_reflection_of(&[1, 2, 3, 4]));
2095        assert!(!a.circular().is_reflection_of(&[2, 1, 4, 3]));
2096    }
2097
2098    #[test]
2099    fn is_reversion_of_basic() {
2100        let a = [1, 2, 3, 4];
2101        assert!(a.circular().is_reversion_of(&[1, 2, 3, 4]));
2102        assert!(a.circular().is_reversion_of(&[4, 3, 2, 1]));
2103        assert!(!a.circular().is_reversion_of(&[3, 2, 1, 4]));
2104    }
2105
2106    #[test]
2107    fn is_rotation_or_reflection_of_basic() {
2108        let a = [1, 2, 3, 4];
2109        assert!(a.circular().is_rotation_or_reflection_of(&[3, 4, 1, 2])); // rotation
2110        assert!(a.circular().is_rotation_or_reflection_of(&[2, 1, 4, 3])); // rotation of reflection
2111        assert!(!a.circular().is_rotation_or_reflection_of(&[1, 3, 2, 4])); // neither
2112    }
2113
2114    #[test]
2115    fn rotation_offset_basic() {
2116        let a = [10, 20, 30, 40];
2117        assert_eq!(a.circular().rotation_offset(&[30, 40, 10, 20]), Some(2));
2118        assert_eq!(a.circular().rotation_offset(&[10, 20, 30, 40]), Some(0));
2119        assert_eq!(a.circular().rotation_offset(&[1, 2, 3, 4]), None);
2120    }
2121
2122    #[test]
2123    fn rotation_offset_length_mismatch() {
2124        assert_eq!([1, 2, 3].circular().rotation_offset(&[1, 2]), None);
2125    }
2126
2127    // ── Distance ───────────────────────────────────────────────────────
2128
2129    #[test]
2130    fn hamming_distance_basic() {
2131        let a = [1, 2, 3, 4];
2132        let b = [1, 2, 0, 4];
2133        assert_eq!(a.circular().hamming_distance(&b), 1);
2134        assert_eq!(a.circular().hamming_distance(&a), 0);
2135    }
2136
2137    #[test]
2138    #[should_panic]
2139    fn hamming_distance_length_mismatch_panics() {
2140        let _ = [1, 2].circular().hamming_distance(&[1, 2, 3]);
2141    }
2142
2143    #[test]
2144    fn min_rotational_hamming_distance_basic() {
2145        let a = [1, 2, 3, 4];
2146        let b = [3, 4, 1, 2]; // rotation of a
2147        assert_eq!(a.circular().min_rotational_hamming_distance(&b), 0);
2148        assert_eq!(a.circular().min_rotational_hamming_distance(&a), 0);
2149        let c = [3, 4, 1, 5]; // 1 off from a rotation
2150        assert_eq!(a.circular().min_rotational_hamming_distance(&c), 1);
2151    }
2152
2153    // ── contains_slice / index_of_slice ────────────────────────────────
2154
2155    #[test]
2156    fn contains_slice_basic() {
2157        let r = [1, 2, 3, 4, 5].circular();
2158        assert!(r.contains_slice(&[2, 3, 4]));
2159        assert!(r.contains_slice(&[5, 1, 2])); // wraps
2160        assert!(!r.contains_slice(&[2, 4]));
2161    }
2162
2163    #[test]
2164    fn contains_slice_empty() {
2165        assert!([1, 2, 3].circular().contains_slice(&[]));
2166        let empty: [i32; 0] = [];
2167        assert!(!empty.circular().contains_slice(&[1]));
2168        assert!(empty.circular().contains_slice(&[]));
2169    }
2170
2171    #[test]
2172    fn index_of_slice_basic() {
2173        let r = [1, 2, 3, 4, 5].circular();
2174        assert_eq!(r.index_of_slice(&[3, 4], 0), Some(2));
2175        assert_eq!(r.index_of_slice(&[5, 1], 0), Some(4)); // wraps
2176        assert_eq!(r.index_of_slice(&[9], 0), None);
2177    }
2178
2179    // Positions returned (and searched from) are view positions, not
2180    // underlying-slice indices. Regression tests: the original
2181    // implementation mapped `from` into the underlying frame and so
2182    // double-applied the offset on any transformed view.
2183
2184    #[test]
2185    fn index_of_slice_on_rotated_view() {
2186        // view = [1, 1, 0, 0]
2187        let v = [0, 1, 1, 0].circular().rotate_left(1);
2188        assert_eq!(v.index_of_slice(&[1], 0), Some(0));
2189        assert_eq!(v.index_of_slice(&[1], 1), Some(1));
2190        assert_eq!(v.index_of_slice(&[1], 2), Some(0)); // wraps past the seam
2191        assert_eq!(v.index_of_slice(&[0, 0], 0), Some(2));
2192        assert_eq!(v.index_of_slice(&[0, 1], 0), Some(3)); // wrapping match
2193    }
2194
2195    #[test]
2196    fn index_of_slice_on_reflected_view() {
2197        // reflect_at(0) of [10, 20, 30, 40] = [10, 40, 30, 20]
2198        let v = [10, 20, 30, 40].circular().reflect_at(0);
2199        assert_eq!(v.index_of_slice(&[40, 30], 0), Some(1));
2200        assert_eq!(v.index_of_slice(&[20], 0), Some(3));
2201        assert_eq!(v.index_of_slice(&[20, 10], 0), Some(3)); // wrapping match
2202    }
2203
2204    #[test]
2205    fn index_of_slice_empty_needle_returns_view_position() {
2206        let v = [10, 20, 30, 40, 50].circular().rotate_left(2);
2207        assert_eq!(v.index_of_slice(&[], 0), Some(0));
2208        assert_eq!(v.index_of_slice(&[], 7), Some(2));
2209        assert_eq!(v.index_of_slice(&[], -1), Some(4));
2210        let empty: [i32; 0] = [];
2211        assert_eq!(empty.circular().index_of_slice(&[], 3), Some(0));
2212        assert_eq!(empty.circular().index_of_slice(&[1], 0), None);
2213    }
2214
2215    // ── Symmetry counts ────────────────────────────────────────────────
2216
2217    #[test]
2218    fn rotational_symmetry_basic() {
2219        assert_eq!([1, 2, 3, 4].circular().rotational_symmetry(), 1);
2220        assert_eq!([1, 2, 1, 2].circular().rotational_symmetry(), 2);
2221        assert_eq!([1, 1, 1, 1].circular().rotational_symmetry(), 4);
2222    }
2223
2224    #[test]
2225    fn rotational_symmetry_short() {
2226        let empty: [i32; 0] = [];
2227        assert_eq!(empty.circular().rotational_symmetry(), 1);
2228        assert_eq!([5].circular().rotational_symmetry(), 1);
2229    }
2230
2231    #[test]
2232    fn symmetry_palindrome() {
2233        // Palindrome has at least one reflectional symmetry axis.
2234        assert!([1, 2, 3, 2, 1].circular().symmetry() >= 1);
2235    }
2236
2237    #[test]
2238    fn symmetry_empty() {
2239        let empty: [i32; 0] = [];
2240        assert_eq!(empty.circular().symmetry(), 0);
2241    }
2242
2243    // ── canonical_index (alloc-free) ───────────────────────────────────
2244
2245    #[test]
2246    fn canonical_index_basic() {
2247        assert_eq!([3, 1, 2].circular().canonical_index(), 1);
2248        assert_eq!([1, 2, 3].circular().canonical_index(), 0);
2249        assert_eq!([2, 3, 0, 1].circular().canonical_index(), 2);
2250    }
2251
2252    #[test]
2253    fn canonical_index_short() {
2254        let empty: [i32; 0] = [];
2255        assert_eq!(empty.circular().canonical_index(), 0);
2256        assert_eq!([7].circular().canonical_index(), 0);
2257    }
2258
2259    #[test]
2260    fn canonical_index_periodic_returns_first() {
2261        // Ties between periodic rotations resolve to the smallest offset.
2262        assert_eq!([3, 1, 2, 3, 1, 2].circular().canonical_index(), 1);
2263        assert_eq!([5, 5, 5].circular().canonical_index(), 0);
2264        assert_eq!([1, 0, 1, 0].circular().canonical_index(), 1);
2265    }
2266
2267    #[test]
2268    fn canonical_index_on_transformed_views() {
2269        let r = [2, 3, 0, 1].circular();
2270        // Rotating the view moves the minimal rotation accordingly.
2271        assert_eq!(r.rotate_left(2).canonical_index(), 0);
2272        // reflect_at(0) of [2,3,0,1] = [2,1,0,3]; min rotation [0,3,2,1] at 2.
2273        assert_eq!(r.reflect_at(0).canonical_index(), 2);
2274    }
2275
2276    // ── get / Index ────────────────────────────────────────────────────
2277
2278    #[test]
2279    fn get_basic_and_empty() {
2280        let r = [10, 20, 30].circular();
2281        assert_eq!(r.get(4), Some(&20));
2282        assert_eq!(r.get(-1), Some(&30));
2283        let empty: [i32; 0] = [];
2284        assert_eq!(empty.circular().get(0), None);
2285    }
2286
2287    #[test]
2288    fn index_operator_wraps() {
2289        let r = [10, 20, 30].circular();
2290        assert_eq!(r[0], 10);
2291        assert_eq!(r[4], 20);
2292        assert_eq!(r[-1], 30);
2293        assert_eq!(r.reflect_at(0)[1], 30);
2294    }
2295
2296    #[test]
2297    #[should_panic]
2298    fn index_operator_panics_on_empty() {
2299        let empty: [i32; 0] = [];
2300        let _ = empty.circular()[0];
2301    }
2302
2303    // ── IntoIterator ───────────────────────────────────────────────────
2304
2305    #[test]
2306    fn into_iterator_by_value_and_by_ref() {
2307        let r = [1, 2, 3].circular();
2308        let mut sum = 0;
2309        for &x in r {
2310            sum += x;
2311        }
2312        assert_eq!(sum, 6);
2313        // By reference too; the view is Copy so it is still usable after.
2314        let mut prod = 1;
2315        for &x in &r {
2316            prod *= x;
2317        }
2318        assert_eq!(prod, 6);
2319        assert_eq!(r.len(), 3);
2320    }
2321
2322    // ── CircularIter: double-ended + overrides ─────────────────────────
2323
2324    #[test]
2325    fn iter_rev_walks_backward() {
2326        let r = [1, 2, 3, 4].circular();
2327        let mut out = [0; 4];
2328        for (s, &x) in out.iter_mut().zip(r.iter().rev()) {
2329            *s = x;
2330        }
2331        assert_eq!(out, [4, 3, 2, 1]);
2332        // On a transformed view too: rotate_left(1) = [2, 3, 4, 1].
2333        for (s, &x) in out.iter_mut().zip(r.rotate_left(1).iter().rev()) {
2334            *s = x;
2335        }
2336        assert_eq!(out, [1, 4, 3, 2]);
2337    }
2338
2339    #[test]
2340    fn iter_mixed_front_and_back() {
2341        let r = [1, 2, 3, 4, 5].circular();
2342        let mut it = r.iter();
2343        assert_eq!(it.next(), Some(&1));
2344        assert_eq!(it.next_back(), Some(&5));
2345        assert_eq!(it.next(), Some(&2));
2346        assert_eq!(it.next_back(), Some(&4));
2347        assert_eq!(it.next(), Some(&3));
2348        assert_eq!(it.next(), None);
2349        assert_eq!(it.next_back(), None);
2350    }
2351
2352    #[test]
2353    fn iter_rev_on_wrapping_window() {
2354        // Windows longer than the ring wrap; rev must see the same elements.
2355        let r = [1, 2, 3].circular();
2356        let w = r.windows(5).next().unwrap();
2357        let mut out = [0; 5];
2358        for (s, &x) in out.iter_mut().zip(w.rev()) {
2359            *s = x;
2360        }
2361        assert_eq!(out, [2, 1, 3, 2, 1]);
2362    }
2363
2364    #[test]
2365    fn iter_nth_count_last() {
2366        let r = [10, 20, 30, 40, 50].circular();
2367        assert_eq!(r.iter().nth(1), Some(&20));
2368        assert_eq!(r.iter().nth(3), Some(&40));
2369        assert_eq!(r.iter().nth(5), None);
2370        let mut it = r.iter();
2371        assert_eq!(it.nth(2), Some(&30));
2372        assert_eq!(it.next(), Some(&40)); // nth consumed 0..=2
2373        assert_eq!(it.count(), 1);
2374        assert_eq!(r.iter().count(), 5);
2375        assert_eq!(r.iter().last(), Some(&50));
2376        assert_eq!(r.slice(0, 0).last(), None);
2377    }
2378}
2379
2380#[cfg(all(test, feature = "alloc"))]
2381mod alloc_tests {
2382    use super::*;
2383    use alloc::vec;
2384    use alloc::vec::Vec;
2385
2386    // ── Canonical (Vec-returning) ──────────────────────────────────────
2387
2388    #[test]
2389    fn canonical_basic() {
2390        assert_eq!([3, 1, 2].circular().canonical(), vec![1, 2, 3]);
2391        assert_eq!([1, 2, 3].circular().canonical(), vec![1, 2, 3]);
2392    }
2393
2394    #[test]
2395    fn canonical_all_equal() {
2396        assert_eq!([5, 5, 5].circular().canonical(), vec![5, 5, 5]);
2397    }
2398
2399    #[test]
2400    fn canonical_rotations_share_canonical() {
2401        let a = [3, 1, 2];
2402        let b = [1, 2, 3];
2403        let c = [2, 3, 1];
2404        assert_eq!(a.circular().canonical(), b.circular().canonical());
2405        assert_eq!(b.circular().canonical(), c.circular().canonical());
2406    }
2407
2408    #[test]
2409    fn bracelet_basic() {
2410        // Bracelet treats sequence and its reflection as equivalent.
2411        let a = [3, 1, 2];
2412        let b = [3, 2, 1]; // reflection of a at index 0 = [3, 2, 1]
2413        assert_eq!(a.circular().bracelet(), b.circular().bracelet());
2414    }
2415
2416    // ── Symmetry indices / axes ────────────────────────────────────────
2417
2418    #[test]
2419    fn symmetry_indices_palindrome() {
2420        let r = [1, 2, 3, 2, 1].circular();
2421        assert!(!r.symmetry_indices().is_empty());
2422    }
2423
2424    #[test]
2425    fn symmetry_indices_none() {
2426        let r = [1, 2, 3, 4].circular();
2427        // Asymmetric ring has no reflectional symmetries
2428        assert_eq!(r.symmetry_indices(), Vec::<usize>::new());
2429    }
2430
2431    #[test]
2432    fn symmetry_indices_empty() {
2433        let empty: [i32; 0] = [];
2434        assert_eq!(empty.circular().symmetry_indices(), Vec::<usize>::new());
2435    }
2436
2437    #[test]
2438    fn reflectional_symmetry_axes_count_matches_symmetry() {
2439        let r = [1, 2, 1, 2].circular();
2440        let axes = r.reflectional_symmetry_axes();
2441        assert_eq!(axes.len(), r.symmetry());
2442    }
2443
2444    #[test]
2445    fn reflectional_symmetry_axes_square_geometry() {
2446        use crate::AxisLocation::{Edge, Vertex};
2447        // A square of equal elements has all four axes of the dihedral
2448        // group: two vertex-vertex, two edge-edge.
2449        let axes = [7, 7, 7, 7].circular().reflectional_symmetry_axes();
2450        assert_eq!(axes.len(), 4);
2451        assert!(axes.contains(&(Vertex(0), Vertex(2))));
2452        assert!(axes.contains(&(Vertex(1), Vertex(3))));
2453        assert!(axes.contains(&(Edge(0, 1), Edge(2, 3))));
2454        assert!(axes.contains(&(Edge(1, 2), Edge(3, 0))));
2455    }
2456
2457    #[test]
2458    fn reflectional_symmetry_axes_odd_palindrome_geometry() {
2459        use crate::AxisLocation::{Edge, Vertex};
2460        // Pentagon [1,2,3,2,1]: the single axis passes through the `3`
2461        // at vertex 2 and the midpoint of the opposite edge (4, 0).
2462        let axes = [1, 2, 3, 2, 1].circular().reflectional_symmetry_axes();
2463        assert_eq!(axes, vec![(Vertex(2), Edge(4, 0))]);
2464    }
2465
2466    #[test]
2467    fn reflectional_symmetry_axes_vertex_only_geometry() {
2468        use crate::AxisLocation::Vertex;
2469        // [0,1,0,1]: only the two vertex-vertex axes survive the labels
2470        // (edge axes would swap a 0 with a 1).
2471        let axes = [0, 1, 0, 1].circular().reflectional_symmetry_axes();
2472        assert_eq!(axes, vec![(Vertex(1), Vertex(3)), (Vertex(0), Vertex(2))]);
2473    }
2474
2475    #[test]
2476    fn reflectional_symmetry_axes_edge_only_geometry() {
2477        use crate::AxisLocation::Edge;
2478        // [1,1,2,2]: one axis, through the midpoints of the edges that
2479        // join the equal pairs.
2480        let axes = [1, 1, 2, 2].circular().reflectional_symmetry_axes();
2481        assert_eq!(axes, vec![(Edge(0, 1), Edge(2, 3))]);
2482    }
2483
2484    // ── to_vec ─────────────────────────────────────────────────────────
2485
2486    #[test]
2487    fn to_vec_basic() {
2488        let r = [10, 20, 30].circular();
2489        assert_eq!(r.to_vec(), vec![10, 20, 30]);
2490        assert_eq!(r.rotate_right(1).to_vec(), vec![30, 10, 20]);
2491        assert_eq!(r.reflect_at(0).to_vec(), vec![10, 30, 20]);
2492    }
2493
2494    #[test]
2495    fn to_vec_empty() {
2496        let empty: [i32; 0] = [];
2497        assert_eq!(empty.circular().to_vec(), Vec::<i32>::new());
2498    }
2499
2500    // ── Chained: prototype's headline example ──────────────────────────
2501
2502    #[test]
2503    fn rotations_map_canonical_index() {
2504        let r = [3, 1, 2, 3, 1, 2].circular();
2505        let indices: Vec<usize> = r.rotations().map(|v| v.canonical_index()).collect();
2506        assert_eq!(indices, vec![1, 0, 2, 1, 0, 2]);
2507    }
2508}