diff --git a/README.adoc b/README.adoc index 066e312..034f3cb 100644 --- a/README.adoc +++ b/README.adoc @@ -58,7 +58,7 @@ asciidoctor REAMDE.adoc If you have not done this before, open the calculator and press `` to save the basic config. -Edit the config file (in Linux, edit `~/.config/rpn_rs/rpn_rs.toml` and add any constants or macros and press `` or reopen the calculator. +Edit the config file (in Linux, edit `~/.config/rpn_rs/rpn_rs.yaml` and add any constants or macros and press `` or reopen the calculator. Sample Macros: diff --git a/src/calc.rs b/src/calc.rs index ad2d671..3e8aa39 100644 --- a/src/calc.rs +++ b/src/calc.rs @@ -280,6 +280,7 @@ impl Calculator { 'V' => self.op(CalculatorOperation::BuildVector), 'M' => self.op(CalculatorOperation::BuildMatrix), '_' => self.op(CalculatorOperation::Deconstruct), + ')' => self.op(CalculatorOperation::Transpose), // Special '\\' => self.op(CalculatorOperation::Drop), ' ' => self.op(CalculatorOperation::Dup), @@ -517,8 +518,7 @@ impl Calculator { pub fn peek_usize(&mut self) -> CalculatorResult<(usize, Entry)> { let entry = self.peek(0)?; let f = match entry { - Entry::Matrix(_) => return Err(CalculatorError::TypeMismatch), - Entry::Vector(_) => return Err(CalculatorError::TypeMismatch), + Entry::Matrix(_) | Entry::Vector(_) => return Err(CalculatorError::TypeMismatch), Entry::Number(Number { value }) => value, }; // Ensure this can be cast to a usize @@ -547,6 +547,7 @@ impl Calculator { pub fn build_vector(&mut self) -> CalculatorResult { let (count, count_entry) = self.peek_usize()?; + // The arguments need to be reversed since 1 2 3 3V should produce a vector [1;2;3], not [3;2;1] let mut entries: Vec = (1..=count) .rev() .map(|i| self.peek(i)) @@ -556,21 +557,25 @@ impl Calculator { entries.push(count_entry); Ok(CalculatorStateChange { + // Since we reversed the arguments once, we need to reverse again pop: OpArgs::Variable(entries.into_iter().rev().collect::>()), push: OpArgs::Unary(new_entry), }) } pub fn build_matrix(&mut self) -> CalculatorResult { let (count, count_entry) = self.peek_usize()?; + // The arguments need to be reversed, see build_vector let mut entries: Vec = (1..=count) + .rev() .map(|i| self.peek(i)) .collect::>>()?; let new_entry = Matrix::from(&entries)?; - entries.splice(1..1, vec![count_entry]); + entries.push(count_entry); Ok(CalculatorStateChange { - pop: OpArgs::Variable(Vec::from(entries)), + // Since we reversed the arguments once, we need to reverse again + pop: OpArgs::Variable(entries.into_iter().rev().collect::>()), push: OpArgs::Unary(new_entry), }) } @@ -580,12 +585,14 @@ impl Calculator { Entry::Matrix(matrix) => Ok(matrix .vectors .iter() + .rev() .map(|v| Entry::Vector(v.clone())) .collect()), Entry::Vector(vector) => Ok(vector .values .iter() - .map(|n| Entry::Number(n.clone())) + .rev() + .map(|n| Entry::Number(*n)) .collect()), Entry::Number(_number) => Err(CalculatorError::TypeMismatch), }?; @@ -671,6 +678,7 @@ impl Calculator { CalculatorOperation::BuildVector => self.build_vector(), CalculatorOperation::BuildMatrix => self.build_matrix(), CalculatorOperation::Deconstruct => self.deconstruct(), + CalculatorOperation::Transpose => self.unary_op(|a| Ok(OpArgs::Unary(a.transpose()?))), CalculatorOperation::Dup => self.unary_op(|a| Ok(OpArgs::Binary([a.clone(), a]))), CalculatorOperation::Drop => self.unary_op(|_| Ok(OpArgs::None)), CalculatorOperation::Swap => self.binary_op(|[a, b]| Ok(OpArgs::Binary([b, a]))), diff --git a/src/calc/entries.rs b/src/calc/entries.rs index 4a03d22..5c0d9ca 100644 --- a/src/calc/entries.rs +++ b/src/calc/entries.rs @@ -71,6 +71,15 @@ pub struct MatrixDimensions { columns: usize, } +impl MatrixDimensions { + pub const fn transpose(&self) -> Self { + Self { + rows: self.columns, + columns: self.rows, + } + } +} + #[derive(Clone, PartialEq, Debug, Serialize, Deserialize)] pub struct Matrix { pub vectors: Vec, @@ -150,6 +159,13 @@ impl CalculatorEntry for Entry { Self::Matrix(matrix) => matrix.inverse(), } } + fn transpose(&self) -> CalculatorResult { + match self { + Self::Number(number) => number.transpose(), + Self::Vector(vector) => vector.transpose(), + Self::Matrix(matrix) => matrix.transpose(), + } + } fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult { match self { Self::Number(number) => number.sin(angle_mode), @@ -309,22 +325,33 @@ impl CalculatorEntry for Matrix { Err(CalculatorError::NotYetImplemented) } fn inverse(&self) -> CalculatorResult { + // TODO: Inverse Err(CalculatorError::NotYetImplemented) - // TODO: Figure out where to put transpose function. - // Self { - // vectors: self - // .vectors - // .iter() - // .map(|v| v.inverse()) - // .map(|e| match e { - // Ok(Entry::Vector(vector)) => Ok(vector), - // _ => Err(CalculatorError::ArithmeticError), - // }) - // .collect::>>()?, - // direction: self.direction.swap(), - // dimensions: self.dimensions, - // } - // .validate() + } + fn transpose(&self) -> CalculatorResult { + // Iterate over all rows + let mut vectors: Vec = vec![]; + for r in 0..self.dimensions.rows { + vectors.push(Vector { + values: self + .vectors + .iter() + .map(|v| { + // For each row, get the r'th element to build a new vector + v.values + .get(r) + .map_or_else(|| Err(CalculatorError::DimensionMismatch), |n| Ok(*n)) + }) + .collect::>>()?, + direction: VectorDirection::Column, + }); + } + + Self { + vectors, + dimensions: self.dimensions.transpose(), + } + .validate() } fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult { self.iterated_unary(|v| v.sin(angle_mode)) @@ -433,8 +460,8 @@ impl CalculatorEntry for Matrix { } impl Matrix { - pub fn from(entries: &Vec) -> CalculatorResult { - if entries.len() == 0 { + pub fn from(entries: &[Entry]) -> CalculatorResult { + if entries.is_empty() { return Err(CalculatorError::NotEnoughStackEntries); } @@ -450,16 +477,16 @@ impl Matrix { let first_vector = vectors .get(0) .ok_or(CalculatorError::NotEnoughStackEntries)?; + + // The number of rows in this column-based matrix let num_rows = first_vector.values.len(); + // The direction all vectors must face let vector_direction = first_vector.direction; - // TODO: Only build a matrix for column vectors; transpose if they are row vectors - - if vectors - .iter() - .any(|v| v.values.len() != num_rows || v.direction != vector_direction) + // Either the dimension lengths mismatch, or the vectors are facing different directions (and are longer than 1, since a 1-length vector orientation does not matter + if vectors.iter().any(|v| v.values.len() != num_rows) + || (num_rows > 1 && vectors.iter().any(|v| v.direction != vector_direction)) { - // One vector has a different length than another return Err(CalculatorError::DimensionMismatch); } @@ -467,11 +494,18 @@ impl Matrix { rows: num_rows, columns: vectors.len(), }; - Self { + + let ret = Self { vectors, dimensions, + }; + + // If the user tried making a matrix out of row vectors, we need to transpose it, which forces column vectors + if vector_direction == VectorDirection::Row && num_rows > 1 { + ret.transpose() + } else { + ret.validate() } - .validate() } fn iterated_unary( @@ -585,6 +619,12 @@ impl CalculatorEntry for Vector { direction: self.direction.swap(), })) } + fn transpose(&self) -> CalculatorResult { + Ok(Entry::Vector(Self { + values: self.values.clone(), + direction: self.direction.swap(), + })) + } fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult { self.iterated_unary(|n| n.sin(angle_mode)) } @@ -688,8 +728,8 @@ impl CalculatorEntry for Vector { } impl Vector { - pub fn from(entries: &Vec) -> CalculatorResult { - if entries.len() == 0 { + pub fn from(entries: &[Entry]) -> CalculatorResult { + if entries.is_empty() { return Err(CalculatorError::NotEnoughStackEntries); } Self { @@ -697,7 +737,7 @@ impl Vector { .iter() .map(|e| match e { Entry::Matrix(_) | Entry::Vector(_) => Err(CalculatorError::TypeMismatch), - Entry::Number(number) => Ok(number.clone()), + Entry::Number(number) => Ok(*number), }) .collect::>>()?, direction: VectorDirection::default(), @@ -806,9 +846,13 @@ impl CalculatorEntry for Number { })) } fn inverse(&self) -> CalculatorResult { - Ok(Entry::Number(Self { + Self { value: self.value.recip(), - })) + } + .validate() + } + fn transpose(&self) -> CalculatorResult { + Err(CalculatorError::TypeMismatch) } fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult { Ok(Entry::Number(Self { @@ -984,7 +1028,7 @@ impl Number { vectors: matrix .vectors .iter() - .map(|v| op(&self, &Entry::Vector(v.clone()))) // TODO: Do I need to clone? + .map(|v| op(&self, &Entry::Vector(v.clone()))) .map(|e| match e { // Only numbers are valid in a vector Ok(Entry::Vector(vector)) => Ok(vector), @@ -1012,6 +1056,7 @@ where fn negate(&self) -> CalculatorResult; fn abs(&self) -> CalculatorResult; fn inverse(&self) -> CalculatorResult; + fn transpose(&self) -> CalculatorResult; fn sin(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult; fn cos(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult; fn tan(&self, angle_mode: CalculatorAngleMode) -> CalculatorResult; diff --git a/src/calc/operations.rs b/src/calc/operations.rs index d108ce4..6d59a54 100644 --- a/src/calc/operations.rs +++ b/src/calc/operations.rs @@ -30,6 +30,7 @@ pub enum CalculatorOperation { BuildVector, BuildMatrix, Deconstruct, + Transpose, Undo, Redo, Drop,