Skip to main content

alphanumeric_stepper/
lib.rs

1/*!
2# Alphanumeric Stepper
3
4A reversible alphanumeric sequence codec for compact serial codes like 000..999, A00..Z99, AA0..ZZ9, and AAA..ZZZ.
5
6## Examples
7
8Encode a number to an Alphanumeric Stepper string.
9
10```rust
11use alphanumeric_stepper::AlphanumericStepper;
12
13let stepper = AlphanumericStepper::<u16>::new(3).unwrap();
14
15assert_eq!("000", stepper.encode(0).unwrap());
16assert_eq!("001", stepper.encode(1).unwrap());
17assert_eq!("999", stepper.encode(999).unwrap());
18assert_eq!("A00", stepper.encode(1000).unwrap());
19assert_eq!("A99", stepper.encode(1099).unwrap());
20assert_eq!("B00", stepper.encode(1100).unwrap());
21assert_eq!("ZZZ", stepper.encode(27935).unwrap());
22```
23
24Decode an Alphanumeric Stepper string to a number.
25
26```rust
27use alphanumeric_stepper::AlphanumericStepper;
28
29let stepper = AlphanumericStepper::<u16>::new(3).unwrap();
30
31assert_eq!(0, stepper.decode("000").unwrap());
32assert_eq!(1, stepper.decode("001").unwrap());
33assert_eq!(999, stepper.decode("999").unwrap());
34assert_eq!(1000, stepper.decode("A00").unwrap());
35assert_eq!(1099, stepper.decode("A99").unwrap());
36assert_eq!(1100, stepper.decode("B00").unwrap());
37assert_eq!(27935, stepper.decode("ZZZ").unwrap());
38```
39*/
40
41#![cfg_attr(not(feature = "std"), no_std)]
42#![cfg_attr(docsrs, feature(doc_cfg))]
43
44mod errors;
45
46extern crate alloc;
47
48use alloc::{string::String, vec::Vec};
49
50pub use errors::*;
51
52/// A reversible alphanumeric sequence codec for compact serial codes like 000..999, A00..Z99, AA0..ZZ9, and AAA..ZZZ.
53#[derive(Debug, Clone, PartialEq, Eq)]
54pub struct AlphanumericStepper<T = u32> {
55    width:       usize,
56    max_numbers: Vec<T>,
57}
58
59impl<T> AlphanumericStepper<T> {
60    /// Returns the width.
61    #[inline]
62    pub const fn width(&self) -> usize {
63        self.width
64    }
65}
66
67impl<T: Copy> AlphanumericStepper<T> {
68    /// Returns the maximum number that can be encoded.
69    #[inline]
70    pub fn max_number(&self) -> T {
71        self.max_numbers[self.width]
72    }
73}
74
75macro_rules! impl_alphanumeric_stepper_backend {
76    ($($integer:ty),+ $(,)?) => {
77        $(
78            impl AlphanumericStepper<$integer> {
79                #[inline]
80                fn pow(base: $integer, exp: usize) -> $integer {
81                    base.pow(exp as u32)
82                }
83
84                #[inline]
85                fn checked_pow(base: $integer, exp: usize) -> Option<$integer> {
86                    let exp_u32: u32 = exp.try_into().ok()?;
87
88                    base.checked_pow(exp_u32)
89                }
90
91                fn push_padded_number_to_vec(
92                    mut number: $integer,
93                    width: usize,
94                    v: &mut Vec<u8>,
95                ) {
96                    debug_assert!(width > 0);
97
98                    let mut divisor = Self::pow(10, width - 1);
99
100                    for _ in 0..width {
101                        let digit = number / divisor;
102
103                        v.push(b'0' + digit as u8);
104
105                        number -= digit * divisor;
106                        divisor /= 10;
107                    }
108                }
109
110                /// Creates a new `AlphanumericStepper` instance with the specified width.
111                #[inline]
112                pub fn new(width: usize) -> Result<Self, AlphanumericStepperBuildError> {
113                    if width == 0 {
114                        return Err(AlphanumericStepperBuildError::InvalidWidth);
115                    }
116
117                    let mut sum: $integer = 0;
118                    let mut term = Self::checked_pow(10, width)
119                        .ok_or(AlphanumericStepperBuildError::InvalidWidth)?;
120                    let mut max_numbers = Vec::with_capacity(width + 1);
121
122                    for alphabet_count in 0..=width {
123                        sum = sum
124                            .checked_add(term)
125                            .ok_or(AlphanumericStepperBuildError::InvalidWidth)?;
126
127                        max_numbers.push(sum - 1);
128
129                        if alphabet_count < width {
130                            term = (term / 10)
131                                .checked_mul(26)
132                                .ok_or(AlphanumericStepperBuildError::InvalidWidth)?;
133                        }
134                    }
135
136                    Ok(Self {
137                        width,
138                        max_numbers,
139                    })
140                }
141
142                /// Encodes a number into a string.
143                #[inline]
144                pub fn encode(&self, number: $integer) -> Result<String, AlphanumericStepperEncodeError> {
145                    if number > self.max_number() {
146                        return Err(AlphanumericStepperEncodeError::NumberOutOfRange);
147                    }
148
149                    let mut s = String::with_capacity(self.width);
150
151                    self.encode_to_vec_inner(number, unsafe { s.as_mut_vec()} );
152
153                    Ok(s)
154                }
155
156                /// Encodes a number and appends the result to a string.
157                #[inline]
158                pub fn encode_to_string(
159                    &self,
160                    number: $integer,
161                    s: &mut String,
162                ) -> Result<(), AlphanumericStepperEncodeError> {
163                    self.encode_to_vec(number, unsafe { s.as_mut_vec()} )
164                }
165
166                /// Encodes a number and appends the result to a byte vector.
167                #[inline]
168                pub fn encode_to_vec(
169                    &self,
170                    number: $integer,
171                    v: &mut Vec<u8>,
172                ) -> Result<(), AlphanumericStepperEncodeError> {
173                    if number > self.max_number() {
174                        return Err(AlphanumericStepperEncodeError::NumberOutOfRange);
175                    }
176
177                    self.encode_to_vec_inner(number, v);
178
179                    Ok(())
180                }
181
182                /// Encodes a number and appends the result to a byte vector.
183                fn encode_to_vec_inner(
184                    &self,
185                    number: $integer,
186                    v: &mut Vec<u8>,
187                ) {
188                    v.reserve(self.width);
189
190                    if number <= self.max_numbers[0] {
191                        Self::push_padded_number_to_vec(number, self.width, v);
192                    } else {
193                        for (alphabet_count, max_number) in
194                            self.max_numbers.iter().copied().enumerate().skip(1)
195                        {
196                            if number > max_number {
197                                continue;
198                            }
199
200                            let digit_count = self.width - alphabet_count;
201                            let mut n = number - self.max_numbers[alphabet_count - 1] - 1;
202                            let digit_base = Self::pow(10, digit_count);
203                            let mut p = digit_base
204                                .wrapping_mul(Self::pow(26, alphabet_count - 1));
205
206                            for _ in 0..alphabet_count {
207                                let d = n / p;
208
209                                v.push(b'A' + d as u8);
210
211                                n -= d * p;
212                                p /= 26;
213                            }
214
215                            if digit_count > 0 {
216                                Self::push_padded_number_to_vec(n, digit_count, v);
217                            }
218
219                            break;
220                        }
221                    }
222                }
223
224                /// Encodes a number and writes the result to an I/O writer.
225                #[cfg(feature = "std")]
226                pub fn encode_to_writer<W>(
227                    &self,
228                    number: $integer,
229                    writer: &mut W,
230                ) -> Result<(), AlphanumericStepperEncodeWriteError>
231                where
232                    W: std::io::Write + ?Sized,
233                {
234                    if number > self.max_number() {
235                        return Err(AlphanumericStepperEncodeWriteError::NumberOutOfRange);
236                    }
237
238                    if number <= self.max_numbers[0] {
239                        std::io::Write::write_fmt(writer, format_args!("{:0>width$}", number, width = self.width))?;
240                    } else {
241                        for (alphabet_count, max_number) in
242                            self.max_numbers.iter().copied().enumerate().skip(1)
243                        {
244                            if number > max_number {
245                                continue;
246                            }
247
248                            let digit_count = self.width - alphabet_count;
249                            let mut n = number - self.max_numbers[alphabet_count - 1] - 1;
250                            let digit_base = Self::pow(10, digit_count);
251                            let mut p = digit_base
252                                .wrapping_mul(Self::pow(26, alphabet_count - 1));
253
254                            for _ in 0..alphabet_count {
255                                let d = n / p;
256
257                                std::io::Write::write_all(writer, &[b'A' + d as u8])?;
258
259                                n -= d * p;
260                                p /= 26;
261                            }
262
263                            if digit_count > 0 {
264                                std::io::Write::write_fmt(writer, format_args!("{:0>width$}", n, width = digit_count))?;
265                            }
266
267                            break;
268                        }
269                    }
270
271                    Ok(())
272                }
273
274                /// Decodes a string into a number.
275                pub fn decode(&self, s: impl AsRef<str>) -> Result<$integer, AlphanumericStepperDecodeError> {
276                    let s = s.as_ref();
277
278                    if s.len() != self.width {
279                        return Err(AlphanumericStepperDecodeError::InvalidLength);
280                    }
281
282                    let bytes = s.as_bytes();
283                    let mut alphabet_count = 0;
284
285                    while alphabet_count < bytes.len() {
286                        let b = bytes[alphabet_count];
287
288                        if b.is_ascii_uppercase() {
289                            alphabet_count += 1;
290                        } else if b.is_ascii_digit() {
291                            break;
292                        } else {
293                            return Err(AlphanumericStepperDecodeError::InvalidCharacter);
294                        }
295                    }
296
297                    for &b in &bytes[alphabet_count..] {
298                        if !b.is_ascii_digit() {
299                            return Err(AlphanumericStepperDecodeError::InvalidCharacter);
300                        }
301                    }
302
303                    let digit_count = self.width - alphabet_count;
304                    let mut number = if alphabet_count == 0 {
305                        0
306                    } else {
307                        self.max_numbers[alphabet_count - 1] + 1
308                    };
309
310                    let mut n: $integer = 0;
311
312                    for &b in &bytes[..alphabet_count] {
313                        n = n
314                            .wrapping_mul(26)
315                            .wrapping_add((b - b'A') as $integer);
316                    }
317
318                    number = number
319                        .wrapping_add(n.wrapping_mul(Self::pow(10, digit_count)));
320
321                    n = 0;
322
323                    for &b in &bytes[alphabet_count..] {
324                        n = n
325                            .wrapping_mul(10)
326                            .wrapping_add((b - b'0') as $integer);
327                    }
328
329                    number = number.wrapping_add(n);
330
331                    Ok(number)
332                }
333            }
334        )+
335    };
336}
337
338impl_alphanumeric_stepper_backend!(u8, u16, u32, u64, u128, usize);