Skip to content

Commit

Permalink
Begin documenting more functions; remove needless generics
Browse files Browse the repository at this point in the history
  • Loading branch information
inthar-raven committed Aug 12, 2024
1 parent 5721c69 commit 78787cb
Showing 1 changed file with 68 additions and 54 deletions.
122 changes: 68 additions & 54 deletions src/words.rs
Original file line number Diff line number Diff line change
Expand Up @@ -282,6 +282,7 @@ where
}
result
}

/// Whether `scale` as a word is strict variety.
pub fn is_strict_variety<T>(scale: &[T]) -> bool
where
Expand Down Expand Up @@ -341,8 +342,9 @@ pub fn darkest_mos_mode_and_gen_bresenham(
.expect("The dark generator is a (|L|⁻¹ mod |scale|)-step, since stacking it |L| times results in the s step (mod period).")
as usize;
let mut result_scale: Vec<usize> = vec![];
let (mut current_x, mut current_y) = (0usize, 0usize); // Start from the (0, 0) and walk until the dark generator is reached; we now know how many steps to walk.
while current_x < a || current_y < b {
// Start from the origin (0, 0)
let (mut current_x, mut current_y) = (0usize, 0usize);
while (current_x, current_y) != (a, b) {
if a * (current_y) >= b * (current_x + 1) {
// If going east (making a (1, 0) step) doesn't lead to going below the line y == b/a*x,
current_x += 1; // append the x step and reflect the change in the plane vector.
Expand All @@ -353,6 +355,8 @@ pub fn darkest_mos_mode_and_gen_bresenham(
result_scale.push(1);
}
}
// Get the dark generator. We know how many steps and that this will give the perfect generator, not the
// augmented one, since we just got the darkest mode.
let result_gen = CountVector::from_slice(&result_scale[0..count_gen_steps]);
(result_scale, result_gen)
} else {
Expand Down Expand Up @@ -423,6 +427,7 @@ pub fn darkest_mos_mode_and_gen_bjorklund(
}

/// The mode of the MOS aLbs with a given brightness (count of bright generators up from root).
/// Returns `Err` unless 0 <= brightness <= a + b - 1.
pub fn mos_mode(a: usize, b: usize, brightness: usize) -> Result<Vec<Letter>, ScaleError> {
if brightness >= a + b {
Err(ScaleError::CannotMakeScale)
Expand All @@ -443,38 +448,80 @@ pub fn rotate<T: std::clone::Clone>(slice: &[T], degree: usize) -> Vec<T> {
}
}

pub fn are_conjugate<T>(s1: &[T], s2: &[T]) -> bool
/// Whether two slices with elements of type T are rotationally equivalent.
pub fn rotationally_equivalent<T>(s1: &[T], s2: &[T]) -> bool
where
T: Clone + Eq,
{
(s1.len() == s2.len()) && { (0..s1.len()).any(|i| rotate(s1, i) == s2.to_vec()) }
}

/// The lexicographically least mode of a word (where the letters are in their usual order).
pub fn least_mode(scale: &[Letter]) -> Vec<Letter> {
rotate(scale, booth(scale))
}

/// The rotation required from the current word to the
/// lexicographically least mode of a word.
/// Booth's algorithm requires at most 3*n* comparisons and *n* storage locations where *n* is the input word's length.
/// See Booth, K. S. (1980). Lexicographically least circular substrings.
/// Information Processing Letters, 10(4-5), 240–242. doi:10.1016/0020-0190(80)90149-0
pub fn booth(scale: &[Letter]) -> usize {
let n = scale.len();
// `f` is the failure function of the least rotation; `usize::MAX` is used as a null value.
// null indicates that the failure function does not point backwards in the string.
// `usize::MAX` will behave the same way as -1 does, assuming wrapping unsigned addition
let mut f = vec![usize::MAX; 2 * n];
let mut k: usize = 0;
// `j` loops over `scale` twice.
for j in 1..2 * n {
let mut i = f[j - k - 1];
while i != usize::MAX && scale[j % n] != scale[k.wrapping_add(i).wrapping_add(1) % n] {
// (1) If the jth letter is less than s[(k + i + 1) % n] then change k to j - i - 1,
// in effect left-shifting the failure function and the input string.
// This appropriately compensates for the new, shorter least substring.
if scale[j % n] < scale[k.wrapping_add(i).wrapping_add(1) % n] {
k = j.wrapping_sub(i).wrapping_sub(1);
}
i = f[i];
}
if i == usize::MAX && scale[j % n] != scale[k.wrapping_add(i).wrapping_add(1) % n] {
// See note (1) above.
if scale[j % n] < scale[k.wrapping_add(i).wrapping_add(1) % n] {
k = j;
}
f[j - k] = usize::MAX;
} else {
f[j - k] = i.wrapping_add(1);
}
// The induction hypothesis is that
// at this point `f[0..j - k]` is the failure function of `s[k..(k+j)%n]`,
// and `k` is the lexicographically least subword of the letters scanned so far.
}
k
}

/// [Letterwise substitution](https://en.xen.wiki/w/MOS_substitution) for scale words.
///
/// Note: This function does not fail even if the number of times `x` occurs in `template`
/// does not divide `filler.len()`.
pub fn subst<T>(template: &[T], x: &T, filler: &[T]) -> Vec<T>
where
T: PartialEq + Clone,
{
pub fn subst(template: &[Letter], x: Letter, filler: &[Letter]) -> Vec<Letter> {
let mut ret = vec![];
let mut i: usize = 0;
if !filler.is_empty() {
for letter in template {
if *letter == *x {
ret.push(filler[i % filler.len()].clone());
for &letter in template {
if letter == x {
// Use the currently pointed-to letter of `filler` in place of `x`.
ret.push(filler[i % filler.len()]);
// Only update `i` when an `x` is replaced.
i += 1;
} else {
ret.push((*letter).clone());
ret.push(letter);
}
}
} else {
return template
.iter()
.filter(|letter| **letter != *x)
.map(|y| (*y).clone())
.collect();
// If `filler` is empty, we return `template` but with all `x`s removed.
return delete(template, x);
}
ret
}
Expand All @@ -489,48 +536,15 @@ fn mos_substitution_scales_one_perm(n0: usize, n1: usize, n2: usize) -> Vec<Vec<
let gener_size = gener.len();
(0..(n1 + n2))
.map(|i| {
subst::<usize>(
subst(
&template,
&1usize,
1usize,
&rotate(&filler, (i * gener_size) % filler.len()),
)
})
.collect()
}

/// The lexicographically brightest mode of a word (where the letters are in their usual order).
/// (Booth's algorithm)
pub fn least_mode(scale: &[Letter]) -> Vec<Letter> {
rotate(scale, booth(scale))
}

/// The lexicographically brightest mode of a word (where the letters are in their usual order).
/// (Booth's algorithm)
pub fn booth(scale: &[Letter]) -> usize {
let s = scale;
let n = scale.len();
let mut f = vec![usize::MAX; 2 * n];
let mut k: usize = 0;
for j in 1..2 * n {
let mut i = f[j - k - 1];
while i != usize::MAX && s[j % n] != s[k.wrapping_add(i).wrapping_add(1) % n] {
if s[j % n] < s[k.wrapping_add(i).wrapping_add(1) % n] {
k = j.wrapping_sub(i).wrapping_sub(1);
}
i = f[i];
}
if i == usize::MAX && s[j % n] != s[k.wrapping_add(i).wrapping_add(1) % n] {
if s[j % n] < s[k.wrapping_add(i).wrapping_add(1) % n] {
k = j;
}
f[j - k] = usize::MAX;
} else {
f[j - k] = i.wrapping_add(1);
}
}
k
}

/// The set of all [MOS substitution](https://en.xen.wiki/w/User:Inthar/MOS_substitution) ternary scales.
pub fn mos_substitution_scales(sig: &[usize]) -> Vec<Vec<Letter>> {
let (n0, n1, n2) = (sig[0], sig[1], sig[2]);
Expand Down Expand Up @@ -570,14 +584,14 @@ pub fn step_variety(scale: &[Letter]) -> usize {
scale.iter().collect::<BTreeSet<_>>().len()
}

/// subst but single letters.
/// `subst()` but the filler is just one letter.
pub fn replace(scale: &[Letter], from: Letter, to: Letter) -> Vec<Letter> {
subst(scale, &from, &[to])
subst(scale, from, &[to])
}

/// Delete all instances of one letter.
pub fn delete(scale: &[Letter], letter: Letter) -> Vec<Letter> {
scale.iter().filter(|x| **x != letter).copied().collect()
scale.iter().filter(|x| **x != letter).cloned().collect()
}

/// If `scale` is ternary, return whether identifying L = m, m = s, and s = 0 results in a MOS.
Expand Down

0 comments on commit 78787cb

Please sign in to comment.