Code for billiard scales: Difference between revisions
| (2 intermediate revisions by the same user not shown) | |||
| Line 502: | Line 502: | ||
//! 3. For each region, trace a trajectory and record which coordinate changes | //! 3. For each region, trace a trajectory and record which coordinate changes | ||
//! 4. The sequence of changes (0=L, 1=m, 2=s) forms the scale word | //! 4. The sequence of changes (0=L, 1=m, 2=s) forms the scale word | ||
//! # Properties | //! # Properties | ||
//! | //! | ||
| Line 531: | Line 515: | ||
use crate::helpers::gcd; | use crate::helpers::gcd; | ||
use crate::plane_geometry::{ConvexPolygon, Line, Point, PointLineConfiguration, Slope}; | use crate::plane_geometry::{ConvexPolygon, Line, Point, PointLineConfiguration, Slope}; | ||
pub type Letter = usize; | |||
// ERRORS | // ERRORS | ||
| Line 563: | Line 548: | ||
impl std::error::Error for BadBilliardState {} | impl std::error::Error for BadBilliardState {} | ||
/// Rotate a scale word left by `degree` positions (change mode). | |||
pub fn rotate<T: std::clone::Clone>(slice: &[T], degree: usize) -> Vec<T> { | |||
let degree = degree % slice.len(); | |||
if degree == 0 { | |||
slice.to_vec() | |||
} else { | |||
[&slice[degree..slice.len()], &slice[0..degree]].concat() | |||
} | |||
} | |||
/// 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 scale_len = scale.len(); | |||
// `failure_func` 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 failure_func = vec![usize::MAX; 2 * scale_len]; | |||
let mut least_rotation: usize = 0; | |||
// `scan_pos` loops over `scale` twice. | |||
for scan_pos in 1..2 * scale_len { | |||
let mut match_len = failure_func[scan_pos - least_rotation - 1]; | |||
while match_len != usize::MAX | |||
&& scale[scan_pos % scale_len] | |||
!= scale[least_rotation.wrapping_add(match_len).wrapping_add(1) % scale_len] | |||
{ | |||
// (1) If the scan_pos-th letter is less than s[(least_rotation + match_len + 1) % scale_len] then change least_rotation to scan_pos - match_len - 1, | |||
// in effect left-shifting the failure function and the input string. | |||
// This appropriately compensates for the new, shorter least substring. | |||
if scale[scan_pos % scale_len] | |||
< scale[least_rotation.wrapping_add(match_len).wrapping_add(1) % scale_len] | |||
{ | |||
least_rotation = scan_pos.wrapping_sub(match_len).wrapping_sub(1); | |||
} | |||
match_len = failure_func[match_len]; | |||
} | |||
if match_len == usize::MAX | |||
&& scale[scan_pos % scale_len] | |||
!= scale[least_rotation.wrapping_add(match_len).wrapping_add(1) % scale_len] | |||
{ | |||
// See note (1) above. | |||
if scale[scan_pos % scale_len] | |||
< scale[least_rotation.wrapping_add(match_len).wrapping_add(1) % scale_len] | |||
{ | |||
least_rotation = scan_pos; | |||
} | |||
failure_func[scan_pos - least_rotation] = usize::MAX; | |||
} else { | |||
failure_func[scan_pos - least_rotation] = match_len.wrapping_add(1); | |||
} | |||
// The induction hypothesis is that | |||
// at this point `failure_func[0..scan_pos - least_rotation]` is the failure function of `s[least_rotation..(least_rotation+scan_pos)%scale_len]`, | |||
// and `least_rotation` is the lexicographically least subword of the letters scanned so far. | |||
} | |||
least_rotation | |||
} | |||
fn projected_constraint_planes(a: u32, b: u32, c: u32) -> (Vec<Line>, Vec<Line>, Vec<Line>) { | fn projected_constraint_planes(a: u32, b: u32, c: u32) -> (Vec<Line>, Vec<Line>, Vec<Line>) { | ||
| Line 761: | Line 811: | ||
/// * `b` - Number of m (medium) steps | /// * `b` - Number of m (medium) steps | ||
/// * `c` - Number of s (small) steps | /// * `c` - Number of s (small) steps | ||
/// # Returns | /// # Returns | ||
/// | /// | ||
| Line 823: | Line 857: | ||
} | } | ||
</syntaxhighlight> | </syntaxhighlight> | ||
=== lib.rs === | === lib.rs === | ||
<syntaxhighlight lang="rs">use std::collections::BTreeSet; | <syntaxhighlight lang="rs">use std::collections::BTreeSet; | ||
| Line 1,134: | Line 1,169: | ||
name = "billiard_scales" | name = "billiard_scales" | ||
version = "0.0.0" | version = "0.0.0" | ||
edition = " | edition = "2024" | ||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html | ||