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 1/5] 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 {}) + } +} From 5ae823612fd975f6669143844e3dd0adc7b6a5be Mon Sep 17 00:00:00 2001 From: Luccas Mateus de Medeiros Gomes Date: Wed, 3 Nov 2021 08:59:08 -0300 Subject: [PATCH 2/5] MathCeil, MathFloor and MathMode --- crates/nu-command/src/default_context.rs | 1 + crates/nu-command/src/math/ceil.rs | 73 ++++++++++++++++++++++++ crates/nu-command/src/math/mod.rs | 2 + crates/nu-command/src/math/mode.rs | 2 +- 4 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 crates/nu-command/src/math/ceil.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 428caa5607..5f9f6d4eb4 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -62,6 +62,7 @@ pub fn create_default_context() -> EngineState { Math, MathAbs, MathAvg, + MathCeil, MathFloor, MathMax, MathMin, diff --git a/crates/nu-command/src/math/ceil.rs b/crates/nu-command/src/math/ceil.rs new file mode 100644 index 0000000000..a23720ffbe --- /dev/null +++ b/crates/nu-command/src/math/ceil.rs @@ -0,0 +1,73 @@ +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 ceil" + } + + fn signature(&self) -> Signature { + Signature::build("math ceil") + } + + fn usage(&self) -> &str { + "Applies the ceil 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 ceil function to a list of numbers", + example: "[1.5 2.3 -3.1] | math ceil", + result: Some(Value::List { + vals: vec![Value::test_int(2), Value::test_int(3), Value::test_int(-3)], + span: Span::unknown(), + }), + }] + } +} + +fn operate(value: Value, head: Span) -> Value { + match value { + Value::Int { .. } => value, + Value::Float { val, span } => Value::Float { + val: val.ceil(), + span, + }, + other => Value::Error { + error: ShellError::UnsupportedInput( + String::from("Only numerical values are supported"), + other.span().unwrap_or(head), + ), + }, + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/math/mod.rs b/crates/nu-command/src/math/mod.rs index 96466658db..2ff997c4e3 100644 --- a/crates/nu-command/src/math/mod.rs +++ b/crates/nu-command/src/math/mod.rs @@ -1,5 +1,6 @@ mod abs; mod avg; +mod ceil; pub mod command; mod floor; mod max; @@ -14,6 +15,7 @@ mod utils; pub use abs::SubCommand as MathAbs; pub use avg::SubCommand as MathAvg; +pub use ceil::SubCommand as MathCeil; pub use command::MathCommand as Math; pub use floor::SubCommand as MathFloor; pub use max::SubCommand as MathMax; diff --git a/crates/nu-command/src/math/mode.rs b/crates/nu-command/src/math/mode.rs index 126d6abc38..b3eb646b16 100644 --- a/crates/nu-command/src/math/mode.rs +++ b/crates/nu-command/src/math/mode.rs @@ -88,7 +88,7 @@ pub fn mode(values: &[Value], head: &Span) -> Result { // 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() + .iter() .map(|val| match val { Value::Int { val, .. } => Ok(HashableType::new(val.to_be_bytes(), NumberTypes::Int)), Value::Duration { val, .. } => { From d3e5c5a34247fe7d089f236bd0073f647277986f Mon Sep 17 00:00:00 2001 From: Luccas Mateus de Medeiros Gomes Date: Wed, 3 Nov 2021 09:19:28 -0300 Subject: [PATCH 3/5] Fix tests --- crates/nu-command/src/math/floor.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/crates/nu-command/src/math/floor.rs b/crates/nu-command/src/math/floor.rs index 84f4c93886..ea1e4cbb43 100644 --- a/crates/nu-command/src/math/floor.rs +++ b/crates/nu-command/src/math/floor.rs @@ -61,13 +61,12 @@ fn operate(value: Value, head: Span) -> Value { } #[cfg(test)] -mod tests { - use super::ShellError; - use super::SubCommand; +mod test { + use super::*; #[test] - fn examples_work_as_expected() -> Result<(), ShellError> { - use crate::examples::test as test_examples; + fn test_examples() { + use crate::test_examples; test_examples(SubCommand {}) } From 47ebde4087ae456830986a04d988173ebbce09e2 Mon Sep 17 00:00:00 2001 From: Luccas Mateus de Medeiros Gomes Date: Wed, 3 Nov 2021 18:24:15 -0300 Subject: [PATCH 4/5] Added MathMedian Added MathMedian Fix tests --- crates/nu-command/src/default_context.rs | 3 +- crates/nu-command/src/math/median.rs | 121 +++++++++++++++++++++++ crates/nu-command/src/math/mod.rs | 2 + 3 files changed, 125 insertions(+), 1 deletion(-) create mode 100644 crates/nu-command/src/math/median.rs diff --git a/crates/nu-command/src/default_context.rs b/crates/nu-command/src/default_context.rs index 5f9f6d4eb4..82e991ca65 100644 --- a/crates/nu-command/src/default_context.rs +++ b/crates/nu-command/src/default_context.rs @@ -65,12 +65,13 @@ pub fn create_default_context() -> EngineState { MathCeil, MathFloor, MathMax, + MathMedian, MathMin, + MathMode, MathProduct, MathRound, MathSqrt, MathSum, - MathMode, Mkdir, Module, Mv, diff --git a/crates/nu-command/src/math/median.rs b/crates/nu-command/src/math/median.rs new file mode 100644 index 0000000000..1204485fca --- /dev/null +++ b/crates/nu-command/src/math/median.rs @@ -0,0 +1,121 @@ +use crate::math::avg::average; +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}; + +#[derive(Clone)] +pub struct SubCommand; + +impl Command for SubCommand { + fn name(&self) -> &str { + "math median" + } + + fn signature(&self) -> Signature { + Signature::build("math median") + } + + fn usage(&self) -> &str { + "Gets the median of a list of numbers" + } + + fn run( + &self, + _engine_state: &EngineState, + _stack: &mut Stack, + call: &Call, + input: PipelineData, + ) -> Result { + run_with_function(call, input, median) + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Get the median of a list of numbers", + example: "[3 8 9 12 12 15] | math median", + result: Some(Value::Float { + val: 10.5, + span: Span::unknown(), + }), + }] + } +} + +enum Pick { + MedianAverage, + Median, +} + +pub fn median(values: &[Value], head: &Span) -> Result { + let take = if values.len() % 2 == 0 { + Pick::MedianAverage + } else { + Pick::Median + }; + + let mut sorted = vec![]; + + for item in values { + sorted.push(item.clone()); + } + + 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); + } + + sorted.sort_by(|a, b| a.partial_cmp(b).unwrap()); + + match take { + Pick::Median => { + let idx = (values.len() as f64 / 2.0).floor() as usize; + let out = sorted + .get(idx) + .ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), *head))?; + Ok(out.clone()) + } + Pick::MedianAverage => { + let idx_end = (values.len() / 2) as usize; + let idx_start = idx_end - 1; + + let left = sorted + .get(idx_start) + .ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), *head))? + .clone(); + + let right = sorted + .get(idx_end) + .ok_or_else(|| ShellError::UnsupportedInput("Empty input".to_string(), *head))? + .clone(); + + average(&[left, right], head) + } + } +} + +#[cfg(test)] +mod test { + use super::*; + + #[test] + fn test_examples() { + use crate::test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-command/src/math/mod.rs b/crates/nu-command/src/math/mod.rs index 2ff997c4e3..bf392f68c1 100644 --- a/crates/nu-command/src/math/mod.rs +++ b/crates/nu-command/src/math/mod.rs @@ -4,6 +4,7 @@ mod ceil; pub mod command; mod floor; mod max; +mod median; mod min; mod mode; mod product; @@ -19,6 +20,7 @@ pub use ceil::SubCommand as MathCeil; pub use command::MathCommand as Math; pub use floor::SubCommand as MathFloor; pub use max::SubCommand as MathMax; +pub use median::SubCommand as MathMedian; pub use min::SubCommand as MathMin; pub use mode::SubCommand as MathMode; pub use product::SubCommand as MathProduct; From d23929fc8099f25d99420f78f85938f649eadf19 Mon Sep 17 00:00:00 2001 From: JT <547158+jntrnr@users.noreply.github.com> Date: Fri, 5 Nov 2021 07:04:02 +1300 Subject: [PATCH 5/5] Update mode.rs trying a switch to native endian --- crates/nu-command/src/math/mode.rs | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/crates/nu-command/src/math/mode.rs b/crates/nu-command/src/math/mode.rs index b3eb646b16..baa8f2e739 100644 --- a/crates/nu-command/src/math/mode.rs +++ b/crates/nu-command/src/math/mode.rs @@ -90,15 +90,15 @@ pub fn mode(values: &[Value], head: &Span) -> Result { let hashable_values: Result, ShellError> = values .iter() .map(|val| match val { - Value::Int { val, .. } => Ok(HashableType::new(val.to_be_bytes(), NumberTypes::Int)), + Value::Int { val, .. } => Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Int)), Value::Duration { val, .. } => { - Ok(HashableType::new(val.to_be_bytes(), NumberTypes::Duration)) + Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Duration)) } Value::Float { val, .. } => { - Ok(HashableType::new(val.to_be_bytes(), NumberTypes::Float)) + Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Float)) } Value::Filesize { val, .. } => { - Ok(HashableType::new(val.to_be_bytes(), NumberTypes::Filesize)) + Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Filesize)) } other => Err(ShellError::UnsupportedInput( "Unable to give a result with this input".to_string(), @@ -143,19 +143,19 @@ 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), + val: i64::from_ne_bytes(bytes), span: head, }, NumberTypes::Float => Value::Float { - val: f64::from_be_bytes(bytes), + val: f64::from_ne_bytes(bytes), span: head, }, NumberTypes::Duration => Value::Duration { - val: i64::from_be_bytes(bytes), + val: i64::from_ne_bytes(bytes), span: head, }, NumberTypes::Filesize => Value::Filesize { - val: i64::from_be_bytes(bytes), + val: i64::from_ne_bytes(bytes), span: head, }, }