use crate::gen::Gen;
#[derive(Clone, PartialEq, Eq, Hash)]
pub struct Word {
gens: Vec<Gen>,
}
impl Word {
pub fn scalar() -> Word {
Word { gens: vec![] }
}
pub fn gen(g: Gen) -> Word {
Word { gens: vec![g] }
}
pub fn from_gens(gens: &[Gen]) -> Word {
Word {
gens: gens.to_vec(),
}
}
pub fn mul(&self, other: &Word) -> Word {
let mut gens = self.gens.clone();
gens.extend_from_slice(&other.gens);
Word { gens }
}
pub fn is_scalar(&self) -> bool {
self.gens.is_empty()
}
pub fn grade(&self) -> usize {
self.gens.len()
}
pub fn generators(&self) -> &[Gen] {
&self.gens
}
pub fn head(&self) -> Option<Gen> {
self.gens.first().copied()
}
pub fn last(&self) -> Option<Gen> {
self.gens.last().copied()
}
pub fn find_subword(&self, sub: &Word) -> Option<usize> {
if sub.gens.is_empty() {
return Some(0);
}
self.gens
.windows(sub.gens.len())
.position(|w| w == sub.gens.as_slice())
}
pub fn is_sorted(&self) -> bool {
self.gens.windows(2).all(|w| w[0] <= w[1])
}
pub fn reverse_sign(&self) -> (Word, bool) {
let k = self.grade();
let sign_flip = (k * k.wrapping_sub(1) / 2) % 2 == 1;
let rev: Vec<Gen> = self.gens.iter().rev().copied().collect();
(Word { gens: rev }, sign_flip)
}
}
impl PartialOrd for Word {
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
Some(self.cmp(other))
}
}
impl Ord for Word {
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
self.gens
.len()
.cmp(&other.gens.len())
.then_with(|| self.gens.cmp(&other.gens))
}
}
impl std::fmt::Debug for Word {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
if self.gens.is_empty() {
write!(f, "1")
} else {
let parts: Vec<String> = self.gens.iter().map(|g| format!("{g}")).collect();
write!(f, "{}", parts.join("·"))
}
}
}
impl std::fmt::Display for Word {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
write!(f, "{:?}", self)
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::trit::{N, P};
fn ei(idx: u32) -> Gen {
Gen::imaginary(idx)
}
fn eh(idx: u32) -> Gen {
Gen::hyperbolic(idx)
}
#[test]
fn scalar_is_grade0() {
let s = Word::scalar();
assert!(s.is_scalar());
assert_eq!(s.grade(), 0);
}
#[test]
fn single_gen_grade1() {
let w = Word::gen(ei(0));
assert_eq!(w.grade(), 1);
assert_eq!(w.generators(), &[ei(0)]);
}
#[test]
fn free_product_concatenates() {
let e1 = Word::gen(ei(0));
let e2 = Word::gen(ei(1));
let e1e2 = e1.mul(&e2);
assert_eq!(e1e2.grade(), 2);
assert_eq!(e1e2.generators(), &[ei(0), ei(1)]);
}
#[test]
fn scalar_is_identity() {
let e1 = Word::gen(ei(0));
let s = Word::scalar();
assert_eq!(e1.mul(&s), e1);
assert_eq!(s.mul(&e1), e1);
}
#[test]
fn find_subword() {
let w = Word::from_gens(&[ei(0), ei(1), ei(2)]);
let sub = Word::from_gens(&[ei(1), ei(2)]);
assert_eq!(w.find_subword(&sub), Some(1));
assert_eq!(w.find_subword(&Word::gen(ei(3))), None);
}
#[test]
fn grade_ordering() {
let e1 = Word::gen(ei(0));
let e2 = Word::gen(ei(1));
let e1e2 = e1.mul(&e2);
assert!(e1 < e1e2); assert!(e2 < e1e2);
}
#[test]
fn type_flows_into_word() {
let w = Word::from_gens(&[ei(0), Gen::degenerate(0), eh(0)]);
assert_eq!(w.generators()[0].signature(), N);
assert_eq!(w.generators()[1].signature(), crate::trit::Z);
assert_eq!(w.generators()[2].signature(), P);
}
}