1use num_iter::range_inclusive;
2use num_traits::{Bounded, One, ToPrimitive};
3use std::convert::TryFrom;
4use std::fmt;
5use std::hash::Hash;
6use std::ops::{Add, Sub};
7
8#[derive(Copy, Clone, PartialEq, Eq, Hash, Debug)]
26pub struct Position<T>(pub T, pub T);
27
28impl<T> Position<T> {
29 pub fn try_from<U>(value: Position<U>) -> Result<Position<T>, T::Error>
46 where
47 T: TryFrom<U>,
48 {
49 Ok(Position(T::try_from(value.0)?, T::try_from(value.1)?))
50 }
51
52 #[inline]
70 pub fn try_into<U>(self) -> Result<Position<U>, U::Error>
71 where
72 U: TryFrom<T>,
73 {
74 Position::<U>::try_from(self)
75 }
76
77 pub fn moore_neighborhood_positions(&self) -> impl Iterator<Item = Self>
98 where
99 T: Copy + PartialOrd + Add<Output = T> + Sub<Output = T> + One + Bounded + ToPrimitive,
100 {
101 let Position(x, y) = *self;
102 let min = T::min_value();
103 let max = T::max_value();
104 let one = T::one();
105 let x_start = if x > min { x - one } else { x };
106 let x_stop = if x < max { x + one } else { x };
107 let y_start = if y > min { y - one } else { y };
108 let y_stop = if y < max { y + one } else { y };
109 range_inclusive(y_start, y_stop)
110 .flat_map(move |v| range_inclusive(x_start, x_stop).map(move |u| Position(u, v)))
111 .filter(move |&pos| pos != Position(x, y))
112 }
113}
114
115impl<T> fmt::Display for Position<T>
116where
117 T: fmt::Display,
118{
119 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
120 write!(f, "({}, {})", self.0, self.1)?;
121 Ok(())
122 }
123}
124
125#[cfg(test)]
126mod tests {
127 use super::*;
128 use i32 as I;
129 use std::collections::HashSet;
130 #[test]
131 fn display() {
132 let target = Position(1, 2);
133 assert_eq!(format!("{target}"), "(1, 2)".to_string());
134 }
135 #[test]
136 fn try_from_infallible() {
137 let base: Position<i8> = Position(0, 0);
138 let target = Position::<i16>::try_from(base);
139 assert!(target.is_ok());
140 }
141 #[test]
142 fn try_from_fallible_no_error() {
143 let base: Position<i16> = Position(0, 0);
144 let target = Position::<i8>::try_from(base);
145 assert!(target.is_ok());
146 }
147 #[test]
148 fn try_from_fallible_with_error() {
149 let base: Position<i8> = Position(-1, 0);
150 let target = Position::<u8>::try_from(base);
151 assert!(target.is_err());
152 }
153 #[test]
154 fn try_into_infallible() {
155 let base: Position<i8> = Position(0, 0);
156 let target = base.try_into::<i16>();
157 assert!(target.is_ok());
158 }
159 #[test]
160 fn try_into_fallible_no_error() {
161 let base: Position<i16> = Position(0, 0);
162 let target = base.try_into::<i8>();
163 assert!(target.is_ok());
164 }
165 #[test]
166 fn try_into_fallible_with_error() {
167 let base: Position<i8> = Position(-1, 0);
168 let target = base.try_into::<u8>();
169 assert!(target.is_err());
170 }
171 #[test]
172 fn moore_neighborhood_positions_basic() {
173 let target: Position<I> = Position(0, 0);
174 let result: HashSet<_> = target.moore_neighborhood_positions().collect();
175 assert_eq!(
176 result,
177 [(-1, -1), (0, -1), (1, -1), (-1, 0), (1, 0), (-1, 1), (0, 1), (1, 1)]
178 .iter()
179 .copied()
180 .map(|(x, y)| Position(x, y))
181 .collect::<HashSet<_>>()
182 );
183 }
184 #[test]
185 fn moore_neighborhood_positions_bounds() {
186 let min = I::min_value();
187 let max = I::max_value();
188 let zero: I = 0;
189 for (pos_tuple, expected_count) in [
190 ((min, min), 3),
191 ((min, zero), 5),
192 ((min, max), 3),
193 ((zero, min), 5),
194 ((zero, zero), 8),
195 ((zero, max), 5),
196 ((max, min), 3),
197 ((max, zero), 5),
198 ((max, max), 3),
199 ] {
200 let pos = Position(pos_tuple.0, pos_tuple.1);
201 assert_eq!(pos.moore_neighborhood_positions().count(), expected_count);
202 }
203 }
204}