use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, Serialize, Deserialize)]
pub struct RodNumeral {
pub value: i64,
}
impl RodNumeral {
#[must_use]
#[inline]
pub fn new(value: i64) -> Self {
Self { value }
}
}
impl core::fmt::Display for RodNumeral {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
if self.value == 0 {
return write!(f, "[ ]");
}
let abs_val = self.value.unsigned_abs();
let sign = if self.value < 0 { "-" } else { "" };
write!(f, "{sign}[")?;
let digits: Vec<u8> = {
let mut d = Vec::new();
let mut n = abs_val;
if n == 0 {
d.push(0);
} else {
while n > 0 {
d.push((n % 10) as u8);
n /= 10;
}
d.reverse();
}
d
};
for (i, &digit) in digits.iter().enumerate() {
if i > 0 {
write!(f, " ")?;
}
let pos_from_right = digits.len() - 1 - i;
let rod_char = if pos_from_right.is_multiple_of(2) {
'|'
} else {
'-'
};
if digit == 0 {
write!(f, " ")?;
} else {
for _ in 0..digit.min(5) {
write!(f, "{rod_char}")?;
}
if digit > 5 {
write!(f, "+")?;
for _ in 0..(digit - 5) {
write!(f, "{rod_char}")?;
}
}
}
}
write!(f, "]")
}
}
#[must_use]
#[inline]
pub fn rod_add(a: RodNumeral, b: RodNumeral) -> RodNumeral {
RodNumeral::new(a.value.wrapping_add(b.value))
}
#[must_use]
#[inline]
pub fn rod_subtract(a: RodNumeral, b: RodNumeral) -> RodNumeral {
RodNumeral::new(a.value.wrapping_sub(b.value))
}
#[must_use]
#[inline]
pub fn rod_multiply(a: RodNumeral, b: RodNumeral) -> RodNumeral {
RodNumeral::new(a.value.wrapping_mul(b.value))
}
#[must_use]
pub fn chinese_remainder(residues: &[(u64, u64)]) -> Option<u64> {
if residues.is_empty() {
return None;
}
if residues.iter().any(|&(_, m)| m == 0) {
return None;
}
if residues.len() == 1 {
let (r, m) = residues[0];
return Some(r % m);
}
let mut product: u128 = 1;
for &(_, m) in residues {
product = product.checked_mul(u128::from(m))?;
}
let mut sum: u128 = 0;
for &(remainder, modulus) in residues {
let m = u128::from(modulus);
let r = u128::from(remainder);
let p = product / m;
let inv = mod_inverse(p % m, m)?;
sum = (sum + r * p % product * inv % product) % product;
}
u64::try_from(sum % product).ok()
}
fn mod_inverse(a: u128, m: u128) -> Option<u128> {
if m == 1 {
return Some(0);
}
let (mut old_r, mut r) = (a as i128, m as i128);
let (mut old_s, mut s) = (1i128, 0i128);
while r != 0 {
let quotient = old_r / r;
let temp_r = r;
r = old_r - quotient * r;
old_r = temp_r;
let temp_s = s;
s = old_s - quotient * s;
old_s = temp_s;
}
if old_r != 1 {
return None;
}
let result = ((old_s % m as i128) + m as i128) % m as i128;
Some(result as u128)
}
#[must_use]
pub fn magic_square(n: usize) -> Option<Vec<Vec<u64>>> {
if n < 3 || n.is_multiple_of(2) {
return None;
}
if n == 3 {
return Some(vec![vec![2, 7, 6], vec![9, 5, 1], vec![4, 3, 8]]);
}
let mut square = vec![vec![0u64; n]; n];
let mut row = 0;
let mut col = n / 2;
for num in 1..=((n * n) as u64) {
square[row][col] = num;
let new_row = if row == 0 { n - 1 } else { row - 1 };
let new_col = (col + 1) % n;
if square[new_row][new_col] != 0 {
row = (row + 1) % n;
} else {
row = new_row;
col = new_col;
}
}
Some(square)
}
#[must_use]
pub fn is_magic_square(square: &[Vec<u64>]) -> bool {
let n = square.len();
if n == 0 {
return false;
}
if square.iter().any(|row| row.len() != n) {
return false;
}
let magic_sum: u64 = square[0].iter().sum();
for row in square {
let s: u64 = row.iter().sum();
if s != magic_sum {
return false;
}
}
for col in 0..n {
let s: u64 = square.iter().map(|row| row[col]).sum();
if s != magic_sum {
return false;
}
}
let s: u64 = (0..n).map(|i| square[i][i]).sum();
if s != magic_sum {
return false;
}
let s: u64 = (0..n).map(|i| square[i][n - 1 - i]).sum();
if s != magic_sum {
return false;
}
true
}
#[cfg(feature = "varna")]
#[must_use = "returns the Unicode rod numeral string without side effects"]
pub fn to_unicode_rods(n: u64) -> crate::error::Result<String> {
if n == 0 {
return Err(crate::error::SankhyaError::InvalidBase(
"zero has no rod numeral representation".into(),
));
}
let system = varna::script::numerals::chinese_rod_numerals();
let mut digits = Vec::new();
let mut remaining = n;
while remaining > 0 {
let d = (remaining % 10) as u32;
if d == 0 {
digits.push(" ".to_string());
} else if let Some(ch) = system.char_for(d) {
digits.push(ch.to_string());
}
remaining /= 10;
}
digits.reverse();
Ok(digits.join(""))
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rod_arithmetic() {
let a = RodNumeral::new(42);
let b = RodNumeral::new(17);
assert_eq!(rod_add(a, b).value, 59);
assert_eq!(rod_subtract(a, b).value, 25);
assert_eq!(rod_multiply(a, b).value, 714);
}
#[test]
fn crt_sun_tzu_problem() {
let result = chinese_remainder(&[(2, 3), (3, 5), (2, 7)]);
assert_eq!(result, Some(23));
}
#[test]
fn crt_single() {
assert_eq!(chinese_remainder(&[(3, 7)]), Some(3));
}
#[test]
fn crt_empty() {
assert_eq!(chinese_remainder(&[]), None);
}
#[test]
fn lo_shu_magic_square() {
let sq = magic_square(3).unwrap();
assert!(is_magic_square(&sq));
let sum: u64 = sq[0].iter().sum();
assert_eq!(sum, 15);
}
#[test]
fn magic_square_5x5() {
let sq = magic_square(5).unwrap();
assert!(is_magic_square(&sq));
let sum: u64 = sq[0].iter().sum();
assert_eq!(sum, 65);
}
#[test]
fn magic_square_even_returns_none() {
assert!(magic_square(4).is_none());
}
#[cfg(feature = "varna")]
mod unicode_rod_tests {
use super::*;
#[test]
fn unicode_rods_single_digit() {
assert_eq!(to_unicode_rods(1).unwrap(), "𝍠");
assert_eq!(to_unicode_rods(9).unwrap(), "𝍨");
}
#[test]
fn unicode_rods_multi_digit() {
let s = to_unicode_rods(42).unwrap();
assert_eq!(s, "𝍣𝍡");
}
#[test]
fn unicode_rods_with_zero() {
let s = to_unicode_rods(101).unwrap();
assert_eq!(s, "𝍠 𝍠");
}
#[test]
fn unicode_rods_zero_errors() {
assert!(to_unicode_rods(0).is_err());
}
}
}