turing_machine_ai/code.rs
1//! This module contains functionality for working with codes and sets of codes.
2
3use std::fmt::Debug;
4use std::num::NonZeroU128;
5
6use thiserror::Error;
7
8/// A Turing Machine code, represented by a flipped bit in a [`u128`]. This is
9/// the most efficient format for use with [`Set`] since it allows for fast
10/// set inclusion checks.
11#[derive(Eq, PartialEq, Copy, Clone, Hash)]
12pub struct Code {
13 bits: NonZeroU128,
14}
15
16/// This error may be returned when attempting to construct an invalid code.
17#[derive(Copy, Clone, Eq, PartialEq, Debug, Error, Hash)]
18pub enum Error {
19 /// Returned when attempting to construct an invalid code.
20 #[error("the provided digits do not form a valid code")]
21 InvalidDigits,
22}
23
24/// Returned by [`Code::is_ascending_or_descending`] to indicate whether the code
25/// has an ascending or descending sequence.
26#[derive(Clone, Copy, Eq, PartialEq, Debug, Hash)]
27pub enum Order {
28 /// The code has an ascending sequence, i.e. `(1, 2, 3)`.
29 Ascending,
30 /// The code has a descending sequence, i.e. `(4, 3, 2)`.
31 Descending,
32 /// The code has neither an ascending or descending sequence.
33 NoOrder,
34}
35
36impl Code {
37 fn digits_to_index(triangle: u8, square: u8, circle: u8) -> usize {
38 (usize::from(triangle) - 1) + (usize::from(square) - 1) * 5 + (usize::from(circle) - 1) * 25
39 }
40
41 fn from_index(index: usize) -> Result<Self, Error> {
42 if index < 125 {
43 Ok(Code {
44 bits: (1 << index).try_into().unwrap(),
45 })
46 } else {
47 Err(Error::InvalidDigits)
48 }
49 }
50
51 /// Get the code with the given digits.
52 ///
53 /// # Errors
54 /// If the provided digits do not lie in the range `1..=5`, the code is
55 /// invalid and the error [`code::Error::InvalidDigits`] will be returned.
56 ///
57 /// # Examples
58 /// ```rust
59 /// use turing_machine_ai::code;
60 /// assert!(code::Code::from_digits(1, 2, 3).is_ok());
61 /// assert_eq!(code::Code::from_digits(3, 4, 9), Err(code::Error::InvalidDigits));
62 /// ```
63 #[allow(clippy::missing_panics_doc)]
64 pub fn from_digits(triangle: u8, square: u8, circle: u8) -> Result<Self, Error> {
65 if !(1..=5).contains(&triangle) || !(1..=5).contains(&square) || !(1..=5).contains(&circle)
66 {
67 Err(Error::InvalidDigits)
68 } else {
69 Ok(Code {
70 bits: (1 << Self::digits_to_index(triangle, square, circle))
71 .try_into()
72 .unwrap(),
73 })
74 }
75 }
76
77 /// Get the digits of this code.
78 /// ```rust
79 /// use turing_machine_ai::code::Code;
80 ///
81 /// let code = Code::from_digits(5, 4, 3)?;
82 /// assert_eq!(code.digits(), (5, 4, 3));
83 ///
84 /// let code_2 = Code::from_digits(1, 3, 4)?;
85 /// assert_eq!(code_2.digits(), (1, 3, 4));
86 /// # Ok::<(), turing_machine_ai::code::Error>(())
87 /// ```
88 #[must_use]
89 // It is not possible to make this function panic, since all digits will
90 // lie between 1-5.
91 #[allow(clippy::missing_panics_doc)]
92 pub fn digits(self) -> (u8, u8, u8) {
93 let index = self.bits.trailing_zeros();
94 let triangle = (index % 5) + 1;
95 let square = ((index / 5) % 5) + 1;
96 let circle = ((index / 25) % 5) + 1;
97 (
98 u8::try_from(triangle).unwrap(),
99 u8::try_from(square).unwrap(),
100 u8::try_from(circle).unwrap(),
101 )
102 }
103
104 /// Returns the digit for the triangle symbol in this code.
105 #[must_use]
106 pub fn triangle(self) -> u8 {
107 self.digits().0
108 }
109
110 /// Returns the digit for the square symbol in this code.
111 #[must_use]
112 pub fn square(self) -> u8 {
113 self.digits().1
114 }
115
116 /// Returns the digit for the circle symbol in this code.
117 #[must_use]
118 pub fn circle(self) -> u8 {
119 self.digits().2
120 }
121
122 /// Get the sum of the digits.
123 ///
124 /// ```rust
125 /// use turing_machine_ai::code::Code;
126 /// let code = Code::from_digits(5, 2, 4)?;
127 /// assert_eq!(code.digit_sum(), 11);
128 /// # Ok::<(), turing_machine_ai::code::Error>(())
129 /// ```
130 #[must_use]
131 pub fn digit_sum(self) -> u8 {
132 let (a, b, c) = self.digits();
133 a + b + c
134 }
135
136 /// Count the appearances of a particular digit.
137 ///
138 /// # Example
139 /// ```rust
140 ///
141 /// use turing_machine_ai::code::Code;
142 ///
143 /// assert_eq!(Code::from_digits(2, 3, 4)?.count_digit(2), 1);
144 /// assert_eq!(Code::from_digits(2, 3, 2)?.count_digit(2), 2);
145 /// # Ok::<(), turing_machine_ai::code::Error>(())
146 /// ```
147 pub fn count_digit(&self, digit: u8) -> usize {
148 usize::from(self.triangle() == digit)
149 + usize::from(self.square() == digit)
150 + usize::from(self.circle() == digit)
151 }
152
153 /// Count the even digits.
154 ///
155 /// # Example
156 /// ```
157 /// use turing_machine_ai::code::Code;
158 ///
159 /// assert_eq!(Code::from_digits(2, 3, 4)?.count_even(), 2);
160 /// # Ok::<(), turing_machine_ai::code::Error>(())
161 /// ```
162 #[must_use]
163 pub fn count_even(&self) -> usize {
164 usize::from(self.triangle() % 2 == 0)
165 + usize::from(self.square() % 2 == 0)
166 + usize::from(self.circle() % 2 == 0)
167 }
168
169 /// Number of digits in ascending or descending order as specified by
170 /// verifier 25.
171 #[must_use]
172 pub fn sequence_ascending_or_descending(&self) -> usize {
173 let (t, s, c) = self.digits();
174 if (t + 1 == s && s + 1 == c) || (t == s + 1 && s == c + 1) {
175 3
176 } else if t + 1 == s || s + 1 == c || t == s + 1 || s == c + 1 {
177 2
178 } else {
179 0
180 }
181 }
182
183 /// Number of digits in ascending order.
184 /// ```
185 /// use turing_machine_ai::code::Code;
186 /// assert_eq!(Code::from_digits(2, 3, 4)?.sequence_ascending(), 3);
187 /// assert_eq!(Code::from_digits(2, 3, 3)?.sequence_ascending(), 2);
188 /// assert_eq!(Code::from_digits(1, 3, 5)?.sequence_ascending(), 0);
189 /// # Ok::<(), turing_machine_ai::code::Error>(())
190 /// ```
191 #[must_use]
192 pub fn sequence_ascending(self) -> usize {
193 let (t, s, c) = self.digits();
194 if t + 1 == s && s + 1 == c {
195 3
196 } else if t + 1 == s || s + 1 == c {
197 2
198 } else {
199 0
200 }
201 }
202
203 /// Counts the repetitions of the most frequent number, à la verifier card
204 /// 20.
205 ///
206 /// ```rust
207 /// use turing_machine_ai::code::Code;
208 /// assert_eq!(Code::from_digits(2, 2, 2)?.repeating_numbers(), 2);
209 /// assert_eq!(Code::from_digits(1, 1, 2)?.repeating_numbers(), 1);
210 /// assert_eq!(Code::from_digits(1, 2, 1)?.repeating_numbers(), 1);
211 /// assert_eq!(Code::from_digits(1, 2, 5)?.repeating_numbers(), 0);
212 /// # Ok::<(), turing_machine_ai::code::Error>(())
213 /// ```
214 #[must_use]
215 pub fn repeating_numbers(self) -> usize {
216 match self.digits() {
217 (a, b, c) if a == b && b == c => 2,
218 (a, b, c) if a == b || b == c || a == c => 1,
219 _ => 0,
220 }
221 }
222
223 /// Provides the order of the digits as in verifier 22.
224 ///
225 /// ```rust
226 /// use turing_machine_ai::code::{Code, Order};
227 /// assert_eq!(Code::from_digits(1, 3, 5)?.is_ascending_or_descending(), Order::Ascending);
228 /// assert_eq!(Code::from_digits(4, 2, 1)?.is_ascending_or_descending(), Order::Descending);
229 /// assert_eq!(Code::from_digits(2, 3, 1)?.is_ascending_or_descending(), Order::NoOrder);
230 /// # Ok::<(), turing_machine_ai::code::Error>(())
231 /// ```
232 #[must_use]
233 pub fn is_ascending_or_descending(self) -> Order {
234 let (triangle, square, circle) = self.digits();
235 if triangle < square && square < circle {
236 Order::Ascending
237 } else if triangle > square && square > circle {
238 Order::Descending
239 } else {
240 Order::NoOrder
241 }
242 }
243}
244
245impl Debug for Code {
246 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
247 let (triangle, circle, square) = self.digits();
248 write!(f, "△: {triangle}, □: {square}, ○: {circle}")
249 }
250}
251
252/// A struct representing a set of codes.
253#[derive(Copy, Clone, Eq, PartialEq, Hash, Default)]
254pub struct Set {
255 code_bitmap: u128,
256}
257
258impl Debug for Set {
259 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
260 writeln!(f, "△ □ ○")?;
261 for code in self.into_iter() {
262 writeln!(f, "{} {} {}", code.triangle(), code.square(), code.circle())?;
263 }
264 Ok(())
265 }
266}
267
268impl Set {
269 /// Create a new code set, containing only the provided code. This is a
270 /// free operation.
271 #[must_use]
272 pub fn new_from_code(code: Code) -> Self {
273 Set {
274 code_bitmap: code.bits.get(),
275 }
276 }
277
278 /// Insert the given code into this code set.
279 pub fn insert(&mut self, code: Code) {
280 self.code_bitmap |= Set::new_from_code(code).code_bitmap;
281 }
282
283 /// Get a new code set, that contains only the elements contained in both
284 /// this set, as well as the provided set.
285 #[must_use]
286 pub fn intersected_with(self, code_set: Set) -> Set {
287 Set {
288 code_bitmap: self.code_bitmap & code_set.code_bitmap,
289 }
290 }
291
292 /// Get a new code set that contains all elements contained in either this
293 /// set, or the provided set.
294 #[must_use]
295 pub fn union_with(self, code_set: Set) -> Set {
296 Set {
297 code_bitmap: self.code_bitmap | code_set.code_bitmap,
298 }
299 }
300
301 /// Return an empty set.
302 ///
303 /// # Example
304 /// ```rust
305 /// use turing_machine_ai::code::Set;
306 ///
307 /// let empty_set = Set::empty();
308 /// assert_eq!(empty_set.size(), 0);
309 /// ```
310 #[must_use]
311 pub fn empty() -> Set {
312 Set { code_bitmap: 0 }
313 }
314
315 /// Return a set containing all codes.
316 ///
317 /// # Example
318 /// ```rust
319 /// use turing_machine_ai::code::Set;
320 ///
321 /// let complete_set = Set::all();
322 /// assert_eq!(complete_set.size(), 125);
323 /// ```
324 #[must_use]
325 pub fn all() -> Set {
326 Set {
327 code_bitmap: (1 << 125) - 1,
328 }
329 }
330
331 /// Get the size of this code set.
332 ///
333 /// # Example
334 /// ```
335 /// use turing_machine_ai::code::Set;
336 /// assert_eq!(Set::all().size(), 125);
337 /// assert_eq!(Set::empty().size(), 0);
338 /// ```
339 #[must_use]
340 pub fn size(self) -> u32 {
341 self.code_bitmap.count_ones()
342 }
343
344 /// Construct a new code set based on a closure that returns `true` for any
345 /// code that must be in the set.
346 pub fn from_closure(checker: fn(Code) -> bool) -> Self {
347 Set::all()
348 .into_iter()
349 .filter(|code| checker(*code))
350 .collect()
351 }
352
353 /// Returns whether the given code is part of this set.
354 /// ```rust
355 /// use turing_machine_ai::code::{Set, Code};
356 /// let code_1 = Code::from_digits(1, 2, 3)?;
357 /// let code_2 = Code::from_digits(3, 3, 5)?;
358 /// let set = Set::new_from_code(code_1);
359 /// assert!(set.contains(code_1));
360 /// assert!(!set.contains(code_2));
361 /// # Ok::<(), turing_machine_ai::code::Error>(())
362 /// ```
363 #[must_use]
364 pub fn contains(self, code: Code) -> bool {
365 (self.code_bitmap & code.bits.get()) != 0
366 }
367
368 /// Create an iterator that goes through every code in this set.
369 pub fn into_iter(self) -> impl Iterator<Item = Code> {
370 (0..125)
371 .map(Code::from_index)
372 .map(Result::unwrap)
373 .filter(move |code| self.contains(*code))
374 }
375}
376
377impl FromIterator<Code> for Set {
378 /// Create a new code set containing all codes in the iterator.
379 fn from_iter<T: IntoIterator<Item = Code>>(iter: T) -> Self {
380 let mut code_set = Set::empty();
381 for code in iter {
382 code_set.insert(code);
383 }
384 code_set
385 }
386}
387
388impl FromIterator<Set> for Set {
389 fn from_iter<T: IntoIterator<Item = Set>>(iter: T) -> Self {
390 let mut code_set = Set::empty();
391 for new_code_set in iter {
392 code_set = code_set.union_with(new_code_set);
393 }
394 code_set
395 }
396}
397
398#[cfg(test)]
399mod tests {
400 use super::{Code, Set};
401
402 #[test]
403 fn test_code_set() {
404 let code_set = Set::from_closure(|code| code.triangle() == 1);
405 assert!(code_set.contains(Code::from_digits(1, 2, 3).unwrap()));
406 assert!(!code_set.contains(Code::from_digits(3, 2, 1).unwrap()));
407 }
408}