1use core::iter::FusedIterator;
10
11#[cfg(feature = "alloc")]
12use alloc::vec::Vec;
13
14#[cfg(feature = "alloc")]
15use crate::AxisLocation;
16
17pub struct Circular<'a, T> {
36 ring: &'a [T],
37 offset: usize,
38 reflected: bool,
39}
40
41impl<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 #[inline]
64 pub(crate) fn new(ring: &'a [T]) -> Self {
65 Circular {
66 ring,
67 offset: 0,
68 reflected: false,
69 }
70 }
71
72 #[inline]
74 #[must_use]
75 pub fn len(self) -> usize {
76 self.ring.len()
77 }
78
79 #[inline]
81 #[must_use]
82 pub fn is_empty(self) -> bool {
83 self.ring.is_empty()
84 }
85
86 #[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 #[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 #[inline]
142 #[must_use]
143 pub fn iter(self) -> CircularIter<'a, T> {
144 CircularIter {
145 view: self,
146 pos: 0,
147 remaining: self.ring.len(),
148 }
149 }
150
151 #[must_use]
170 pub fn start_at(self, i: isize) -> Circular<'a, T> {
171 let n = self.ring.len();
172 if n == 0 {
173 return self;
174 }
175 let i_pos = i.rem_euclid(n as isize) as usize;
176 let new_offset = if self.reflected {
177 (self.offset + n - i_pos) % n
178 } else {
179 (self.offset + i_pos) % n
180 };
181 Circular {
182 ring: self.ring,
183 offset: new_offset,
184 reflected: self.reflected,
185 }
186 }
187
188 #[inline]
204 #[must_use]
205 pub fn rotate_left(self, step: isize) -> Circular<'a, T> {
206 self.start_at(step)
207 }
208
209 #[inline]
225 #[must_use]
226 pub fn rotate_right(self, step: isize) -> Circular<'a, T> {
227 self.start_at(-step)
228 }
229
230 #[must_use]
246 pub fn reflect_at(self, i: isize) -> Circular<'a, T> {
247 let n = self.ring.len();
248 if n == 0 {
249 return self;
250 }
251 let i_pos = i.rem_euclid(n as isize) as usize;
252 let new_offset = if self.reflected {
253 (self.offset + n - i_pos) % n
254 } else {
255 (self.offset + i_pos) % n
256 };
257 Circular {
258 ring: self.ring,
259 offset: new_offset,
260 reflected: !self.reflected,
261 }
262 }
263
264 #[must_use]
285 pub fn slice(self, from: isize, to: isize) -> CircularIter<'a, T> {
286 if self.ring.is_empty() || to <= from {
287 return CircularIter {
288 view: self,
289 pos: 0,
290 remaining: 0,
291 };
292 }
293 let count = (to - from) as usize;
294 let started = self.start_at(from);
295 CircularIter {
296 view: started,
297 pos: 0,
298 remaining: count,
299 }
300 }
301
302 pub fn take_while<P>(self, mut pred: P, from: isize) -> impl Iterator<Item = &'a T>
316 where
317 P: FnMut(&T) -> bool,
318 {
319 self.start_at(from).iter().take_while(move |x| pred(*x))
320 }
321
322 pub fn drop_while<P>(self, mut pred: P, from: isize) -> impl Iterator<Item = &'a T>
336 where
337 P: FnMut(&T) -> bool,
338 {
339 self.start_at(from).iter().skip_while(move |x| pred(*x))
340 }
341
342 #[must_use]
359 pub fn enumerate(self, from: isize) -> Enumerate<'a, T> {
360 let started = if self.ring.is_empty() {
361 self
362 } else {
363 self.start_at(from)
364 };
365 Enumerate {
366 view: started,
367 pos: 0,
368 remaining: started.ring.len(),
369 }
370 }
371
372 #[must_use]
393 pub fn rotations(self) -> Rotations<'a, T> {
394 let total = if self.ring.is_empty() {
395 1
396 } else {
397 self.ring.len()
398 };
399 Rotations {
400 base: self,
401 index: 0,
402 total,
403 }
404 }
405
406 #[must_use]
422 pub fn reflections(self) -> Reflections<'a, T> {
423 Reflections {
424 base: self,
425 state: 0,
426 }
427 }
428
429 #[must_use]
450 pub fn reversions(self) -> Reversions<'a, T> {
451 Reversions {
452 base: self,
453 state: 0,
454 }
455 }
456
457 #[must_use]
472 pub fn rotations_and_reflections(self) -> RotationsAndReflections<'a, T> {
473 let n = self.ring.len();
474 let total = if n == 0 { 1 } else { 2 * n };
475 let reflected = if n == 0 { self } else { self.reflect_at(0) };
476 RotationsAndReflections {
477 base: self,
478 reflected,
479 index: 0,
480 total,
481 n,
482 }
483 }
484
485 #[must_use]
508 pub fn windows(self, size: usize) -> Windows<'a, T> {
509 assert!(size > 0, "window size must be positive");
510 let total = if self.ring.is_empty() {
511 0
512 } else {
513 self.ring.len()
514 };
515 Windows {
516 base: self,
517 size,
518 step: 1,
519 index: 0,
520 total,
521 }
522 }
523
524 #[must_use]
547 pub fn index_from(self, i: isize) -> usize {
548 let n = self.ring.len();
549 assert!(n > 0, "cannot normalize against an empty ring");
550 let pos = i.rem_euclid(n as isize) as usize;
551 self.map_index(pos)
552 }
553
554 #[must_use]
560 pub fn is_rotation_of(self, other: &[T]) -> bool
561 where
562 T: PartialEq,
563 {
564 if self.len() != other.len() {
565 return false;
566 }
567 if self.ring.is_empty() {
568 return true;
569 }
570 self.rotations().any(|rot| rot.iter().eq(other.iter()))
571 }
572
573 #[must_use]
576 pub fn is_reflection_of(self, other: &[T]) -> bool
577 where
578 T: PartialEq,
579 {
580 if self.len() != other.len() {
581 return false;
582 }
583 self.iter().eq(other.iter()) || self.reflect_at(0).iter().eq(other.iter())
584 }
585
586 #[must_use]
588 pub fn is_reversion_of(self, other: &[T]) -> bool
589 where
590 T: PartialEq,
591 {
592 if self.len() != other.len() {
593 return false;
594 }
595 self.iter().eq(other.iter()) || self.reflect_at(-1).iter().eq(other.iter())
596 }
597
598 #[must_use]
601 pub fn is_rotation_or_reflection_of(self, other: &[T]) -> bool
602 where
603 T: PartialEq,
604 {
605 if self.len() != other.len() {
606 return false;
607 }
608 self.rotations_and_reflections()
609 .any(|v| v.iter().eq(other.iter()))
610 }
611
612 #[must_use]
615 pub fn rotation_offset(self, other: &[T]) -> Option<usize>
616 where
617 T: PartialEq,
618 {
619 if self.len() != other.len() {
620 return None;
621 }
622 if self.ring.is_empty() {
623 return Some(0);
624 }
625 let n = self.ring.len();
626 (0..n).find(|&i| self.start_at(i as isize).iter().eq(other.iter()))
627 }
628
629 #[must_use]
635 pub fn hamming_distance(self, other: &[T]) -> usize
636 where
637 T: PartialEq,
638 {
639 assert_eq!(self.len(), other.len(), "sequences must have the same size");
640 self.iter()
641 .zip(other.iter())
642 .filter(|(a, b)| a != b)
643 .count()
644 }
645
646 #[must_use]
653 pub fn min_rotational_hamming_distance(self, other: &[T]) -> usize
654 where
655 T: PartialEq,
656 {
657 assert_eq!(self.len(), other.len(), "sequences must have the same size");
658 let n = self.ring.len();
659 if n == 0 {
660 return 0;
661 }
662 let mut best = usize::MAX;
663 for rot in self.rotations() {
664 let count = rot.iter().zip(other.iter()).filter(|(a, b)| a != b).count();
665 if count < best {
666 best = count;
667 }
668 if best == 0 {
669 break;
670 }
671 }
672 best
673 }
674
675 #[must_use]
680 pub fn contains_slice(self, needle: &[T]) -> bool
681 where
682 T: PartialEq,
683 {
684 if needle.is_empty() {
685 return true;
686 }
687 if self.ring.is_empty() {
688 return false;
689 }
690 let n = self.ring.len();
691 let m = needle.len();
692 (0..n).any(|start| (0..m).all(|j| *self.apply((start + j) as isize) == needle[j]))
693 }
694
695 #[must_use]
698 pub fn index_of_slice(self, needle: &[T], from: isize) -> Option<usize>
699 where
700 T: PartialEq,
701 {
702 if needle.is_empty() {
703 return if self.ring.is_empty() {
704 Some(0)
705 } else {
706 Some(self.index_from(from))
707 };
708 }
709 if self.ring.is_empty() {
710 return None;
711 }
712 let n = self.ring.len();
713 let m = needle.len();
714 let start = self.index_from(from);
715 (0..n)
716 .map(|k| (start + k) % n)
717 .find(|&i| (0..m).all(|j| *self.apply((i + j) as isize) == needle[j]))
718 }
719
720 #[must_use]
729 pub fn rotational_symmetry(self) -> usize
730 where
731 T: PartialEq,
732 {
733 let n = self.ring.len();
734 if n < 2 {
735 return 1;
736 }
737 let smallest = (1..=n).find(|&d| {
738 n % d == 0
739 && (0..n - d).all(|i| *self.apply(i as isize) == *self.apply((i + d) as isize))
740 });
741 n / smallest.unwrap_or(n)
742 }
743
744 #[must_use]
751 pub fn symmetry(self) -> usize
752 where
753 T: PartialEq,
754 {
755 let n = self.ring.len();
756 if n == 0 {
757 return 0;
758 }
759 let reversed = self.reflect_at(-1);
760 (0..n)
761 .filter(|&shift| {
762 (0..n).all(|i| *self.apply(i as isize) == *reversed.apply((i + shift) as isize))
763 })
764 .count()
765 }
766
767 #[must_use]
792 pub fn chunks(self, size: usize) -> Windows<'a, T> {
793 assert!(size > 0, "chunk size must be positive");
794 let n = self.ring.len();
795 let total = if n == 0 { 0 } else { (n + size - 1) / size };
796 Windows {
797 base: self,
798 size,
799 step: size,
800 index: 0,
801 total,
802 }
803 }
804}
805
806#[derive(Debug)]
815pub struct CircularIter<'a, T> {
816 view: Circular<'a, T>,
817 pos: usize,
818 remaining: usize,
819}
820
821impl<T> Clone for CircularIter<'_, T> {
822 fn clone(&self) -> Self {
823 CircularIter {
824 view: self.view,
825 pos: self.pos,
826 remaining: self.remaining,
827 }
828 }
829}
830
831impl<'a, T> Iterator for CircularIter<'a, T> {
832 type Item = &'a T;
833
834 #[inline]
835 fn next(&mut self) -> Option<&'a T> {
836 if self.remaining == 0 {
837 return None;
838 }
839 let item = &self.view.ring[self.view.map_index(self.pos)];
840 self.pos += 1;
841 self.remaining -= 1;
842 Some(item)
843 }
844
845 #[inline]
846 fn size_hint(&self) -> (usize, Option<usize>) {
847 (self.remaining, Some(self.remaining))
848 }
849}
850
851impl<T> ExactSizeIterator for CircularIter<'_, T> {}
852impl<T> FusedIterator for CircularIter<'_, T> {}
853
854#[derive(Debug)]
864pub struct Enumerate<'a, T> {
865 view: Circular<'a, T>,
866 pos: usize,
867 remaining: usize,
868}
869
870impl<T> Clone for Enumerate<'_, T> {
871 fn clone(&self) -> Self {
872 Enumerate {
873 view: self.view,
874 pos: self.pos,
875 remaining: self.remaining,
876 }
877 }
878}
879
880impl<'a, T> Iterator for Enumerate<'a, T> {
881 type Item = (&'a T, usize);
882
883 #[inline]
884 fn next(&mut self) -> Option<Self::Item> {
885 if self.remaining == 0 {
886 return None;
887 }
888 let idx = self.view.map_index(self.pos);
889 let item = &self.view.ring[idx];
890 self.pos += 1;
891 self.remaining -= 1;
892 Some((item, idx))
893 }
894
895 #[inline]
896 fn size_hint(&self) -> (usize, Option<usize>) {
897 (self.remaining, Some(self.remaining))
898 }
899}
900
901impl<T> ExactSizeIterator for Enumerate<'_, T> {}
902impl<T> FusedIterator for Enumerate<'_, T> {}
903
904#[derive(Debug)]
913pub struct Rotations<'a, T> {
914 base: Circular<'a, T>,
915 index: usize,
916 total: usize,
917}
918
919impl<T> Clone for Rotations<'_, T> {
920 fn clone(&self) -> Self {
921 Rotations {
922 base: self.base,
923 index: self.index,
924 total: self.total,
925 }
926 }
927}
928
929impl<'a, T> Iterator for Rotations<'a, T> {
930 type Item = Circular<'a, T>;
931
932 fn next(&mut self) -> Option<Self::Item> {
933 if self.index >= self.total {
934 return None;
935 }
936 let item = if self.base.ring.is_empty() {
937 self.base
938 } else {
939 self.base.start_at(self.index as isize)
940 };
941 self.index += 1;
942 Some(item)
943 }
944
945 fn size_hint(&self) -> (usize, Option<usize>) {
946 let r = self.total - self.index;
947 (r, Some(r))
948 }
949}
950
951impl<T> ExactSizeIterator for Rotations<'_, T> {}
952impl<T> FusedIterator for Rotations<'_, T> {}
953
954#[derive(Debug)]
964pub struct Reflections<'a, T> {
965 base: Circular<'a, T>,
966 state: u8,
967}
968
969impl<T> Clone for Reflections<'_, T> {
970 fn clone(&self) -> Self {
971 Reflections {
972 base: self.base,
973 state: self.state,
974 }
975 }
976}
977
978impl<'a, T> Iterator for Reflections<'a, T> {
979 type Item = Circular<'a, T>;
980
981 fn next(&mut self) -> Option<Self::Item> {
982 match self.state {
983 0 => {
984 self.state = if self.base.ring.is_empty() { 2 } else { 1 };
985 Some(self.base)
986 }
987 1 => {
988 self.state = 2;
989 Some(self.base.reflect_at(0))
990 }
991 _ => None,
992 }
993 }
994
995 fn size_hint(&self) -> (usize, Option<usize>) {
996 let r = match self.state {
997 0 => {
998 if self.base.ring.is_empty() {
999 1
1000 } else {
1001 2
1002 }
1003 }
1004 1 => 1,
1005 _ => 0,
1006 };
1007 (r, Some(r))
1008 }
1009}
1010
1011impl<T> ExactSizeIterator for Reflections<'_, T> {}
1012impl<T> FusedIterator for Reflections<'_, T> {}
1013
1014#[derive(Debug)]
1023pub struct Reversions<'a, T> {
1024 base: Circular<'a, T>,
1025 state: u8,
1026}
1027
1028impl<T> Clone for Reversions<'_, T> {
1029 fn clone(&self) -> Self {
1030 Reversions {
1031 base: self.base,
1032 state: self.state,
1033 }
1034 }
1035}
1036
1037impl<'a, T> Iterator for Reversions<'a, T> {
1038 type Item = Circular<'a, T>;
1039
1040 fn next(&mut self) -> Option<Self::Item> {
1041 match self.state {
1042 0 => {
1043 self.state = if self.base.ring.is_empty() { 2 } else { 1 };
1044 Some(self.base)
1045 }
1046 1 => {
1047 self.state = 2;
1048 Some(self.base.reflect_at(-1))
1049 }
1050 _ => None,
1051 }
1052 }
1053
1054 fn size_hint(&self) -> (usize, Option<usize>) {
1055 let r = match self.state {
1056 0 => {
1057 if self.base.ring.is_empty() {
1058 1
1059 } else {
1060 2
1061 }
1062 }
1063 1 => 1,
1064 _ => 0,
1065 };
1066 (r, Some(r))
1067 }
1068}
1069
1070impl<T> ExactSizeIterator for Reversions<'_, T> {}
1071impl<T> FusedIterator for Reversions<'_, T> {}
1072
1073#[derive(Debug)]
1082pub struct RotationsAndReflections<'a, T> {
1083 base: Circular<'a, T>,
1084 reflected: Circular<'a, T>,
1085 index: usize,
1086 total: usize,
1087 n: usize,
1088}
1089
1090impl<T> Clone for RotationsAndReflections<'_, T> {
1091 fn clone(&self) -> Self {
1092 RotationsAndReflections {
1093 base: self.base,
1094 reflected: self.reflected,
1095 index: self.index,
1096 total: self.total,
1097 n: self.n,
1098 }
1099 }
1100}
1101
1102impl<'a, T> Iterator for RotationsAndReflections<'a, T> {
1103 type Item = Circular<'a, T>;
1104
1105 fn next(&mut self) -> Option<Self::Item> {
1106 if self.index >= self.total {
1107 return None;
1108 }
1109 if self.n == 0 {
1110 self.index += 1;
1111 return Some(self.base);
1112 }
1113 let item = if self.index < self.n {
1114 self.base.start_at(self.index as isize)
1115 } else {
1116 self.reflected.start_at((self.index - self.n) as isize)
1117 };
1118 self.index += 1;
1119 Some(item)
1120 }
1121
1122 fn size_hint(&self) -> (usize, Option<usize>) {
1123 let r = self.total - self.index;
1124 (r, Some(r))
1125 }
1126}
1127
1128impl<T> ExactSizeIterator for RotationsAndReflections<'_, T> {}
1129impl<T> FusedIterator for RotationsAndReflections<'_, T> {}
1130
1131#[derive(Debug)]
1141pub struct Windows<'a, T> {
1142 base: Circular<'a, T>,
1143 size: usize,
1144 step: usize,
1145 index: usize,
1146 total: usize,
1147}
1148
1149impl<T> Clone for Windows<'_, T> {
1150 fn clone(&self) -> Self {
1151 Windows {
1152 base: self.base,
1153 size: self.size,
1154 step: self.step,
1155 index: self.index,
1156 total: self.total,
1157 }
1158 }
1159}
1160
1161impl<'a, T> Iterator for Windows<'a, T> {
1162 type Item = CircularIter<'a, T>;
1163
1164 fn next(&mut self) -> Option<Self::Item> {
1165 if self.index >= self.total {
1166 return None;
1167 }
1168 let start = (self.index * self.step) as isize;
1169 let view = self.base.start_at(start);
1170 let iter = CircularIter {
1171 view,
1172 pos: 0,
1173 remaining: self.size,
1174 };
1175 self.index += 1;
1176 Some(iter)
1177 }
1178
1179 fn size_hint(&self) -> (usize, Option<usize>) {
1180 let r = self.total - self.index;
1181 (r, Some(r))
1182 }
1183}
1184
1185impl<T> ExactSizeIterator for Windows<'_, T> {}
1186impl<T> FusedIterator for Windows<'_, T> {}
1187
1188pub trait AsCircular<T> {
1207 fn circular(&self) -> Circular<'_, T>;
1209}
1210
1211impl<T> AsCircular<T> for [T] {
1212 #[inline]
1213 fn circular(&self) -> Circular<'_, T> {
1214 Circular::new(self)
1215 }
1216}
1217
1218#[cfg(feature = "alloc")]
1224impl<'a, T> Circular<'a, T> {
1225 #[must_use]
1227 pub fn to_vec(self) -> Vec<T>
1228 where
1229 T: Clone,
1230 {
1231 self.iter().cloned().collect()
1232 }
1233
1234 #[must_use]
1239 pub fn canonical_index(self) -> usize
1240 where
1241 T: Ord,
1242 {
1243 if self.ring.len() <= 1 {
1244 0
1245 } else {
1246 booth_least_rotation(self)
1247 }
1248 }
1249
1250 #[must_use]
1253 pub fn canonical(self) -> Vec<T>
1254 where
1255 T: Clone + Ord,
1256 {
1257 let idx = self.canonical_index();
1258 self.start_at(idx as isize).to_vec()
1259 }
1260
1261 #[must_use]
1265 pub fn bracelet(self) -> Vec<T>
1266 where
1267 T: Clone + Ord,
1268 {
1269 let a = self.canonical();
1270 let b = self.reflect_at(0).canonical();
1271 if a <= b {
1272 a
1273 } else {
1274 b
1275 }
1276 }
1277
1278 #[must_use]
1282 pub fn symmetry_indices(self) -> Vec<usize>
1283 where
1284 T: PartialEq,
1285 {
1286 let n = self.ring.len();
1287 if n == 0 {
1288 return Vec::new();
1289 }
1290 let reversed = self.reflect_at(-1);
1291 (0..n)
1292 .filter(|&shift| {
1293 (0..n).all(|i| *self.apply(i as isize) == *reversed.apply((i + shift) as isize))
1294 })
1295 .collect()
1296 }
1297
1298 #[must_use]
1302 pub fn reflectional_symmetry_axes(self) -> Vec<(AxisLocation, AxisLocation)>
1303 where
1304 T: PartialEq,
1305 {
1306 let n = self.ring.len();
1307 if n == 0 {
1308 return Vec::new();
1309 }
1310
1311 #[allow(clippy::cast_possible_wrap, clippy::cast_sign_loss)]
1312 self.symmetry_indices()
1313 .into_iter()
1314 .map(|shift| {
1315 let raw_k = (n as isize - 1 - shift as isize) % n as isize;
1316 let k = if raw_k < 0 {
1317 (raw_k + n as isize) as usize
1318 } else {
1319 raw_k as usize
1320 };
1321
1322 if n % 2 != 0 {
1323 let v = (k * (n + 1) / 2) % n;
1324 let opp = (v + n / 2) % n;
1325 (AxisLocation::Vertex(v), AxisLocation::edge(opp, n))
1326 } else if k % 2 == 0 {
1327 let v1 = k / 2;
1328 let v2 = (v1 + n / 2) % n;
1329 (AxisLocation::Vertex(v1), AxisLocation::Vertex(v2))
1330 } else {
1331 let e1 = (k - 1) / 2;
1332 let e2 = (e1 + n / 2) % n;
1333 (AxisLocation::edge(e1, n), AxisLocation::edge(e2, n))
1334 }
1335 })
1336 .collect()
1337 }
1338}
1339
1340#[cfg(feature = "alloc")]
1343#[allow(
1344 clippy::cast_sign_loss,
1345 clippy::cast_possible_wrap,
1346 clippy::many_single_char_names
1347)]
1348fn booth_least_rotation<T: Ord>(c: Circular<'_, T>) -> usize {
1349 let n = c.ring.len();
1350 let len = 2 * n;
1351 let mut f: Vec<isize> = alloc::vec![-1; len];
1352 let mut k: usize = 0;
1353 let at = |idx: usize| &c.ring[c.map_index(idx % n)];
1354
1355 for j in 1..len {
1356 let sj = at(j);
1357 let mut i = f[j - k - 1];
1358 while i != -1 && at(k + i as usize + 1) != sj {
1359 if sj < at(k + i as usize + 1) {
1360 k = j - i as usize - 1;
1361 }
1362 i = f[i as usize];
1363 }
1364 if i == -1 && at(k) != sj {
1365 if sj < at(k) {
1366 k = j;
1367 }
1368 f[j - k] = -1;
1369 } else {
1370 f[j - k] = i + 1;
1371 }
1372 }
1373 k
1374}
1375
1376#[cfg(test)]
1381mod tests {
1382 use super::*;
1383
1384 #[test]
1385 fn empty_ring_is_empty() {
1386 let empty: [i32; 0] = [];
1387 let c = empty.circular();
1388 assert_eq!(c.len(), 0);
1389 assert!(c.is_empty());
1390 assert_eq!(c.iter().count(), 0);
1391 }
1392
1393 #[test]
1394 fn len_and_is_empty() {
1395 let r = [1, 2, 3].circular();
1396 assert_eq!(r.len(), 3);
1397 assert!(!r.is_empty());
1398 }
1399
1400 #[test]
1401 fn apply_positive() {
1402 let r = [10, 20, 30].circular();
1403 assert_eq!(*r.apply(0), 10);
1404 assert_eq!(*r.apply(1), 20);
1405 assert_eq!(*r.apply(2), 30);
1406 }
1407
1408 #[test]
1409 fn apply_wraps_forward() {
1410 let r = [10, 20, 30].circular();
1411 assert_eq!(*r.apply(3), 10);
1412 assert_eq!(*r.apply(7), 20);
1413 }
1414
1415 #[test]
1416 fn apply_wraps_backward() {
1417 let r = [10, 20, 30].circular();
1418 assert_eq!(*r.apply(-1), 30);
1419 assert_eq!(*r.apply(-3), 10);
1420 assert_eq!(*r.apply(-4), 30);
1421 }
1422
1423 #[test]
1424 #[should_panic]
1425 fn apply_panics_on_empty() {
1426 let empty: [i32; 0] = [];
1427 let _ = empty.circular().apply(0);
1428 }
1429
1430 #[test]
1431 fn iter_yields_elements_in_order() {
1432 let r = [1, 2, 3].circular();
1433 let mut it = r.iter();
1434 assert_eq!(it.next(), Some(&1));
1435 assert_eq!(it.next(), Some(&2));
1436 assert_eq!(it.next(), Some(&3));
1437 assert_eq!(it.next(), None);
1438 }
1439
1440 #[test]
1441 fn iter_size_hint_and_exact_size() {
1442 let r = [1, 2, 3, 4].circular();
1443 let mut it = r.iter();
1444 assert_eq!(it.len(), 4);
1445 assert_eq!(it.size_hint(), (4, Some(4)));
1446 it.next();
1447 assert_eq!(it.len(), 3);
1448 }
1449
1450 #[test]
1451 fn iter_is_fused() {
1452 let r = [1, 2].circular();
1453 let mut it = r.iter();
1454 it.next();
1455 it.next();
1456 assert_eq!(it.next(), None);
1457 assert_eq!(it.next(), None); }
1459
1460 #[test]
1461 fn copy_semantics() {
1462 let r = [1, 2, 3].circular();
1463 let r2 = r; assert_eq!(r.len(), 3);
1465 assert_eq!(r2.len(), 3);
1466 }
1467
1468 #[test]
1470 fn copy_works_without_t_clone() {
1471 #[allow(dead_code)]
1472 struct NoClone(i32);
1473 let arr = [NoClone(1), NoClone(2)];
1474 let r = arr.circular();
1475 let r2 = r; assert_eq!(r.len(), 2);
1477 assert_eq!(r2.len(), 2);
1478 }
1479
1480 fn into_array<const N: usize>(c: Circular<'_, i32>) -> [i32; N] {
1483 let mut out = [0; N];
1484 for (slot, &x) in out.iter_mut().zip(c.iter()) {
1485 *slot = x;
1486 }
1487 out
1488 }
1489
1490 #[test]
1491 fn start_at_basic() {
1492 let r = [10, 20, 30, 40].circular();
1493 assert_eq!(into_array::<4>(r.start_at(0)), [10, 20, 30, 40]);
1494 assert_eq!(into_array::<4>(r.start_at(2)), [30, 40, 10, 20]);
1495 assert_eq!(into_array::<4>(r.start_at(-1)), [40, 10, 20, 30]);
1496 assert_eq!(into_array::<4>(r.start_at(5)), [20, 30, 40, 10]);
1497 }
1498
1499 #[test]
1500 fn start_at_empty() {
1501 let empty: [i32; 0] = [];
1502 assert_eq!(empty.circular().start_at(3).len(), 0);
1503 }
1504
1505 #[test]
1506 fn rotate_right_basic() {
1507 let r = [1, 2, 3, 4].circular();
1508 assert_eq!(into_array::<4>(r.rotate_right(0)), [1, 2, 3, 4]);
1509 assert_eq!(into_array::<4>(r.rotate_right(1)), [4, 1, 2, 3]);
1510 assert_eq!(into_array::<4>(r.rotate_right(-1)), [2, 3, 4, 1]);
1511 assert_eq!(into_array::<4>(r.rotate_right(5)), [4, 1, 2, 3]);
1512 }
1513
1514 #[test]
1515 fn rotate_left_basic() {
1516 let r = [1, 2, 3, 4].circular();
1517 assert_eq!(into_array::<4>(r.rotate_left(1)), [2, 3, 4, 1]);
1518 assert_eq!(into_array::<4>(r.rotate_left(-1)), [4, 1, 2, 3]);
1519 }
1520
1521 #[test]
1522 fn rotate_right_then_left_is_identity() {
1523 let ring = [1, 2, 3, 4, 5, 6, 7];
1524 let r = ring.circular();
1525 for step in -10..=10 {
1526 assert_eq!(
1527 into_array::<7>(r.rotate_right(step).rotate_left(step)),
1528 ring,
1529 "failed for step {}",
1530 step,
1531 );
1532 }
1533 }
1534
1535 #[test]
1536 fn reflect_at_basic() {
1537 let r = [10, 20, 30, 40].circular();
1538 assert_eq!(into_array::<4>(r.reflect_at(0)), [10, 40, 30, 20]);
1540 assert_eq!(into_array::<4>(r.reflect_at(2)), [30, 20, 10, 40]);
1542 }
1543
1544 #[test]
1545 fn reflect_at_is_involution() {
1546 let ring = [1, 2, 3, 4, 5, 6, 7];
1547 let r = ring.circular();
1548 for i in -3..10 {
1549 assert_eq!(
1550 into_array::<7>(r.reflect_at(i).reflect_at(i)),
1551 ring,
1552 "failed for i {}",
1553 i
1554 );
1555 }
1556 }
1557
1558 #[test]
1559 fn rotate_then_reflect_commutes() {
1560 let ring = [1, 2, 3, 4, 5];
1562 let r = ring.circular();
1563 for k in -7..=7 {
1564 assert_eq!(
1565 into_array::<5>(r.rotate_right(k).reflect_at(0)),
1566 into_array::<5>(r.reflect_at(0).rotate_left(k)),
1567 "failed for k {}",
1568 k,
1569 );
1570 }
1571 }
1572
1573 #[test]
1576 fn slice_within_one_lap() {
1577 let r = [0, 1, 2, 3, 4].circular();
1578 let v: [i32; 3] = {
1579 let mut out = [0; 3];
1580 for (s, &x) in out.iter_mut().zip(r.slice(1, 4)) {
1581 *s = x;
1582 }
1583 out
1584 };
1585 assert_eq!(v, [1, 2, 3]);
1586 }
1587
1588 #[test]
1589 fn slice_wraps() {
1590 let r = [0, 1, 2, 3, 4].circular();
1591 let mut out = [0; 5];
1592 for (s, &x) in out.iter_mut().zip(r.slice(2, 7)) {
1593 *s = x;
1594 }
1595 assert_eq!(out, [2, 3, 4, 0, 1]);
1596 }
1597
1598 #[test]
1599 fn slice_negative_from() {
1600 let r = [0, 1, 2, 3, 4].circular();
1601 let mut out = [0; 3];
1602 for (s, &x) in out.iter_mut().zip(r.slice(-2, 1)) {
1603 *s = x;
1604 }
1605 assert_eq!(out, [3, 4, 0]);
1606 }
1607
1608 #[test]
1609 fn slice_empty_range() {
1610 let r = [0, 1, 2, 3, 4].circular();
1611 assert_eq!(r.slice(3, 3).count(), 0);
1612 assert_eq!(r.slice(5, 2).count(), 0);
1613 }
1614
1615 #[test]
1616 fn slice_empty_ring() {
1617 let empty: [i32; 0] = [];
1618 assert_eq!(empty.circular().slice(0, 4).count(), 0);
1619 }
1620
1621 #[test]
1624 fn take_while_basic() {
1625 let r = [1, 2, 3, 4, 5].circular();
1626 let mut out = [0; 3];
1627 for (s, &x) in out.iter_mut().zip(r.take_while(|&x| x < 4, 0)) {
1628 *s = x;
1629 }
1630 assert_eq!(out, [1, 2, 3]);
1631 }
1632
1633 #[test]
1634 fn take_while_stops_after_one_lap() {
1635 let r = [1, 1, 1].circular();
1637 assert_eq!(r.take_while(|_| true, 0).count(), 3);
1638 }
1639
1640 #[test]
1641 fn drop_while_basic() {
1642 let r = [1, 2, 3, 4, 5].circular();
1643 let mut out = [0; 2];
1644 for (s, &x) in out.iter_mut().zip(r.drop_while(|&x| x < 4, 0)) {
1645 *s = x;
1646 }
1647 assert_eq!(out, [4, 5]);
1648 }
1649
1650 #[test]
1651 fn drop_while_all_dropped() {
1652 let r = [1, 2, 3].circular();
1653 assert_eq!(r.drop_while(|_| true, 0).count(), 0);
1654 }
1655
1656 #[test]
1659 fn enumerate_yields_ring_indices() {
1660 let r = [10, 20, 30].circular();
1661 let mut out: [(i32, usize); 3] = [(0, 0); 3];
1662 for (s, (&x, i)) in out.iter_mut().zip(r.enumerate(1)) {
1663 *s = (x, i);
1664 }
1665 assert_eq!(out, [(20, 1), (30, 2), (10, 0)]);
1666 }
1667
1668 #[test]
1669 fn enumerate_empty() {
1670 let empty: [i32; 0] = [];
1671 assert_eq!(empty.circular().enumerate(0).count(), 0);
1672 }
1673
1674 #[test]
1675 fn enumerate_is_exact_size() {
1676 let r = [10, 20, 30, 40].circular();
1677 let it = r.enumerate(2);
1678 assert_eq!(it.len(), 4);
1679 }
1680
1681 #[test]
1684 fn rotations_yields_n_views() {
1685 let ring = [1, 2, 3, 4];
1686 let r = ring.circular();
1687 let firsts: [i32; 4] = {
1688 let mut out = [0; 4];
1689 for (s, v) in out.iter_mut().zip(r.rotations()) {
1690 *s = *v.apply(0);
1691 }
1692 out
1693 };
1694 assert_eq!(firsts, [1, 2, 3, 4]);
1695 }
1696
1697 #[test]
1698 fn rotations_empty_yields_one() {
1699 let empty: [i32; 0] = [];
1700 assert_eq!(empty.circular().rotations().count(), 1);
1701 }
1702
1703 #[test]
1704 fn rotations_exact_size() {
1705 let r = [1, 2, 3, 4, 5].circular();
1706 let it = r.rotations();
1707 assert_eq!(it.len(), 5);
1708 }
1709
1710 #[test]
1711 fn rotations_preserves_reflected() {
1712 let r = [1, 2, 3].circular().reflect_at(0);
1714 let firsts: [i32; 3] = {
1715 let mut out = [0; 3];
1716 for (s, v) in out.iter_mut().zip(r.rotations()) {
1717 *s = *v.apply(0);
1718 }
1719 out
1720 };
1721 assert_eq!(firsts, [1, 3, 2]);
1723 }
1724
1725 #[test]
1726 fn reflections_yields_two() {
1727 let r = [1, 2, 3, 4].circular();
1728 let count = r.reflections().count();
1729 assert_eq!(count, 2);
1730 }
1731
1732 #[test]
1733 fn reflections_second_is_reflect_at_zero() {
1734 let r = [1, 2, 3, 4].circular();
1735 let mut it = r.reflections();
1736 let first = it.next().unwrap();
1737 let second = it.next().unwrap();
1738 let first_v: [i32; 4] = into_array(first);
1739 let second_v: [i32; 4] = into_array(second);
1740 assert_eq!(first_v, [1, 2, 3, 4]);
1741 assert_eq!(second_v, [1, 4, 3, 2]);
1742 }
1743
1744 #[test]
1745 fn reflections_empty_yields_one() {
1746 let empty: [i32; 0] = [];
1747 assert_eq!(empty.circular().reflections().count(), 1);
1748 }
1749
1750 #[test]
1751 fn reversions_yields_two() {
1752 let r = [1, 2, 3].circular();
1753 assert_eq!(r.reversions().count(), 2);
1754 }
1755
1756 #[test]
1757 fn reversions_second_is_reverse() {
1758 let r = [1, 2, 3, 4].circular();
1759 let mut it = r.reversions();
1760 let first = it.next().unwrap();
1761 let second = it.next().unwrap();
1762 let first_v: [i32; 4] = into_array(first);
1763 let second_v: [i32; 4] = into_array(second);
1764 assert_eq!(first_v, [1, 2, 3, 4]);
1765 assert_eq!(second_v, [4, 3, 2, 1]);
1766 }
1767
1768 #[test]
1769 fn rotations_and_reflections_yields_2n() {
1770 let r = [1, 2, 3, 4].circular();
1771 assert_eq!(r.rotations_and_reflections().count(), 8);
1772 }
1773
1774 #[test]
1775 fn rotations_and_reflections_distinct_views() {
1776 let r = [1, 2, 3, 4].circular();
1778 let all: [[i32; 4]; 8] = {
1779 let mut out = [[0; 4]; 8];
1780 for (s, v) in out.iter_mut().zip(r.rotations_and_reflections()) {
1781 *s = into_array(v);
1782 }
1783 out
1784 };
1785 assert_eq!(all[0], [1, 2, 3, 4]);
1786 assert_eq!(all[1], [2, 3, 4, 1]);
1787 assert_eq!(all[2], [3, 4, 1, 2]);
1788 assert_eq!(all[3], [4, 1, 2, 3]);
1789 assert_eq!(all[4], [1, 4, 3, 2]);
1791 assert_eq!(all[5], [4, 3, 2, 1]);
1792 assert_eq!(all[6], [3, 2, 1, 4]);
1793 assert_eq!(all[7], [2, 1, 4, 3]);
1794 }
1795
1796 #[test]
1797 fn rotations_and_reflections_empty_yields_one() {
1798 let empty: [i32; 0] = [];
1799 assert_eq!(empty.circular().rotations_and_reflections().count(), 1);
1800 }
1801
1802 fn collect_iter<const N: usize>(mut iter: CircularIter<'_, i32>) -> [i32; N] {
1805 let mut out = [0; N];
1806 for s in out.iter_mut() {
1807 *s = *iter.next().unwrap();
1808 }
1809 out
1810 }
1811
1812 #[test]
1813 fn windows_basic() {
1814 let r = [1, 2, 3, 4].circular();
1815 let mut it = r.windows(2);
1816 assert_eq!(collect_iter::<2>(it.next().unwrap()), [1, 2]);
1817 assert_eq!(collect_iter::<2>(it.next().unwrap()), [2, 3]);
1818 assert_eq!(collect_iter::<2>(it.next().unwrap()), [3, 4]);
1819 assert_eq!(collect_iter::<2>(it.next().unwrap()), [4, 1]); assert!(it.next().is_none());
1821 }
1822
1823 #[test]
1824 fn windows_count_equals_ring_len() {
1825 let r = [1, 2, 3, 4, 5].circular();
1826 assert_eq!(r.windows(2).count(), 5);
1827 assert_eq!(r.windows(3).count(), 5);
1828 assert_eq!(r.windows(5).count(), 5);
1829 }
1830
1831 #[test]
1832 fn windows_size_greater_than_ring() {
1833 let r = [1, 2, 3].circular();
1834 let mut it = r.windows(5);
1836 assert_eq!(collect_iter::<5>(it.next().unwrap()), [1, 2, 3, 1, 2]);
1837 assert_eq!(collect_iter::<5>(it.next().unwrap()), [2, 3, 1, 2, 3]);
1838 assert_eq!(collect_iter::<5>(it.next().unwrap()), [3, 1, 2, 3, 1]);
1839 assert!(it.next().is_none());
1840 }
1841
1842 #[test]
1843 fn windows_empty_ring() {
1844 let empty: [i32; 0] = [];
1845 assert_eq!(empty.circular().windows(3).count(), 0);
1846 }
1847
1848 #[test]
1849 #[should_panic]
1850 fn windows_zero_size_panics() {
1851 let _ = [1, 2, 3].circular().windows(0);
1852 }
1853
1854 #[test]
1855 fn chunks_evenly_divisible() {
1856 let r = [1, 2, 3, 4].circular();
1857 let mut it = r.chunks(2);
1858 assert_eq!(collect_iter::<2>(it.next().unwrap()), [1, 2]);
1859 assert_eq!(collect_iter::<2>(it.next().unwrap()), [3, 4]);
1860 assert!(it.next().is_none());
1861 }
1862
1863 #[test]
1864 fn chunks_wrap_at_seam() {
1865 let r = [1, 2, 3, 4, 5].circular();
1867 let mut it = r.chunks(2);
1868 assert_eq!(collect_iter::<2>(it.next().unwrap()), [1, 2]);
1869 assert_eq!(collect_iter::<2>(it.next().unwrap()), [3, 4]);
1870 assert_eq!(collect_iter::<2>(it.next().unwrap()), [5, 1]);
1871 assert!(it.next().is_none());
1872 }
1873
1874 #[test]
1875 fn chunks_size_one() {
1876 let r = [1, 2, 3].circular();
1877 assert_eq!(r.chunks(1).count(), 3);
1878 }
1879
1880 #[test]
1881 fn chunks_size_greater_than_ring() {
1882 let r = [1, 2, 3].circular();
1883 let mut it = r.chunks(5);
1885 assert_eq!(collect_iter::<5>(it.next().unwrap()), [1, 2, 3, 1, 2]);
1886 assert!(it.next().is_none());
1887 }
1888
1889 #[test]
1890 fn chunks_empty_ring() {
1891 let empty: [i32; 0] = [];
1892 assert_eq!(empty.circular().chunks(2).count(), 0);
1893 }
1894
1895 #[test]
1896 #[should_panic]
1897 fn chunks_zero_size_panics() {
1898 let _ = [1, 2, 3].circular().chunks(0);
1899 }
1900
1901 #[test]
1904 fn chained_rotations_first_element() {
1905 let r = [3, 1, 2].circular();
1906 let firsts: [i32; 3] = {
1907 let mut out = [0; 3];
1908 for (s, v) in out.iter_mut().zip(r.rotations()) {
1909 *s = *v.apply(0);
1910 }
1911 out
1912 };
1913 assert_eq!(firsts, [3, 1, 2]);
1914 }
1915
1916 #[test]
1917 fn chained_windows_count_satisfying() {
1918 let r = [1, 2, 3, 4, 5].circular();
1919 let n = r
1921 .windows(2)
1922 .filter(|w| {
1923 let mut clone = w.clone();
1924 clone.next().map_or(false, |x| x % 2 == 0)
1925 })
1926 .count();
1927 assert_eq!(n, 2); }
1929
1930 #[test]
1933 fn index_from_basic() {
1934 let r = [10, 20, 30].circular();
1935 assert_eq!(r.index_from(0), 0);
1936 assert_eq!(r.index_from(2), 2);
1937 assert_eq!(r.index_from(3), 0);
1938 assert_eq!(r.index_from(7), 1);
1939 assert_eq!(r.index_from(-1), 2);
1940 assert_eq!(r.index_from(-4), 2);
1941 }
1942
1943 #[test]
1944 #[should_panic]
1945 fn index_from_empty_panics() {
1946 let empty: [i32; 0] = [];
1947 let _ = empty.circular().index_from(0);
1948 }
1949
1950 #[test]
1953 fn is_rotation_of_true() {
1954 let a = [1, 2, 3, 4];
1955 let b = [3, 4, 1, 2];
1956 assert!(a.circular().is_rotation_of(&b));
1957 }
1958
1959 #[test]
1960 fn is_rotation_of_false() {
1961 let a = [1, 2, 3, 4];
1962 let b = [4, 3, 2, 1];
1963 assert!(!a.circular().is_rotation_of(&b));
1964 }
1965
1966 #[test]
1967 fn is_rotation_of_empty() {
1968 let a: [i32; 0] = [];
1969 let b: [i32; 0] = [];
1970 assert!(a.circular().is_rotation_of(&b));
1971 }
1972
1973 #[test]
1974 fn is_rotation_of_length_mismatch() {
1975 assert!(![1, 2].circular().is_rotation_of(&[1, 2, 3]));
1976 }
1977
1978 #[test]
1979 fn is_reflection_of_basic() {
1980 let a = [1, 2, 3, 4];
1981 assert!(a.circular().is_reflection_of(&[1, 4, 3, 2]));
1982 assert!(a.circular().is_reflection_of(&[1, 2, 3, 4]));
1983 assert!(!a.circular().is_reflection_of(&[2, 1, 4, 3]));
1984 }
1985
1986 #[test]
1987 fn is_reversion_of_basic() {
1988 let a = [1, 2, 3, 4];
1989 assert!(a.circular().is_reversion_of(&[1, 2, 3, 4]));
1990 assert!(a.circular().is_reversion_of(&[4, 3, 2, 1]));
1991 assert!(!a.circular().is_reversion_of(&[3, 2, 1, 4]));
1992 }
1993
1994 #[test]
1995 fn is_rotation_or_reflection_of_basic() {
1996 let a = [1, 2, 3, 4];
1997 assert!(a.circular().is_rotation_or_reflection_of(&[3, 4, 1, 2])); assert!(a.circular().is_rotation_or_reflection_of(&[2, 1, 4, 3])); assert!(!a.circular().is_rotation_or_reflection_of(&[1, 3, 2, 4])); }
2001
2002 #[test]
2003 fn rotation_offset_basic() {
2004 let a = [10, 20, 30, 40];
2005 assert_eq!(a.circular().rotation_offset(&[30, 40, 10, 20]), Some(2));
2006 assert_eq!(a.circular().rotation_offset(&[10, 20, 30, 40]), Some(0));
2007 assert_eq!(a.circular().rotation_offset(&[1, 2, 3, 4]), None);
2008 }
2009
2010 #[test]
2011 fn rotation_offset_length_mismatch() {
2012 assert_eq!([1, 2, 3].circular().rotation_offset(&[1, 2]), None);
2013 }
2014
2015 #[test]
2018 fn hamming_distance_basic() {
2019 let a = [1, 2, 3, 4];
2020 let b = [1, 2, 0, 4];
2021 assert_eq!(a.circular().hamming_distance(&b), 1);
2022 assert_eq!(a.circular().hamming_distance(&a), 0);
2023 }
2024
2025 #[test]
2026 #[should_panic]
2027 fn hamming_distance_length_mismatch_panics() {
2028 let _ = [1, 2].circular().hamming_distance(&[1, 2, 3]);
2029 }
2030
2031 #[test]
2032 fn min_rotational_hamming_distance_basic() {
2033 let a = [1, 2, 3, 4];
2034 let b = [3, 4, 1, 2]; assert_eq!(a.circular().min_rotational_hamming_distance(&b), 0);
2036 assert_eq!(a.circular().min_rotational_hamming_distance(&a), 0);
2037 let c = [3, 4, 1, 5]; assert_eq!(a.circular().min_rotational_hamming_distance(&c), 1);
2039 }
2040
2041 #[test]
2044 fn contains_slice_basic() {
2045 let r = [1, 2, 3, 4, 5].circular();
2046 assert!(r.contains_slice(&[2, 3, 4]));
2047 assert!(r.contains_slice(&[5, 1, 2])); assert!(!r.contains_slice(&[2, 4]));
2049 }
2050
2051 #[test]
2052 fn contains_slice_empty() {
2053 assert!([1, 2, 3].circular().contains_slice(&[]));
2054 let empty: [i32; 0] = [];
2055 assert!(!empty.circular().contains_slice(&[1]));
2056 assert!(empty.circular().contains_slice(&[]));
2057 }
2058
2059 #[test]
2060 fn index_of_slice_basic() {
2061 let r = [1, 2, 3, 4, 5].circular();
2062 assert_eq!(r.index_of_slice(&[3, 4], 0), Some(2));
2063 assert_eq!(r.index_of_slice(&[5, 1], 0), Some(4)); assert_eq!(r.index_of_slice(&[9], 0), None);
2065 }
2066
2067 #[test]
2070 fn rotational_symmetry_basic() {
2071 assert_eq!([1, 2, 3, 4].circular().rotational_symmetry(), 1);
2072 assert_eq!([1, 2, 1, 2].circular().rotational_symmetry(), 2);
2073 assert_eq!([1, 1, 1, 1].circular().rotational_symmetry(), 4);
2074 }
2075
2076 #[test]
2077 fn rotational_symmetry_short() {
2078 let empty: [i32; 0] = [];
2079 assert_eq!(empty.circular().rotational_symmetry(), 1);
2080 assert_eq!([5].circular().rotational_symmetry(), 1);
2081 }
2082
2083 #[test]
2084 fn symmetry_palindrome() {
2085 assert!([1, 2, 3, 2, 1].circular().symmetry() >= 1);
2087 }
2088
2089 #[test]
2090 fn symmetry_empty() {
2091 let empty: [i32; 0] = [];
2092 assert_eq!(empty.circular().symmetry(), 0);
2093 }
2094}
2095
2096#[cfg(all(test, feature = "alloc"))]
2097mod alloc_tests {
2098 use super::*;
2099 use alloc::vec;
2100 use alloc::vec::Vec;
2101
2102 #[test]
2105 fn canonical_index_basic() {
2106 assert_eq!([3, 1, 2].circular().canonical_index(), 1);
2107 assert_eq!([1, 2, 3].circular().canonical_index(), 0);
2108 assert_eq!([2, 3, 0, 1].circular().canonical_index(), 2);
2109 }
2110
2111 #[test]
2112 fn canonical_index_short() {
2113 let empty: [i32; 0] = [];
2114 assert_eq!(empty.circular().canonical_index(), 0);
2115 assert_eq!([7].circular().canonical_index(), 0);
2116 }
2117
2118 #[test]
2119 fn canonical_basic() {
2120 assert_eq!([3, 1, 2].circular().canonical(), vec![1, 2, 3]);
2121 assert_eq!([1, 2, 3].circular().canonical(), vec![1, 2, 3]);
2122 }
2123
2124 #[test]
2125 fn canonical_all_equal() {
2126 assert_eq!([5, 5, 5].circular().canonical(), vec![5, 5, 5]);
2127 }
2128
2129 #[test]
2130 fn canonical_rotations_share_canonical() {
2131 let a = [3, 1, 2];
2132 let b = [1, 2, 3];
2133 let c = [2, 3, 1];
2134 assert_eq!(a.circular().canonical(), b.circular().canonical());
2135 assert_eq!(b.circular().canonical(), c.circular().canonical());
2136 }
2137
2138 #[test]
2139 fn bracelet_basic() {
2140 let a = [3, 1, 2];
2142 let b = [3, 2, 1]; assert_eq!(a.circular().bracelet(), b.circular().bracelet());
2144 }
2145
2146 #[test]
2149 fn symmetry_indices_palindrome() {
2150 let r = [1, 2, 3, 2, 1].circular();
2151 assert!(!r.symmetry_indices().is_empty());
2152 }
2153
2154 #[test]
2155 fn symmetry_indices_none() {
2156 let r = [1, 2, 3, 4].circular();
2157 assert_eq!(r.symmetry_indices(), Vec::<usize>::new());
2159 }
2160
2161 #[test]
2162 fn symmetry_indices_empty() {
2163 let empty: [i32; 0] = [];
2164 assert_eq!(empty.circular().symmetry_indices(), Vec::<usize>::new());
2165 }
2166
2167 #[test]
2168 fn reflectional_symmetry_axes_count_matches_symmetry() {
2169 let r = [1, 2, 1, 2].circular();
2170 let axes = r.reflectional_symmetry_axes();
2171 assert_eq!(axes.len(), r.symmetry());
2172 }
2173
2174 #[test]
2177 fn to_vec_basic() {
2178 let r = [10, 20, 30].circular();
2179 assert_eq!(r.to_vec(), vec![10, 20, 30]);
2180 assert_eq!(r.rotate_right(1).to_vec(), vec![30, 10, 20]);
2181 assert_eq!(r.reflect_at(0).to_vec(), vec![10, 30, 20]);
2182 }
2183
2184 #[test]
2185 fn to_vec_empty() {
2186 let empty: [i32; 0] = [];
2187 assert_eq!(empty.circular().to_vec(), Vec::<i32>::new());
2188 }
2189
2190 #[test]
2193 fn rotations_map_canonical_index() {
2194 let r = [3, 1, 2, 3, 1, 2].circular();
2195 let indices: Vec<usize> = r.rotations().map(|v| v.canonical_index()).collect();
2196 assert_eq!(indices, vec![1, 0, 2, 1, 0, 2]);
2197 }
2198}