alphanumeric_stepper/
lib.rs1#![cfg_attr(not(feature = "std"), no_std)]
52#![cfg_attr(docsrs, feature(doc_cfg))]
53
54mod errors;
55
56extern crate alloc;
57
58use alloc::{string::String, vec::Vec};
59use core::fmt::Write;
60
61pub use errors::*;
62
63#[derive(Debug, Clone, PartialEq, Eq)]
64pub struct AlphanumericStepper<T = u128> {
65 width: usize,
66 max_numbers: Vec<T>,
67}
68
69impl<T> AlphanumericStepper<T> {
70 #[inline]
72 pub const fn width(&self) -> usize {
73 self.width
74 }
75}
76
77impl<T: Copy> AlphanumericStepper<T> {
78 #[inline]
80 pub fn max_number(&self) -> T {
81 self.max_numbers[self.width]
82 }
83}
84
85macro_rules! impl_alphanumeric_stepper_backend {
86 ($($integer:ty),+ $(,)?) => {
87 $(
88 impl AlphanumericStepper<$integer> {
89 #[inline]
90 fn checked_pow(base: $integer, exp: usize) -> Option<$integer> {
91 let mut value: $integer = 1;
92
93 for _ in 0..exp {
94 value = value.checked_mul(base)?;
95 }
96
97 Some(value)
98 }
99
100 #[inline]
102 pub fn new(width: usize) -> Result<Self, AlphanumericStepperBuildError> {
103 if width == 0 {
104 return Err(AlphanumericStepperBuildError::InvalidWidth);
105 }
106
107 let mut max_numbers = Vec::with_capacity(width + 1);
108 let mut sum: $integer = 0;
109 let mut term = Self::checked_pow(10, width)
110 .ok_or(AlphanumericStepperBuildError::InvalidWidth)?;
111
112 for alphabet_count in 0..=width {
113 sum = sum
114 .checked_add(term)
115 .ok_or(AlphanumericStepperBuildError::InvalidWidth)?;
116
117 max_numbers.push(sum - 1);
118
119 if alphabet_count < width {
120 term = (term / 10)
121 .checked_mul(26)
122 .ok_or(AlphanumericStepperBuildError::InvalidWidth)?;
123 }
124 }
125
126 Ok(Self {
127 width,
128 max_numbers,
129 })
130 }
131
132 pub fn encode(&self, number: $integer) -> Result<String, AlphanumericStepperEncodeError> {
134 if number > self.max_number() {
135 return Err(AlphanumericStepperEncodeError::NumberOutOfRange);
136 }
137
138 let mut s = String::with_capacity(self.width);
139
140 if number <= self.max_numbers[0] {
141 s.write_fmt(format_args!("{:0>width$}", number, width = self.width)).unwrap();
142 } else {
143 for (alphabet_count, max_number) in
144 self.max_numbers.iter().copied().enumerate().skip(1)
145 {
146 if number > max_number {
147 continue;
148 }
149
150 let digit_count = self.width - alphabet_count;
151 let mut n = number - self.max_numbers[alphabet_count - 1] - 1;
152 let digit_base = Self::checked_pow(10, digit_count).unwrap();
153 let mut p = digit_base
154 .checked_mul(Self::checked_pow(26, alphabet_count - 1).unwrap())
155 .unwrap();
156
157 for _ in 0..alphabet_count {
158 let d = n / p;
159
160 s.push((b'A' + d as u8) as char);
161
162 n -= d * p;
163 p /= 26;
164 }
165
166 if digit_count > 0 {
167 s.write_fmt(format_args!("{:0>width$}", n, width = digit_count)).unwrap();
168 }
169
170 break;
171 }
172 }
173
174 Ok(s)
175 }
176
177 pub fn decode(&self, s: impl AsRef<str>) -> Result<$integer, AlphanumericStepperDecodeError> {
179 let s = s.as_ref();
180
181 if s.len() != self.width {
182 return Err(AlphanumericStepperDecodeError::InvalidLength);
183 }
184
185 let bytes = s.as_bytes();
186 let mut alphabet_count = 0;
187
188 while alphabet_count < bytes.len() {
189 let b = bytes[alphabet_count];
190
191 if b.is_ascii_uppercase() {
192 alphabet_count += 1;
193 } else if b.is_ascii_digit() {
194 break;
195 } else {
196 return Err(AlphanumericStepperDecodeError::InvalidCharacter);
197 }
198 }
199
200 for &b in &bytes[alphabet_count..] {
201 if !b.is_ascii_digit() {
202 return Err(AlphanumericStepperDecodeError::InvalidCharacter);
203 }
204 }
205
206 let digit_count = self.width - alphabet_count;
207 let mut number = if alphabet_count == 0 {
208 0
209 } else {
210 self.max_numbers[alphabet_count - 1] + 1
211 };
212
213 let mut n: $integer = 0;
214
215 for &b in &bytes[..alphabet_count] {
216 n = n
217 .checked_mul(26)
218 .and_then(|value| value.checked_add((b - b'A') as $integer))
219 .unwrap();
220 }
221
222 number = number
223 .checked_add(n.checked_mul(Self::checked_pow(10, digit_count).unwrap()).unwrap())
224 .unwrap();
225
226 n = 0;
227
228 for &b in &bytes[alphabet_count..] {
229 n = n
230 .checked_mul(10)
231 .and_then(|value| value.checked_add((b - b'0') as $integer))
232 .unwrap();
233 }
234
235 number = number.checked_add(n).unwrap();
236
237 Ok(number)
238 }
239 }
240 )+
241 };
242}
243
244impl_alphanumeric_stepper_backend!(u8, u16, u32, u64, u128, usize);