From 756773a6edb62c5f02aa76d3d9b4ea767f0b1ad5 Mon Sep 17 00:00:00 2001 From: Luccas Mateus de Medeiros Gomes Date: Tue, 2 Nov 2021 21:17:27 -0300 Subject: [PATCH] MathFloor done and MathMode still left work Math mode final form currently MathMode and MathFloor --- crates/nu-command/src/default_context.rs | 2 + crates/nu-command/src/math/floor.rs | 74 ++++++++++ crates/nu-command/src/math/mod.rs | 4 + crates/nu-command/src/math/mode.rs | 174 +++++++++++++++++++++++ 4 files changed, 254 insertions(+) create mode 100644 crates/nu-command/src/math/floor.rs create mode 100644 crates/nu-command/src/math/mode.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 52dc3f309b..428caa5607 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -62,12 +62,14 @@ pub fn create_default_context() -> EngineState { Math, MathAbs, MathAvg, + MathFloor, MathMax, MathMin, MathProduct, MathRound, MathSqrt, MathSum, + MathMode, Mkdir, Module, Mv, diff --git a/crates/nu-command/src/math/floor.rs b/crates/nu-command/src/math/floor.rs new file mode 100644 index 0000000000..84f4c93886 --- /dev/null +++ b/crates/nu-command/src/math/floor.rs @@ -0,0 +1,74 @@ +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, Value}; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "math floor" + } + + fn signature(&self) -> Signature { + Signature::build("math floor") + } + + fn usage(&self) -> &str { + "Applies the floor function to a list of numbers" + } + + fn run( + &self, + engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + let head = call.head; + input.map( + move |value| operate(value, head), + engine_state.ctrlc.clone(), + ) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Apply the floor function to a list of numbers", + example: "[1.5 2.3 -3.1] | math floor", + result: Some(Value::List { + vals: vec![Value::test_int(1), Value::test_int(2), Value::test_int(-4)], + span: Span::unknown(), + }), + }] + } +} + +fn operate(value: Value, head: Span) -> Value { + match value { + Value::Int { .. } => value, + Value::Float { val, span } => Value::Float { + val: val.floor(), + span, + }, + other => Value::Error { + error: ShellError::UnsupportedInput( + String::from("Only numerical values are supported"), + other.span().unwrap_or(head), + ), + }, + } +} + +#[cfg(test)] +mod tests { + use super::ShellError; + use super::SubCommand; + + #[test] + fn examples_work_as_expected() -> Result<(), ShellError> { + use crate::examples::test as test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/math/mod.rs b/crates/nu-command/src/math/mod.rs index 66eff72d82..96466658db 100644 --- a/crates/nu-command/src/math/mod.rs +++ b/crates/nu-command/src/math/mod.rs @@ -1,8 +1,10 @@ mod abs; mod avg; pub mod command; +mod floor; mod max; mod min; +mod mode; mod product; mod reducers; mod round; @@ -13,8 +15,10 @@ mod utils; pub use abs::SubCommand as MathAbs; pub use avg::SubCommand as MathAvg; pub use command::MathCommand as Math; +pub use floor::SubCommand as MathFloor; pub use max::SubCommand as MathMax; pub use min::SubCommand as MathMin; +pub use mode::SubCommand as MathMode; pub use product::SubCommand as MathProduct; pub use round::SubCommand as MathRound; pub use sqrt::SubCommand as MathSqrt; diff --git a/crates/nu-command/src/math/mode.rs b/crates/nu-command/src/math/mode.rs new file mode 100644 index 0000000000..126d6abc38 --- /dev/null +++ b/crates/nu-command/src/math/mode.rs @@ -0,0 +1,174 @@ +use crate::math::utils::run_with_function; +use nu_protocol::ast::Call; +use nu_protocol::engine::{Command, EngineState, Stack}; +use nu_protocol::{Example, PipelineData, ShellError, Signature, Span, Value}; +use std::cmp::Ordering; + +#[derive(Clone)] +pub struct SubCommand; + +#[derive(Hash, Eq, PartialEq, Debug)] +enum NumberTypes { + Float, + Int, + Duration, + Filesize, +} + +#[derive(Hash, Eq, PartialEq, Debug)] +struct HashableType { + bytes: [u8; 8], + original_type: NumberTypes, +} + +impl HashableType { + fn new(bytes: [u8; 8], original_type: NumberTypes) -> HashableType { + HashableType { + bytes, + original_type, + } + } +} + +impl Command for SubCommand { + fn name(&self) -> &str { + "math mode" + } + + fn signature(&self) -> Signature { + Signature::build("math mode") + } + + fn usage(&self) -> &str { + "Gets the most frequent element(s) from a list of numbers or tables" + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + run_with_function(call, input, mode) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Get the mode(s) of a list of numbers", + example: "[3 3 9 12 12 15] | math mode", + result: Some(Value::List { + vals: vec![Value::test_int(3), Value::test_int(12)], + span: Span::unknown(), + }), + }] + } +} + +pub fn mode(values: &[Value], head: &Span) -> Result { + if let Some(Err(values)) = values + .windows(2) + .map(|elem| { + if elem[0].partial_cmp(&elem[1]).is_none() { + return Err(ShellError::OperatorMismatch { + op_span: *head, + lhs_ty: elem[0].get_type(), + lhs_span: elem[0].span()?, + rhs_ty: elem[1].get_type(), + rhs_span: elem[1].span()?, + }); + } + Ok(elem[0].partial_cmp(&elem[1]).unwrap()) + }) + .find(|elem| elem.is_err()) + { + return Err(values); + } + //In e-q, Value doesn't implement Hash or Eq, so we have to get the values inside + // But f64 doesn't implement Hash, so we get the binary representation to use as + // key in the HashMap + let hashable_values: Result, ShellError> = values + .into_iter() + .map(|val| match val { + Value::Int { val, .. } => Ok(HashableType::new(val.to_be_bytes(), NumberTypes::Int)), + Value::Duration { val, .. } => { + Ok(HashableType::new(val.to_be_bytes(), NumberTypes::Duration)) + } + Value::Float { val, .. } => { + Ok(HashableType::new(val.to_be_bytes(), NumberTypes::Float)) + } + Value::Filesize { val, .. } => { + Ok(HashableType::new(val.to_be_bytes(), NumberTypes::Filesize)) + } + other => Err(ShellError::UnsupportedInput( + "Unable to give a result with this input".to_string(), + other.span().unwrap(), + )), + }) + .collect::, ShellError>>(); + if let Err(not_hashable) = hashable_values { + return Err(not_hashable); + } + + let mut frequency_map = std::collections::HashMap::new(); + for v in hashable_values.unwrap() { + let counter = frequency_map.entry(v).or_insert(0); + *counter += 1; + } + + let mut max_freq = -1; + let mut modes = Vec::::new(); + for (value, frequency) in &frequency_map { + match max_freq.cmp(frequency) { + Ordering::Less => { + max_freq = *frequency; + modes.clear(); + modes.push(recreate_value(value, *head)); + } + Ordering::Equal => { + modes.push(recreate_value(value, *head)); + } + Ordering::Greater => (), + } + } + + modes.sort_by(|a, b| a.partial_cmp(b).unwrap()); + Ok(Value::List { + vals: modes, + span: *head, + }) +} + +fn recreate_value(hashable_value: &HashableType, head: Span) -> Value { + let bytes = hashable_value.bytes; + match &hashable_value.original_type { + NumberTypes::Int => Value::Int { + val: i64::from_be_bytes(bytes), + span: head, + }, + NumberTypes::Float => Value::Float { + val: f64::from_be_bytes(bytes), + span: head, + }, + NumberTypes::Duration => Value::Duration { + val: i64::from_be_bytes(bytes), + span: head, + }, + NumberTypes::Filesize => Value::Filesize { + val: i64::from_be_bytes(bytes), + span: head, + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +}