use crate::error::{Error, Result};
use std::fmt;
use std::str::FromStr;
const BASE: u8 = 36;
#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Hash)]
pub struct Rank(String);
impl Rank {
#[must_use]
pub fn as_str(&self) -> &str {
&self.0
}
pub fn parse(s: &str) -> Result<Rank> {
let bytes = s.as_bytes();
let valid = !bytes.is_empty()
&& bytes.iter().all(|&b| digit_value(b).is_some())
&& bytes.last() != Some(&b'0');
if !valid {
return Err(Error::InvalidRank(s.to_string()));
}
Ok(Rank(s.to_string()))
}
#[must_use = "handle an invalid rank bound"]
pub fn between(lo: Option<&Rank>, hi: Option<&Rank>) -> Result<Rank> {
if matches!((lo, hi), (Some(l), Some(h)) if l >= h) {
let lower = lo.map_or_else(|| "<open>".to_string(), ToString::to_string);
let upper = hi.map_or_else(|| "<open>".to_string(), ToString::to_string);
return Err(Error::InvalidRank(format!(
"between requires lo < hi (lo={lower:?}, hi={upper:?})"
)));
}
Ok(Self::between_unchecked(lo, hi))
}
fn between_unchecked(lo: Option<&Rank>, hi: Option<&Rank>) -> Rank {
let lo_digits: Vec<u8> = lo.map(|r| digits_of(r.as_str())).unwrap_or_default();
let (hi_int, hi_digits): (u8, Vec<u8>) = match hi {
Some(r) => (0, digits_of(r.as_str())),
None => (1, Vec::new()),
};
let (int_sum, frac_sum) = add_frac(&lo_digits, &hi_digits, hi_int);
let mut mid = half_frac(int_sum, &frac_sum);
while mid.last() == Some(&0) {
mid.pop();
}
Rank(digits_to_string(&mid))
}
#[must_use]
pub fn after(prev: Option<&Rank>) -> Rank {
let digits = match prev {
None => vec![BASE / 2],
Some(r) => {
let mut d = digits_of(r.as_str());
match d.iter().rposition(|&v| v < BASE - 1) {
Some(i) => {
d[i] += 1;
d.truncate(i + 1);
d
}
None => {
d.push(BASE / 2);
d
}
}
}
};
Rank(digits_to_string(&digits))
}
#[must_use]
pub fn before(next: Option<&Rank>) -> Rank {
Self::between_unchecked(None, next)
}
#[must_use]
pub fn rebalance(count: usize) -> Vec<Rank> {
if count == 0 {
return Vec::new();
}
let (width, capacity) = rebalance_layout(count);
let count_u128 = count as u128;
let mut out = Vec::with_capacity(count);
for index in 0..count {
let ordinal = ((index as u128 + 1) * capacity) / (count_u128 + 1);
out.push(rank_at_ordinal(ordinal, width));
}
out
}
}
fn rebalance_layout(count: usize) -> (usize, u128) {
let target = count as u128;
let mut width = 1;
let mut prefix_space = 1u128;
loop {
let capacity = prefix_space.saturating_mul(u128::from(BASE - 1));
if capacity >= target {
return (width, capacity);
}
prefix_space = prefix_space.saturating_mul(u128::from(BASE));
width += 1;
}
}
fn rank_at_ordinal(ordinal: u128, width: usize) -> Rank {
let last_digit = ordinal % u128::from(BASE - 1) + 1;
let mut prefix = ordinal / u128::from(BASE - 1);
let mut digits = vec![0; width];
for position in (0..width - 1).rev() {
digits[position] = (prefix % u128::from(BASE)) as u8;
prefix /= u128::from(BASE);
}
digits[width - 1] = last_digit as u8;
Rank(digits_to_string(&digits))
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
pub struct RankStats {
pub count: usize,
pub max_len: usize,
pub total_len: usize,
}
impl RankStats {
pub fn collect<'a, I>(ranks: I) -> RankStats
where
I: IntoIterator<Item = &'a Rank>,
{
let mut stats = RankStats::default();
for rank in ranks {
let len = rank.as_str().len();
stats.count += 1;
stats.total_len += len;
stats.max_len = stats.max_len.max(len);
}
stats
}
#[must_use]
pub fn average_len(&self) -> f64 {
if self.count == 0 {
0.0
} else {
self.total_len as f64 / self.count as f64
}
}
#[must_use]
pub fn should_rebalance(&self, max_len_threshold: usize) -> bool {
self.max_len > max_len_threshold
}
}
impl fmt::Display for Rank {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(&self.0)
}
}
impl FromStr for Rank {
type Err = Error;
fn from_str(s: &str) -> Result<Self> {
Rank::parse(s)
}
}
fn digits_to_string(digits: &[u8]) -> String {
digits.iter().map(|&v| value_digit(v) as char).collect()
}
fn digit_value(c: u8) -> Option<u8> {
match c {
b'0'..=b'9' => Some(c - b'0'),
b'a'..=b'z' => Some(c - b'a' + 10),
_ => None,
}
}
fn value_digit(v: u8) -> u8 {
const DIGITS: &[u8; 36] = b"0123456789abcdefghijklmnopqrstuvwxyz";
DIGITS[v as usize]
}
fn digits_of(s: &str) -> Vec<u8> {
s.bytes().filter_map(digit_value).collect()
}
fn add_frac(a: &[u8], b: &[u8], b_int: u8) -> (u8, Vec<u8>) {
let n = a.len().max(b.len());
let mut frac = vec![0u8; n];
let mut carry = 0u8;
for i in (0..n).rev() {
let da = a.get(i).copied().unwrap_or(0);
let db = b.get(i).copied().unwrap_or(0);
let sum = da + db + carry;
frac[i] = sum % BASE;
carry = sum / BASE;
}
(b_int + carry, frac)
}
fn half_frac(int: u8, frac: &[u8]) -> Vec<u8> {
debug_assert!(int / 2 == 0, "rank value out of expected range");
let mut out = Vec::with_capacity(frac.len() + 1);
let mut rem = int % 2;
for &d in frac {
let cur = rem * BASE + d;
out.push(cur / 2);
rem = cur % 2;
}
if rem != 0 {
out.push(BASE / 2);
}
out
}
#[cfg(test)]
mod tests {
use super::*;
use proptest::prelude::*;
proptest! {
#[test]
fn generated_ranks_preserve_canonical_order_and_uniqueness(steps in 1usize..200) {
let mut ranks = Vec::with_capacity(steps);
let mut previous = None;
for _ in 0..steps {
let next = Rank::after(previous.as_ref());
prop_assert!(Rank::parse(next.as_str()).is_ok());
if let Some(previous) = previous {
prop_assert!(previous < next);
}
ranks.push(next.clone());
previous = Some(next);
}
let unique = ranks.iter().collect::<std::collections::HashSet<_>>();
prop_assert_eq!(unique.len(), ranks.len());
}
}
fn r(s: &str) -> Rank {
Rank::parse(s).expect("valid rank")
}
#[test]
fn parse_accepts_base36_and_rejects_others() {
assert!(Rank::parse("i").is_ok());
assert!(Rank::parse("0z9a").is_ok());
assert!(matches!(Rank::parse(""), Err(Error::InvalidRank(_))));
assert!(matches!(Rank::parse("AB"), Err(Error::InvalidRank(_)))); assert!(matches!(Rank::parse("a-b"), Err(Error::InvalidRank(_))));
assert!(matches!(Rank::parse("0"), Err(Error::InvalidRank(_))));
assert!(matches!(Rank::parse("000"), Err(Error::InvalidRank(_))));
assert!(Rank::parse("01").is_ok()); }
#[test]
fn parse_rejects_trailing_zero_for_canonical_form() {
assert!(Rank::parse("1").is_ok());
assert!(matches!(Rank::parse("10"), Err(Error::InvalidRank(_))));
assert!(matches!(Rank::parse("100"), Err(Error::InvalidRank(_))));
assert!(matches!(Rank::parse("iz0"), Err(Error::InvalidRank(_))));
assert!(Rank::parse("101").is_ok());
}
#[test]
fn generated_ranks_are_always_canonical() {
let a = Rank::between(None, None).expect("open bounds produce a rank");
let b = Rank::after(Some(&a));
let c = Rank::before(Some(&a));
for g in [&a, &b, &c] {
assert!(
Rank::parse(g.as_str()).is_ok(),
"generated rank must be canonical: {g}"
);
}
}
#[test]
fn display_roundtrips() {
assert_eq!(r("1i").to_string(), "1i");
assert_eq!("1i".parse::<Rank>().unwrap(), r("1i"));
}
#[test]
fn first_rank_is_deterministic_middle() {
assert_eq!(
Rank::between(None, None).expect("open bounds produce a rank"),
r("i")
);
}
#[test]
fn after_produces_strictly_greater() {
let a = Rank::after(None);
let b = Rank::after(Some(&a));
let c = Rank::after(Some(&b));
assert!(a < b, "{a} < {b}");
assert!(b < c, "{b} < {c}");
}
#[test]
fn before_produces_strictly_smaller() {
let a = Rank::between(None, None).expect("open bounds produce a rank");
let b = Rank::between(None, Some(&a)).expect("valid ascending bounds");
assert!(b < a, "{b} < {a}");
}
#[test]
fn before_api_is_symmetric_to_after() {
assert_eq!(Rank::before(None), Rank::after(None));
assert_eq!(Rank::before(None), r("i"));
let mut next = Rank::before(None);
for _ in 0..50 {
let prev = Rank::before(Some(&next));
assert!(prev < next, "{prev} < {next}");
next = prev;
}
}
#[test]
fn between_is_strictly_ordered() {
let lo = r("1");
let hi = r("2");
let mid = Rank::between(Some(&lo), Some(&hi)).expect("valid ascending bounds");
assert!(lo < mid && mid < hi, "{lo} < {mid} < {hi}");
}
#[test]
fn between_rejects_violated_precondition() {
let r = Rank::between(None, None).expect("open bounds produce a rank"); let error = Rank::between(Some(&r), Some(&r)).expect_err("equal bounds are rejected");
assert!(matches!(error, Error::InvalidRank(message) if message.contains("lo < hi")));
}
#[test]
fn repeated_bisection_stays_between_and_unique() {
let lo = Rank::between(None, None).expect("open bounds produce a rank"); let hi = Rank::after(Some(&lo)); let mut left = lo.clone();
let mut seen = std::collections::HashSet::new();
seen.insert(lo.clone());
seen.insert(hi.clone());
for n in 0..100 {
let mid = Rank::between(Some(&left), Some(&hi)).expect("valid ascending bounds");
assert!(left < mid && mid < hi, "iter {n}: {left} < {mid} < {hi}");
assert!(seen.insert(mid.clone()), "iter {n}: duplicate {mid}");
left = mid;
}
}
#[test]
fn sequential_appends_sort_in_insertion_order() {
let mut ranks = Vec::new();
let mut prev: Option<Rank> = None;
for _ in 0..50 {
let next = Rank::after(prev.as_ref());
ranks.push(next.clone());
prev = Some(next);
}
let mut sorted = ranks.clone();
sorted.sort();
assert_eq!(ranks, sorted, "append order must equal sorted order");
let unique: std::collections::HashSet<_> = ranks.iter().collect();
assert_eq!(unique.len(), ranks.len());
}
#[test]
fn append_growth_is_bounded_over_many_appends() {
let mut prev: Option<Rank> = None;
let mut ranks: Vec<Rank> = Vec::with_capacity(1000);
let mut max_len = 0usize;
for _ in 0..1000 {
let next = Rank::after(prev.as_ref());
max_len = max_len.max(next.as_str().len());
ranks.push(next.clone());
prev = Some(next);
}
for w in ranks.windows(2) {
assert!(
w[0] < w[1],
"append must be strictly increasing: {} < {}",
w[0],
w[1]
);
}
let unique: std::collections::HashSet<_> = ranks.iter().collect();
assert_eq!(unique.len(), ranks.len(), "appended ranks must be unique");
assert!(
max_len <= 70,
"append growth not bounded: max_len = {max_len}"
);
}
#[test]
fn rebalance_preserves_order_and_shortens_ranks() {
let lo = Rank::between(None, None).expect("open bounds produce a rank");
let hi = Rank::after(Some(&lo));
let mut left = lo.clone();
let mut bloated = Vec::new();
for _ in 0..500 {
let mid = Rank::between(Some(&left), Some(&hi)).expect("valid ascending bounds");
bloated.push(mid.clone());
left = mid;
}
let bloated_max = bloated.iter().map(|r| r.as_str().len()).max().unwrap();
let balanced = Rank::rebalance(bloated.len());
assert_eq!(balanced.len(), bloated.len(), "same cardinality");
for w in balanced.windows(2) {
assert!(w[0] < w[1], "rebalanced ranks must be sorted");
}
let unique: std::collections::HashSet<_> = balanced.iter().collect();
assert_eq!(
unique.len(),
balanced.len(),
"rebalanced ranks must be unique"
);
let balanced_max = balanced.iter().map(|r| r.as_str().len()).max().unwrap();
assert!(
balanced_max < bloated_max,
"rebalance must shorten: {balanced_max} < {bloated_max}"
);
assert!(balanced_max <= 70, "rebalanced max_len = {balanced_max}");
}
#[test]
fn rebalance_zero_is_empty() {
assert!(Rank::rebalance(0).is_empty());
}
#[test]
fn rebalance_uses_minimal_fixed_width_even_spacing() {
for (count, expected_width) in [(0, 0), (1, 1), (36, 2), (1_000, 2)] {
let ranks = Rank::rebalance(count);
let stats = RankStats::collect(ranks.iter());
assert_eq!(stats.count, count);
assert_eq!(stats.max_len, expected_width);
assert_eq!(stats.total_len, count * expected_width);
assert_eq!(stats.average_len(), expected_width as f64);
assert!(
ranks
.iter()
.all(|rank| rank.as_str().len() == expected_width),
"all ranks for {count} items must use one fixed width"
);
assert!(
ranks.iter().all(|rank| Rank::parse(rank.as_str()).is_ok()),
"all rebalanced ranks must remain canonical"
);
assert!(
ranks.windows(2).all(|window| window[0] < window[1]),
"rebalanced ranks must be strictly increasing"
);
}
}
#[test]
fn rank_stats_reports_max_and_average() {
let ranks = vec![r("i"), r("zz"), r("1")]; let stats = RankStats::collect(&ranks);
assert_eq!(stats.count, 3);
assert_eq!(stats.max_len, 2);
assert_eq!(stats.total_len, 4);
assert!((stats.average_len() - 4.0 / 3.0).abs() < 1e-9);
assert!(stats.should_rebalance(1), "max_len 2 > 1");
assert!(!stats.should_rebalance(2), "max_len 2 not > 2");
let empty = RankStats::collect(std::iter::empty::<&Rank>());
assert_eq!(empty.count, 0);
assert_eq!(empty.average_len(), 0.0);
assert!(!empty.should_rebalance(0));
}
#[test]
fn generated_ranks_have_no_trailing_zero() {
for _ in 0..20 {
let a = Rank::between(None, None).expect("open bounds produce a rank");
let b = Rank::between(None, Some(&a)).expect("valid ascending bounds");
assert_ne!(b.as_str().as_bytes().last(), Some(&b'0'));
}
}
struct Lcg(u64);
impl Lcg {
fn next_usize(&mut self, bound: usize) -> usize {
self.0 = self.0.wrapping_mul(6364136223846793005).wrapping_add(1);
((self.0 >> 33) as usize) % bound
}
}
#[test]
fn between_property_holds_for_random_insertions() {
let mut rng = Lcg(0x9E3779B97F4A7C15);
let mut ranks = vec![Rank::after(None)];
ranks.push(Rank::after(Some(&ranks[0])));
ranks.push(Rank::after(Some(&ranks[1])));
for _ in 0..2_000 {
let i = rng.next_usize(ranks.len() - 1);
let (lo, hi) = (ranks[i].clone(), ranks[i + 1].clone());
let mid = Rank::between(Some(&lo), Some(&hi)).expect("valid ascending bounds");
assert!(lo < mid && mid < hi, "expected {lo} < {mid} < {hi}");
assert_eq!(
Rank::parse(mid.as_str()),
Ok(mid.clone()),
"{mid} canonical"
);
ranks.insert(i + 1, mid);
}
for pair in ranks.windows(2) {
assert!(pair[0] < pair[1], "order broke: {} !< {}", pair[0], pair[1]);
}
let unique: std::collections::BTreeSet<&str> = ranks.iter().map(Rank::as_str).collect();
assert_eq!(unique.len(), ranks.len(), "ranks must stay unique");
}
}