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}