beetle_collatz/
lib.rs

1//! A collection of functions relating to the Collatz conjecture
2
3pub mod traits;
4use std::ops::{Add, Shr};
5
6use beetle_nonzero::NonZero;
7use num::{Integer, One, PrimInt};
8pub use traits::*;
9
10impl<T> TwoThree for T where T: PrimInt + Integer + One + Add<Output = T> {}
11
12impl<T> Rules for NonZero<T>
13where
14    T: PrimInt + Integer + Shr<u32, Output = T>,
15{
16    fn odd_rule(self) -> Self {
17        unsafe { self.map_unchecked(|x| x * T::three() + T::one()) }
18    }
19
20    fn even_rule(self) -> Self {
21        unsafe { self.map_unchecked(|x| x / T::two()) }
22    }
23
24    fn rules(self) -> Self {
25        if self.is_odd() {
26            self.odd_rule()
27        } else {
28            self.even_rule()
29        }
30    }
31
32    fn rules_halve_odds(self) -> Self {
33        if self.is_odd() {
34            self.odd_rule().even_rule()
35        } else {
36            self.even_rule()
37        }
38    }
39
40    fn rules_remove_trailing_zeros(self) -> Self {
41        let x = if self.is_odd() { self.odd_rule() } else { self };
42        x.without_trailing_zeros()
43    }
44}
45
46impl<T> Steps for NonZero<T>
47where
48    T: PrimInt + Integer + Shr<u32, Output = T>,
49{
50    fn steps_to_one(self) -> u64 {
51        if self.is_even() {
52            self.steps_to_one_for_even_number()
53        } else {
54            self.steps_to_one_for_odd_number()
55        }
56    }
57
58    fn steps_to_one_for_even_number(self) -> u64 {
59        let steps_to_become_odd: u64 = self.trailing_zeros().into();
60        let without_zeros = self.without_trailing_zeros();
61        steps_to_become_odd + without_zeros.steps_to_one_for_odd_number()
62    }
63
64    fn steps_to_one_for_odd_number(self) -> u64 {
65        let mut steps: u64 = 0;
66        let mut n = self;
67        while !n.get().is_one() {
68            // Number is known to be odd here,
69            // so apply odd rule,
70            n = n.odd_rule();
71            steps += 1;
72
73            // After the odd rule is applied, the resulting number is always even,
74            // so remove trailing zeros,
75            // and count each trailing zeros as a step
76            let tz: u64 = n.trailing_zeros().into();
77            n = n.without_trailing_zeros();
78            steps += tz;
79        }
80        steps
81    }
82
83    fn steps_to_decrease(self) -> u64 {
84        if self.is_even() {
85            1
86        } else {
87            self.steps_to_decrease_for_odd_number()
88        }
89    }
90
91    fn steps_to_decrease_for_odd_number(self) -> u64 {
92        let mut n = self;
93        let mut steps: u64 = 0;
94
95        n = n.odd_rule();
96        steps += u64::from(n.trailing_zeros()) + 1;
97        n = n.without_trailing_zeros();
98
99        let starting_value = self;
100        while n > starting_value {
101            n = n.odd_rule();
102            steps += u64::from(n.trailing_zeros()) + 1;
103            n = n.without_trailing_zeros();
104        }
105
106        steps
107    }
108}
109
110impl<T> Transformations for NonZero<T>
111where
112    T: Integer + PrimInt + Shr<u32, Output = T>,
113{
114    fn transformations_to_one(&self) -> Vec<Self> {
115        let mut v: Vec<Self> = Vec::new();
116        let mut n: Self = *self;
117        v.push(n);
118        while !n.get().is_one() {
119            n = n.rules();
120            v.push(n);
121        }
122        v
123    }
124}
125
126#[allow(clippy::unwrap_used, unused_imports)]
127mod tests {
128    use beetle_nonzero::NonZero;
129
130    use crate::{Steps, TwoThree};
131
132    // Number of steps to reach 1 for integers 1..=72 (according to the Online Encyclopedia of Integer Sequences)
133    // For some reason this is counted as dead code currently, but it isn't dead code, I just convert it to a vec, or iter when using it.
134    #[allow(dead_code)]
135    const OEIS_STEPS: [u64; 72] = [
136        0, 1, 7, 2, 5, 8, 16, 3, 19, 6, 14, 9, 9, 17, 17, 4, 12, 20, 20, 7, 7, 15, 15, 10, 23, 10,
137        111, 18, 18, 18, 106, 5, 26, 13, 13, 21, 21, 21, 34, 8, 109, 8, 29, 16, 16, 16, 104, 11,
138        24, 24, 24, 11, 11, 112, 112, 19, 32, 19, 32, 19, 19, 107, 107, 6, 27, 27, 27, 14, 14, 14,
139        102, 22,
140    ];
141
142    #[test]
143    fn step_counts_for_integers_are_correct() {
144        use crate::traits::Steps;
145        use beetle_nonzero::NonZero;
146        use num::One;
147
148        // u8
149        let one: NonZero<u8> = unsafe { NonZero::new_unchecked(1) };
150        assert_eq!(one.steps_to_one(), 0);
151
152        // u16
153        let one: NonZero<u16> = unsafe { NonZero::new_unchecked(1) };
154        assert_eq!(one.steps_to_one(), 0);
155
156        // u32
157        let one: NonZero<u32> = unsafe { NonZero::new_unchecked(1) };
158        assert_eq!(one.steps_to_one(), 0);
159
160        // u64
161        let one: NonZero<u64> = unsafe { NonZero::new_unchecked(1) };
162        assert_eq!(one.steps_to_one(), 0);
163
164        // u128
165        let one: NonZero<u128> = unsafe { NonZero::new_unchecked(1) };
166        assert_eq!(one.steps_to_one(), 0);
167    }
168
169    #[test]
170    fn step_counts_for_ranges_are_correct() {
171        let start: u32 = 1;
172        let stop: u32 = 73;
173        let steps: Vec<u64> = (start..stop)
174            .map(|n| unsafe { NonZero::new_unchecked(n).steps_to_one() })
175            .collect();
176        assert_eq!(steps, OEIS_STEPS.to_vec());
177
178        // BigUint
179        let start: u32 = 1;
180        let stop: u32 = 73;
181
182        let steps: Vec<u64> = (start..stop)
183            .map(|n| unsafe { NonZero::new_unchecked(n).steps_to_one() })
184            .collect();
185        assert_eq!(steps, OEIS_STEPS.to_vec());
186    }
187
188    #[test]
189    fn transformations_for_numbers_are_correct() {
190        use crate::Transformations;
191        let n: NonZero<u32> = unsafe { NonZero::new_unchecked(4u32) };
192        let transforms = n.transformations_to_one();
193        let expected_transformations: Vec<NonZero<u32>> = [4, 2, 1]
194            .into_iter()
195            .map(|x: u32| unsafe { NonZero::new_unchecked(x) })
196            .collect();
197        assert_eq!(transforms, expected_transformations);
198    }
199}