use std::{
cmp::Ordering,
fmt::Display,
iter::{self, Sum},
ops::{Add, AddAssign, Range},
str::FromStr,
};
use itertools::Itertools as _;
use num_traits::{AsPrimitive, PrimInt};
use thiserror::Error;
use crate::{
algorithm::{
as_slice::AsAlgorithmSlice as _,
direction::Direction,
display::{
algorithm::{AlgorithmDisplay as _, DisplaySpaced, DisplayUnspaced},
r#move::{DisplayLongSpaced, DisplayLongUnspaced, DisplayShort},
},
metric::Metric,
r#move::r#move::Move,
slice::AlgorithmSlice,
},
puzzle::sliding_puzzle::SlidingPuzzle,
};
#[cfg(feature = "serde")]
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum SliceError {
#[error("UnorderedRange: range {0:?} is not ordered")]
UnorderedRange(Range<u64>),
#[error("OutOfRange: slice is out of range (range is {range:?} but length is {len})")]
OutOfRange {
range: Range<u64>,
len: u64,
},
}
#[derive(Clone, Debug, Default, PartialEq, Eq, Hash)]
pub struct Algorithm {
pub(super) moves: Vec<Move>,
}
impl Algorithm {
#[must_use]
pub fn new() -> Self {
Self { moves: Vec::new() }
}
#[must_use]
pub fn with_moves(moves: Vec<Move>) -> Self {
Self { moves }
}
#[must_use]
pub fn moves(&self) -> &[Move] {
&self.moves
}
#[must_use]
pub fn len<M: Metric, T: PrimInt + Sum + 'static>(&self) -> T
where
u64: AsPrimitive<T>,
{
self.as_slice().len::<M, T>()
}
#[must_use]
pub fn len_stm<T: PrimInt + Sum + 'static>(&self) -> T
where
u64: AsPrimitive<T>,
{
self.as_slice().len_stm()
}
#[must_use]
pub fn len_mtm<T: PrimInt + Sum + 'static>(&self) -> T
where
u64: AsPrimitive<T>,
{
self.as_slice().len_mtm()
}
#[must_use]
pub fn is_empty(&self) -> bool {
self.moves.is_empty()
}
pub fn push(&mut self, m: Move) {
self.moves.push(m);
}
pub fn push_combine(&mut self, m: Move) {
if let Some(other) = self
.moves
.last_mut()
.filter(|other| m.direction == other.direction)
{
other.amount += m.amount;
} else {
self.moves.push(m);
}
}
pub fn push_simplify(&mut self, m: Move) {
match self.moves.last_mut() {
Some(other) if m.direction == other.direction => {
other.amount += m.amount;
}
Some(other) if m.direction == other.direction.inverse() => {
match m.amount.cmp(&other.amount) {
Ordering::Less => other.amount -= m.amount,
Ordering::Equal => {
self.moves.pop();
}
Ordering::Greater => {
*other = Move {
direction: m.direction,
amount: m.amount - other.amount,
}
}
}
}
_ => self.moves.push(m),
}
}
#[must_use]
pub fn simplified(&self) -> Self {
self.as_slice().simplified()
}
pub fn simplify(&mut self) {
self.moves = self.simplified().moves;
}
#[must_use]
pub fn inverse(&self) -> Self {
Self {
moves: self.moves.iter().rev().map(|m| m.inverse()).collect(),
}
}
pub fn invert(&mut self) {
self.moves = self.inverse().moves;
}
#[must_use]
pub fn transpose(&self) -> Self {
Self {
moves: self.moves.iter().map(|m| m.transpose()).collect(),
}
}
#[must_use]
pub fn repeat(&self, n: usize) -> Self {
Self {
moves: self.moves.repeat(n),
}
}
pub fn try_slice(&self, range: Range<u64>) -> Result<AlgorithmSlice<'_>, SliceError> {
if range.start > range.end {
return Err(SliceError::UnorderedRange(range));
}
let len = self.len_stm();
if range.start > len || range.end > len {
return Err(SliceError::OutOfRange { range, len });
}
let iter = iter::once(0).chain(self.moves.iter().scan(0, |a, b| {
*a += b.amount;
Some(*a)
}));
let (start_idx, start_total) = iter
.clone()
.find_position(|&i| i >= range.start)
.unwrap_or_else(|| (self.moves.len() + 1, self.len_stm()));
let (end_idx, end_total) = iter
.clone()
.tuple_windows()
.find_position(|&(_, j)| j > range.end)
.map_or_else(
|| (self.moves.len(), self.len_stm()),
|(idx, (i, _))| (idx, i),
);
if start_idx > end_idx {
Ok(AlgorithmSlice {
first: self
.moves
.get(start_idx - 1)
.and_then(|mv| Move::new_nonzero(mv.direction, range.end - range.start).ok()),
middle: &[],
last: None,
})
} else {
Ok(AlgorithmSlice {
first: start_idx
.checked_sub(1)
.and_then(|idx| self.moves.get(idx))
.and_then(|mv| Move::new_nonzero(mv.direction, start_total - range.start).ok()),
middle: &self.moves[start_idx..end_idx],
last: self
.moves
.get(end_idx)
.and_then(|mv| Move::new_nonzero(mv.direction, range.end - end_total).ok()),
})
}
}
pub fn try_slice_mtm(&self, range: Range<u64>) -> Result<AlgorithmSlice<'_>, SliceError> {
if range.start > range.end {
return Err(SliceError::UnorderedRange(range));
}
let len = self.len_mtm();
if range.start > len || range.end > len {
return Err(SliceError::OutOfRange { range, len });
}
Ok(AlgorithmSlice {
first: None,
middle: &self.moves[range.start as usize..range.end as usize],
last: None,
})
}
#[must_use]
pub fn is_solution_of<Puzzle: SlidingPuzzle + Clone>(&self, mut puzzle: Puzzle) -> bool {
let b = puzzle.try_apply_alg(self);
b && puzzle.is_solved()
}
#[must_use]
pub fn display_long_spaced(&self) -> DisplaySpaced<'_, DisplayLongSpaced> {
DisplaySpaced::<DisplayLongSpaced>::new(self)
}
#[must_use]
pub fn display_long_unspaced(&self) -> DisplayUnspaced<'_, DisplayLongUnspaced> {
DisplayUnspaced::<DisplayLongUnspaced>::new(self)
}
#[must_use]
pub fn display_short_spaced(&self) -> DisplaySpaced<'_, DisplayShort> {
DisplaySpaced::<DisplayShort>::new(self)
}
#[must_use]
pub fn display_short_unspaced(&self) -> DisplayUnspaced<'_, DisplayShort> {
DisplayUnspaced::<DisplayShort>::new(self)
}
}
impl Display for Algorithm {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.display_short_unspaced().fmt(f)
}
}
#[derive(Clone, Debug, Error, PartialEq, Eq)]
#[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
pub enum ParseAlgorithmError {
#[error("InvalidCharacter: character {0} is invalid")]
InvalidCharacter(char),
#[error("MissingDirection: a number must be preceded by a direction")]
MissingDirection,
#[error("Overflow: integer overflow occurred when reading the number after a direction")]
Overflow,
}
impl FromStr for Algorithm {
type Err = ParseAlgorithmError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
let mut alg = Self::default();
let mut dir = None;
let mut amount = None;
for c in s.chars() {
if let Ok(d) = Direction::try_from(c) {
if let Some(dir) = dir {
alg.push(Move::new(dir, amount.unwrap_or(1)));
}
dir = Some(d);
amount = None;
}
else if let Some(d) = c.to_digit(10) {
let d = d as u64;
if dir.is_none() {
return Err(ParseAlgorithmError::MissingDirection);
}
if let Some(a) = amount {
amount = Some(
a.checked_mul(10)
.ok_or(ParseAlgorithmError::Overflow)?
.checked_add(d)
.ok_or(ParseAlgorithmError::Overflow)?,
);
} else {
amount = Some(d);
}
}
else if c.is_whitespace() {
if let Some(dir) = dir {
alg.push(Move::new(dir, amount.unwrap_or(1)));
}
dir = None;
amount = None;
}
else {
return Err(ParseAlgorithmError::InvalidCharacter(c));
}
}
if let Some(dir) = dir {
alg.push(Move::new(dir, amount.unwrap_or(1)));
}
Ok(alg)
}
}
#[cfg(feature = "serde")]
impl serde::Serialize for Algorithm {
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
where
S: serde::Serializer,
{
let str = self.to_string();
serializer.serialize_str(&str)
}
}
#[cfg(feature = "serde")]
impl<'de> serde::Deserialize<'de> for Algorithm {
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
where
D: serde::Deserializer<'de>,
{
let alg_str = String::deserialize(deserializer)?;
Self::from_str(&alg_str).map_err(serde::de::Error::custom)
}
}
impl Add for Algorithm {
type Output = Self;
fn add(self, rhs: Self) -> Self::Output {
let mut moves = self.moves;
let mut moves2 = rhs.moves;
moves.append(&mut moves2);
Self { moves }
}
}
impl AddAssign for Algorithm {
fn add_assign(&mut self, mut rhs: Self) {
self.moves.append(&mut rhs.moves);
}
}
impl From<AlgorithmSlice<'_>> for Algorithm {
fn from(value: AlgorithmSlice<'_>) -> Self {
let mut alg = Self::new();
if let Some(m) = value.first {
alg.push(m);
}
alg += Self::with_moves(value.middle.to_vec());
if let Some(m) = value.last {
alg.push(m);
}
alg
}
}
impl FromIterator<Direction> for Algorithm {
fn from_iter<T: IntoIterator<Item = Direction>>(iter: T) -> Self {
Self::with_moves(iter.into_iter().map(Move::from).collect())
}
}
impl FromIterator<Move> for Algorithm {
fn from_iter<T: IntoIterator<Item = Move>>(iter: T) -> Self {
Self::with_moves(iter.into_iter().collect())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_len() {
let a = Algorithm::from_str("ULDR").unwrap();
assert_eq!(a.len_stm::<u64>(), 4);
assert_eq!(a.len_mtm::<u64>(), 4);
}
#[test]
fn test_len_2() {
let a = Algorithm::from_str("U3L6D2R20").unwrap();
assert_eq!(a.len_stm::<u64>(), 31);
assert_eq!(a.len_mtm::<u64>(), 4);
}
#[test]
fn test_len_3() {
let a = Algorithm::from_str("UUU3").unwrap();
assert_eq!(a.len_stm::<u64>(), 5);
assert_eq!(a.len_mtm::<u64>(), 3);
}
#[test]
fn test_is_empty() {
let a = Algorithm::from_str("ULDR").unwrap();
assert!(!a.is_empty());
}
#[test]
fn test_is_empty_2() {
let a = Algorithm::new();
assert!(a.is_empty());
}
#[test]
fn test_push_combine() {
let mut a = Algorithm::from_str("ULDR").unwrap();
a.push_combine(Move::from(Direction::Right));
assert_eq!(a.moves.last(), Some(&Move::new(Direction::Right, 2)));
assert_eq!(a.to_string(), "ULDR2");
}
#[test]
fn test_push_combine_2() {
let mut a = Algorithm::from_str("ULDR").unwrap();
a.push_combine(Move::from(Direction::Left));
assert_eq!(a.moves.last(), Some(&Move::new(Direction::Left, 1)));
assert_eq!(a.to_string(), "ULDRL");
}
#[test]
fn test_push_simplify() {
let mut a = Algorithm::from_str("ULDR").unwrap();
a.push_simplify(Move::from(Direction::Right));
assert_eq!(a.moves.last(), Some(&Move::new(Direction::Right, 2)));
assert_eq!(a.to_string(), "ULDR2");
}
#[test]
fn test_push_simplify_2() {
let mut a = Algorithm::from_str("ULDR").unwrap();
a.push_simplify(Move::new(Direction::Left, 3));
assert_eq!(a.moves.last(), Some(&Move::new(Direction::Left, 2)));
assert_eq!(a.to_string(), "ULDL2");
}
#[test]
fn test_push_simplify_3() {
let mut a = Algorithm::from_str("ULDR").unwrap();
a.push_simplify(Move::from(Direction::Left));
assert_eq!(a.moves.last(), Some(&Move::new(Direction::Down, 1)));
assert_eq!(a.to_string(), "ULD");
}
#[test]
fn test_push_simplify_4() {
let mut a = Algorithm::from_str("ULDR").unwrap();
a.push_simplify(Move::from(Direction::Up));
assert_eq!(a.moves.last(), Some(&Move::from(Direction::Up)));
assert_eq!(a.to_string(), "ULDRU");
}
#[test]
fn test_push_simplify_5() {
let mut a = Algorithm::from_str("ULDR5").unwrap();
a.push_simplify(Move::new(Direction::Left, 3));
assert_eq!(a.moves.last(), Some(&Move::new(Direction::Right, 2)));
assert_eq!(a.to_string(), "ULDR2");
}
#[test]
fn test_simplify() {
let mut a = Algorithm::from_str("UD2U3DUDDDUUD2").unwrap();
a.simplify();
assert_eq!(a, Algorithm::from_str("D").unwrap());
}
#[test]
fn test_simplify_2() {
let mut a = Algorithm::from_str("UDLRDRLU").unwrap();
a.simplify();
assert_eq!(a, Algorithm::from_str("").unwrap());
}
#[test]
fn test_simplify_3() {
let mut a = Algorithm::from_str(
"DRLLURLURURURURULLDDDLLUDLLLDDRLURDLRUURULUDLDRDULURRRLUUUUDLRRLLRRULLRRLRDRDUDR
DDRLLLRULLUDULDDURRLDRURRRDRLUDLDDURRURUUULLDRLLDRDUDLLLLDLURLDRLDLDDULLDLDRRLRD
LRLUUUDDLUDULLDUDDLRRLUULRRLDLRRULULRDRULRLUDUDLRDURDLLRRDDURLULRRLDDRRRRUDDDULU
DRURURDULLRDUULLLURRUDUDDURURLUUDULRURLRLLRDRDLDRLRRDURUURLUULRRUURDUDDLRUDLDUUD
RLRURRRDDULDLLURLULLRLUDUDUDRUDLURURLLURRDRUUDURURLLRLDUUURUULUDLDDLLRRLRDRLRULU
LDDLDRRULUUDDDDDRRLURRULLDULUDLRUUDDDRULRRURUDLLUDDDDLULLDULLLDDRULURURUDDLRLULU
LRLDULLLUULLDLUURRRULLUDRRUUUULLRULLRDDLURULUDUULLLRDRLRUUDRRUDRRLRUUURRDLLDULRL
RLLLDDUUDDULUUDDRUDLLUDLRRLLRDDULLDULLRRURDRULUDLRLDRLRLUDDDUUDURUDULULLDLLDUDLL
LRRDDLDDLRLLLURUDLUDRDDRLRDLDRRDDDUDRRRDURDLDUDLURDUDLLRUDUUDULLRRDLLLLLDDULDRUD
LDLRLRLLRRULLDRULRRRULRDDLLRRDRUDDUULRRLRLLRUUUDDRDDLUDLRRULLDLDDLURDRDLRDRUUDUU",
)
.unwrap();
let b = Algorithm::from_str(
"DLU2RURURURUL2D3L5DRURU4R2DRD2LUL2ULDRDRUR3DLDR2URU3L2DLDRDL4DLDLDL2DLDR2DLULUL2
DRDR5U2L3UR3U3RDLDR3U4RU2RDR3DLDL2U2L2URULUR2DRU2RULU2RU2L2D3RULULD2LDR2ULD3RUR2
UL3DRURULD3LUL5D2RULURURDLULUL4U2L2DLU2R3U5LULD2LURULU2L2DRUR4U3R2DL5DL2DL2URDRU
L2DRULUL2DL2DLD2LD2L3URD2RDLDR2D3R4DLDLURDLUL5DLDL2DRUR2D2RU2RDL2DLD2LURDRD2RU3",
)
.unwrap();
a.simplify();
assert_eq!(a, b);
}
#[test]
fn test_simplify_4() {
let mut a = Algorithm::from_str("LRL").unwrap();
a.simplify();
assert_eq!(a, Algorithm::from_str("L").unwrap());
}
#[test]
fn test_simplify_5() {
let mut a = Algorithm::from_str("U10").unwrap();
let b = a.clone();
a.simplify();
assert_eq!(a, b);
}
#[test]
fn test_simplify_6() {
let mut a = Algorithm::new();
let b = a.clone();
a.simplify();
assert_eq!(a, b);
}
#[test]
fn test_invert() {
let mut a = Algorithm::from_str("ULDR").unwrap();
let b = Algorithm::from_str("LURD").unwrap();
a.invert();
assert_eq!(a, b);
}
#[test]
fn test_invert_2() {
let mut a = Algorithm::from_str("").unwrap();
let b = a.clone();
a.invert();
assert_eq!(a, b);
}
#[test]
fn test_invert_3() {
let mut a = Algorithm::from_str("DL3ULU3R2DLD2RUL2U").unwrap();
let b = Algorithm::from_str("DR2DLU2RUL2D3RDR3U").unwrap();
a.invert();
assert_eq!(a, b);
}
#[test]
fn test_transpose() {
let a = Algorithm::from_str("D2RUR2D2L3URU").unwrap();
let b = Algorithm::from_str("R2DLD2R2U3LDL").unwrap();
assert_eq!(a.transpose(), b);
}
#[test]
fn test_repeat() {
let a = Algorithm::from_str("U2LD3R").unwrap();
let b = a.repeat(3);
let expected = Algorithm::from_str(&"U2LD3R".repeat(3)).unwrap();
assert_eq!(b, expected);
}
mod slice {
use super::*;
macro_rules! slice {
($first:literal, $middle:literal, $last:literal) => {{
let first = if $first.is_empty() {
None
} else {
Some(Move::from_str($first).unwrap())
};
let mut middle = Algorithm::from_str($middle).unwrap();
let last = if $last.is_empty() {
None
} else {
Some(Move::from_str($last).unwrap())
};
Ok(AlgorithmSlice {
first,
middle: &std::mem::take(&mut middle.moves),
last,
})
}};
}
#[test]
fn test_slice() {
let alg = Algorithm::from_str("R2DLU10RUR2D2D3D5L5U2L").unwrap();
assert_eq!(alg.try_slice(0..0), slice!("", "", ""));
assert_eq!(alg.try_slice(1..1), slice!("", "", ""));
assert_eq!(alg.try_slice(36..36), slice!("", "", ""));
assert_eq!(
alg.try_slice(0..36),
slice!("", "R2DLU10RUR2D2D3D5L5U2L", "")
);
assert_eq!(alg.try_slice(0..28), slice!("", "R2DLU10RUR2D2D3D5", ""));
assert_eq!(alg.try_slice(4..36), slice!("", "U10RUR2D2D3D5L5U2L", ""));
assert_eq!(alg.try_slice(4..28), slice!("", "U10RUR2D2D3D5", ""));
assert_eq!(alg.try_slice(0..30), slice!("", "R2DLU10RUR2D2D3D5", "L2"));
assert_eq!(alg.try_slice(11..36), slice!("U3", "RUR2D2D3D5L5U2L", ""));
assert_eq!(alg.try_slice(11..30), slice!("U3", "RUR2D2D3D5", "L2"));
assert_eq!(alg.try_slice(3..4), slice!("", "L", ""));
assert_eq!(alg.try_slice(5..7), slice!("U2", "", ""));
assert_eq!(alg.try_slice(16..19), slice!("", "R2", "D"));
assert_eq!(alg.try_slice(17..19), slice!("R", "", "D"));
}
#[test]
fn test_slice_2() {
let alg = Algorithm::from_str("R2DLU10RUR2D2D3D5L5U2L").unwrap();
#[allow(clippy::reversed_empty_ranges)]
let empty_range = 10..5;
assert_eq!(
alg.try_slice(empty_range.clone()),
Err(SliceError::UnorderedRange(empty_range))
);
assert_eq!(
alg.try_slice(0..37),
Err(SliceError::OutOfRange {
range: 0..37,
len: 36
})
);
assert_eq!(
alg.try_slice(37..37),
Err(SliceError::OutOfRange {
range: 37..37,
len: 36
})
);
}
#[test]
fn test_slice_mtm() {
let alg = Algorithm::from_str("R2DLU10RUR2D2D3D5L5U2L").unwrap();
assert_eq!(alg.try_slice_mtm(0..0), slice!("", "", ""));
assert_eq!(alg.try_slice_mtm(1..1), slice!("", "", ""));
assert_eq!(alg.try_slice_mtm(13..13), slice!("", "", ""));
assert_eq!(
alg.try_slice_mtm(0..13),
slice!("", "R2DLU10RUR2D2D3D5L5U2L", "")
);
assert_eq!(alg.try_slice_mtm(2..9), slice!("", "LU10RUR2D2D3", ""));
}
}
mod from_str {
use std::str::FromStr as _;
use crate::algorithm::{
algorithm::{Algorithm, ParseAlgorithmError},
direction::Direction,
r#move::r#move::Move,
};
#[test]
fn test_from_str() {
let a = Algorithm::from_str("U2L3DR4");
assert_eq!(
a,
Ok(Algorithm {
moves: vec![
Move::new(Direction::Up, 2),
Move::new(Direction::Left, 3),
Move::new(Direction::Down, 1),
Move::new(Direction::Right, 4)
],
})
);
}
#[test]
fn test_from_str_2() {
let a = Algorithm::from_str("");
assert_eq!(a, Ok(Algorithm { moves: vec![] }));
}
#[test]
fn test_from_str_3() {
let a = Algorithm::from_str("U");
assert_eq!(
a,
Ok(Algorithm {
moves: vec![Move::new(Direction::Up, 1)]
})
);
}
#[test]
fn test_from_str_4() {
let a = Algorithm::from_str("L1234567890");
assert_eq!(
a,
Ok(Algorithm {
moves: vec![Move::new(Direction::Left, 1234567890)]
})
);
}
#[test]
fn test_from_str_5() {
let a = Algorithm::from_str("ULDR");
assert_eq!(
a,
Ok(Algorithm {
moves: vec![
Move::new(Direction::Up, 1),
Move::new(Direction::Left, 1),
Move::new(Direction::Down, 1),
Move::new(Direction::Right, 1)
]
})
);
}
#[test]
fn test_from_str_6() {
let a = Algorithm::from_str("D3RU2RD2aRU3L3");
assert_eq!(a, Err(ParseAlgorithmError::InvalidCharacter('a')));
}
#[test]
fn test_from_str_7() {
let a = Algorithm::from_str("3R4DL2");
assert_eq!(a, Err(ParseAlgorithmError::MissingDirection));
}
#[test]
fn test_from_str_8() {
let a = Algorithm::from_str("R3L0U2");
assert_eq!(
a,
Ok(Algorithm {
moves: vec![
Move::new(Direction::Right, 3),
Move::new(Direction::Left, 0),
Move::new(Direction::Up, 2),
]
})
);
}
#[test]
fn test_from_str_9() {
let a = Algorithm::from_str("L0");
assert_eq!(
a,
Ok(Algorithm {
moves: vec![Move::new(Direction::Left, 0),]
})
);
}
#[test]
fn test_from_str_10() {
let a = Algorithm::from_str(" U L D R ");
let b = Algorithm::from_str("ULDR");
assert_eq!(a, b);
}
#[test]
fn test_from_str_11() {
let a = Algorithm::from_str(" U2 L3 D4\t R1");
let b = Algorithm::from_str("U2L3D4R");
assert_eq!(a, b);
}
#[test]
fn test_from_str_12() {
let a = Algorithm::from_str("D3 R U2 R D2 R U3 L3 a");
assert_eq!(a, Err(ParseAlgorithmError::InvalidCharacter('a')));
}
#[test]
fn test_from_str_13() {
let a = Algorithm::from_str("D 3L");
assert_eq!(a, Err(ParseAlgorithmError::MissingDirection));
}
#[test]
fn test_from_str_14() {
let a = Algorithm::from_str("U100000000000000000000");
assert_eq!(a, Err(ParseAlgorithmError::Overflow));
}
#[test]
fn test_from_str_to_string() {
let algs = [
"",
"ULDRULDRULDR",
"UD2U3DUDD2DUUD2",
"UDLRLDULUDRLURDLURRULRUDRRUDLLDDUDURLURLRLUDURLUDR",
"DLULD2RU2LDLDRDRU2LULD3RU3R2D3LU2RDLU2LDLDR2ULURDLUL",
];
for a in algs {
assert_eq!(Algorithm::from_str(a).unwrap().to_string(), a);
}
}
}
mod add {
use super::*;
#[test]
fn test_add() {
let a = Algorithm::from_str("ULDR").unwrap();
let b = Algorithm::from_str("DRUL").unwrap();
assert_eq!(a + b, Algorithm::from_str("ULDRDRUL").unwrap());
}
}
mod add_assign {
use super::*;
#[test]
fn test_add_assign() {
let mut a = Algorithm::from_str("ULDRU").unwrap();
let b = Algorithm::from_str("DRUL").unwrap();
a += b;
assert_eq!(a, Algorithm::from_str("ULDRUDRUL").unwrap());
}
}
mod from_algorithm_slice {
use super::*;
#[test]
fn test_from_algorithm_slice() {
let slice = AlgorithmSlice {
first: None,
middle: &[],
last: None,
};
assert_eq!(Algorithm::from(slice), Algorithm::new());
}
#[test]
fn test_from_algorithm_slice_2() {
let slice = AlgorithmSlice {
first: None,
middle: &[Move::new(Direction::Up, 2)],
last: None,
};
assert_eq!(Algorithm::from(slice), Algorithm::from_str("U2").unwrap());
}
#[test]
fn test_from_algorithm_slice_3() {
let slice = AlgorithmSlice {
first: Some(Move::new(Direction::Left, 1)),
middle: &[Move::new(Direction::Up, 2), Move::new(Direction::Right, 3)],
last: Some(Move::new(Direction::Down, 4)),
};
assert_eq!(
Algorithm::from(slice),
Algorithm::from_str("LU2R3D4").unwrap()
);
}
#[test]
fn test_from_algorithm_slice_4() {
let slice = AlgorithmSlice {
first: Some(Move::new(Direction::Left, 1)),
middle: &[],
last: None,
};
assert_eq!(Algorithm::from(slice), Algorithm::from_str("L").unwrap());
}
#[test]
fn test_from_algorithm_slice_5() {
let slice = AlgorithmSlice {
first: None,
middle: &[],
last: Some(Move::new(Direction::Left, 1)),
};
assert_eq!(Algorithm::from(slice), Algorithm::from_str("L").unwrap());
}
#[test]
fn test_from_algorithm_slice_6() {
let slice = AlgorithmSlice {
first: Some(Move::new(Direction::Left, 1)),
middle: &[],
last: Some(Move::new(Direction::Up, 2)),
};
assert_eq!(Algorithm::from(slice), Algorithm::from_str("LU2").unwrap());
}
}
}
#[cfg(all(feature = "nightly", test))]
mod benchmarks {
extern crate test;
use test::Bencher;
use super::*;
#[bench]
fn bench_from_str(b: &mut Bencher) {
b.iter(|| {
Algorithm::from_str(
"DR2D2LULURUR2DL2DRU2RD2LDRULULDRDL2URDLU3RDLUR3DLDLU2RD3LU3R2DLD2LULU2R3D3",
)
});
}
}