From 303a9defd3602351cd5e120149e93c1608d5ac91 Mon Sep 17 00:00:00 2001 From: Gurpreet Singh Date: Wed, 26 Aug 2020 22:58:01 -0700 Subject: [PATCH] Added `math product` support (#2249) * added math product: working initial impl * resolving merge conflicts * impl std::ops::Mul for Filesize * rebased with main; refactored product implementation * fixed error msg, added docs for math product --- Cargo.lock | 88 ++++++++------ crates/nu-cli/src/cli.rs | 1 + crates/nu-cli/src/commands.rs | 4 +- crates/nu-cli/src/commands/math/mod.rs | 2 + crates/nu-cli/src/commands/math/product.rs | 126 ++++++++++++++++++++ crates/nu-cli/src/commands/math/reducers.rs | 33 +++++ docs/commands/math.md | 6 + 7 files changed, 219 insertions(+), 41 deletions(-) create mode 100644 crates/nu-cli/src/commands/math/product.rs diff --git a/Cargo.lock b/Cargo.lock index c052daf94e..a94f7ded23 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -277,9 +277,9 @@ checksum = "1d49d90015b3c36167a20fe2810c5cd875ad504b39cff3d4eae7977e6b7c1cb2" [[package]] name = "autocfg" -version = "1.0.0" +version = "1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8aac770f1885fd7e387acedd76065302551364496e46b3dd00860b2f8359b9d" +checksum = "cdb031dd78e28731d87d56cc8ffef4a8f36ca26c38fe2de700543e627f8a464a" [[package]] name = "backtrace" @@ -896,6 +896,16 @@ dependencies = [ "crossbeam-utils 0.6.6", ] +[[package]] +name = "crossbeam-channel" +version = "0.4.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "09ee0cc8804d5393478d743b035099520087a5186f3b93fa58cec08fa62407b6" +dependencies = [ + "cfg-if", + "crossbeam-utils 0.7.2", +] + [[package]] name = "crossbeam-deque" version = "0.7.3" @@ -913,7 +923,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "058ed274caafc1f60c4997b5fc07bf7dc7cca454af7c6e81edffe5f33f70dace" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", "cfg-if", "crossbeam-utils 0.7.2", "lazy_static 1.4.0", @@ -949,7 +959,7 @@ version = "0.7.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c3c7c73a2d1e9fc0886a08b93e98eb643461230d5f1925e4036204d5f2e261a8" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", "cfg-if", "lazy_static 1.4.0", ] @@ -1343,9 +1353,9 @@ checksum = "a246d82be1c9d791c5dfde9a2bd045fc3cbba3fa2b11ad558f27d01712f00569" [[package]] name = "encoding_rs" -version = "0.8.23" +version = "0.8.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e8ac63f94732332f44fe654443c46f6375d1939684c17b0afb6cb56b0456e171" +checksum = "a51b8cf747471cb9499b6d59e59b0444f4c90eba8968c4e44874e92b5b64ace2" dependencies = [ "cfg-if", ] @@ -1384,9 +1394,9 @@ dependencies = [ [[package]] name = "event-listener" -version = "2.3.3" +version = "2.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7f14646a9e0430150a87951622ba9675472b68e384b7701b8423b30560805c7a" +checksum = "e1cd41440ae7e4734bbd42302f63eaba892afc93a3912dad84006247f0dedb0e" [[package]] name = "failure" @@ -1807,9 +1817,9 @@ checksum = "aaf91faf136cb47367fa430cd46e37a788775e7fa104f8b4bcb3861dc389b724" [[package]] name = "git2" -version = "0.13.9" +version = "0.13.10" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "83a18853c521bcf553a4e3c05ad21f3ad89c1a78a0485adc97829a4c88066a36" +checksum = "86d97249f21e9542caeee9f8e1d150905cd875bf723f5ff771bdb4852eb83a24" dependencies = [ "bitflags", "libc", @@ -1863,7 +1873,7 @@ version = "0.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e91b62f79061a0bc2e046024cb7ba44b08419ed238ecbd9adbd787434b9e8c25" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", ] [[package]] @@ -2252,7 +2262,7 @@ version = "1.5.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "86b45e59b16c76b11bf9738fd5d38879d3bd28ad292d7b313608becb17ae2df9" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", "hashbrown", "serde 1.0.115", ] @@ -2310,7 +2320,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "17b77027f12e53ae59a379f7074259d32eb10867e6183388020e922832d9c3fb" dependencies = [ "bytes 0.4.12", - "crossbeam-channel", + "crossbeam-channel 0.3.9", "crossbeam-utils 0.6.6", "curl", "curl-sys", @@ -2451,15 +2461,15 @@ checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" [[package]] name = "libc" -version = "0.2.74" +version = "0.2.76" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a2f02823cf78b754822df5f7f268fb59822e7296276d3e069d8e8cb26a14bd10" +checksum = "755456fae044e6fa1ebbbd1b3e902ae19e73097ed4ed87bb79934a867c007bc3" [[package]] name = "libgit2-sys" -version = "0.12.11+1.0.1" +version = "0.12.12+1.0.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1f4755167bef8a2959fe4eedeece2ba0f8eb9923b209d46139031f1281d569a2" +checksum = "0100ae90655025134424939f1f60e27e879460d451dff6afedde4f8226cbebfc" dependencies = [ "cc", "libc", @@ -2710,7 +2720,7 @@ version = "0.5.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c198b026e1bbf08a937e94c6c60f9ec4a2267f5b0d2eec9c1b21b061ce2be55f" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", ] [[package]] @@ -2741,9 +2751,9 @@ dependencies = [ [[package]] name = "miniz_oxide" -version = "0.4.0" +version = "0.4.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "be0f75932c1f6cfae3c04000e40114adf955636e19040f9c0a2c380702aa1c7f" +checksum = "4d7559a8a40d0f97e1edea3220f698f78b1c5ab67532e49f68fde3910323b722" dependencies = [ "adler", ] @@ -3478,7 +3488,7 @@ version = "0.2.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "090c7f9998ee0ff65aa5b723e4009f7b217707f1fb5ea551329cc4d6231fb304" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", "num-integer", "num-traits 0.2.12", "serde 1.0.115", @@ -3490,7 +3500,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b6b19411a9719e753aff12e5187b74d60d3dc449ec3f4dc21e3989c3f554bc95" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", "num-traits 0.2.12", ] @@ -3511,7 +3521,7 @@ version = "0.1.43" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8d59457e662d541ba17869cf51cf177c0b5f0cbf476c66bdc90bf1edac4f875b" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", "num-traits 0.2.12", ] @@ -3521,7 +3531,7 @@ version = "0.1.41" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7a6e6b7c748f995c4c29c5f5ae0248536e04a5739927c74ec0fa564805094b9f" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", "num-integer", "num-traits 0.2.12", ] @@ -3532,7 +3542,7 @@ version = "0.2.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5c000134b5dbf44adc5cb772486d335293351644b801551abe8f75c84cfa4aef" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", "num-bigint", "num-integer", "num-traits 0.2.12", @@ -3553,7 +3563,7 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ac267bcc07f48ee5f8935ab0d24f316fb722d7a1292e2913f0cc196b29ffd611" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", ] [[package]] @@ -3698,7 +3708,7 @@ version = "0.9.58" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a842db4709b604f0fe5d1170ae3565899be2ad3d9cbc72dedc789ac0511f78de" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", "cc", "libc", "pkg-config", @@ -4388,11 +4398,11 @@ dependencies = [ [[package]] name = "rayon" -version = "1.3.1" +version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "62f02856753d04e03e26929f820d0a0a337ebe71f849801eea335d464b349080" +checksum = "cfd016f0c045ad38b5251be2c9c0ab806917f82da4d36b2a327e5166adad9270" dependencies = [ - "autocfg 1.0.0", + "autocfg 1.0.1", "crossbeam-deque", "either", "rayon-core", @@ -4400,12 +4410,12 @@ dependencies = [ [[package]] name = "rayon-core" -version = "1.7.1" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e92e15d89083484e11353891f1af602cc661426deb9564c298b270c726973280" +checksum = "91739a34c4355b5434ce54c9086c5895604a9c278586d1f1aa95e04f66b525a0" dependencies = [ + "crossbeam-channel 0.4.3", "crossbeam-deque", - "crossbeam-queue", "crossbeam-utils 0.7.2", "lazy_static 1.4.0", "num_cpus", @@ -5187,9 +5197,9 @@ dependencies = [ [[package]] name = "syn" -version = "1.0.38" +version = "1.0.39" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e69abc24912995b3038597a7a593be5053eb0fb44f3cc5beec0deb421790c1f4" +checksum = "891d8d6567fe7c7f8835a3a98af4208f3846fba258c1bc3c31d6e506239f11f9" dependencies = [ "proc-macro2", "quote", @@ -5632,9 +5642,9 @@ dependencies = [ [[package]] name = "trash" -version = "1.1.0" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bcd4ea126e9b491aa0ee8043f5519cd56498413997a6c3859c8a9812aa300803" +checksum = "329be7bb48445d16bf4c241ba9514af46f2189c715bf5fd854e38f7c95f60194" dependencies = [ "winapi 0.3.9", ] @@ -5875,9 +5885,9 @@ checksum = "6454029bf181f092ad1b853286f23e2c507d8e8194d01d92da4a55c274a5508c" [[package]] name = "vec-arena" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "17dfb54bf57c9043f4616cb03dab30eff012cc26631b797d8354b916708db919" +checksum = "8cb18268690309760d59ee1a9b21132c126ba384f374c59a94db4bc03adeb561" [[package]] name = "vec_map" diff --git a/crates/nu-cli/src/cli.rs b/crates/nu-cli/src/cli.rs index 573465ce8d..27fe1640f4 100644 --- a/crates/nu-cli/src/cli.rs +++ b/crates/nu-cli/src/cli.rs @@ -419,6 +419,7 @@ pub fn create_default_context( whole_stream_command(MathStddev), whole_stream_command(MathSummation), whole_stream_command(MathVariance), + whole_stream_command(MathProduct), // File format output whole_stream_command(To), whole_stream_command(ToCSV), diff --git a/crates/nu-cli/src/commands.rs b/crates/nu-cli/src/commands.rs index 77e62959e9..5c43a6c86c 100644 --- a/crates/nu-cli/src/commands.rs +++ b/crates/nu-cli/src/commands.rs @@ -199,8 +199,8 @@ pub(crate) use last::Last; pub(crate) use lines::Lines; pub(crate) use ls::Ls; pub(crate) use math::{ - Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathStddev, - MathSummation, MathVariance, + Math, MathAverage, MathEval, MathMaximum, MathMedian, MathMinimum, MathMode, MathProduct, + MathStddev, MathSummation, MathVariance, }; pub(crate) use merge::Merge; pub(crate) use mkdir::Mkdir; diff --git a/crates/nu-cli/src/commands/math/mod.rs b/crates/nu-cli/src/commands/math/mod.rs index a383a256c8..563df92f6d 100644 --- a/crates/nu-cli/src/commands/math/mod.rs +++ b/crates/nu-cli/src/commands/math/mod.rs @@ -5,6 +5,7 @@ pub mod max; pub mod median; pub mod min; pub mod mode; +pub mod product; pub mod stddev; pub mod sum; pub mod variance; @@ -19,6 +20,7 @@ pub use max::SubCommand as MathMaximum; pub use median::SubCommand as MathMedian; pub use min::SubCommand as MathMinimum; pub use mode::SubCommand as MathMode; +pub use product::SubCommand as MathProduct; pub use stddev::SubCommand as MathStddev; pub use sum::SubCommand as MathSummation; pub use variance::SubCommand as MathVariance; diff --git a/crates/nu-cli/src/commands/math/product.rs b/crates/nu-cli/src/commands/math/product.rs new file mode 100644 index 0000000000..f745545d36 --- /dev/null +++ b/crates/nu-cli/src/commands/math/product.rs @@ -0,0 +1,126 @@ +use crate::commands::math::reducers::{reducer_for, Reduce}; +use crate::commands::math::utils::run_with_function; +use crate::commands::WholeStreamCommand; +use crate::prelude::*; +use nu_errors::ShellError; +use nu_protocol::{ + hir::{convert_number_to_u64, Number}, + Primitive, Signature, UntaggedValue, Value, +}; + +pub struct SubCommand; + +#[async_trait] +impl WholeStreamCommand for SubCommand { + fn name(&self) -> &str { + "math product" + } + + fn signature(&self) -> Signature { + Signature::build("math product") + } + + fn usage(&self) -> &str { + "Finds the product of a list of numbers or tables" + } + + async fn run( + &self, + args: CommandArgs, + registry: &CommandRegistry, + ) -> Result { + run_with_function( + RunnableContext { + input: args.input, + registry: registry.clone(), + shell_manager: args.shell_manager, + host: args.host, + ctrl_c: args.ctrl_c, + current_errors: args.current_errors, + name: args.call_info.name_tag, + raw_input: args.raw_input, + }, + product, + ) + .await + } + + fn examples(&self) -> Vec { + vec![Example { + description: "Get the product of a list of numbers", + example: "echo [2 3 3 4] | math product", + result: Some(vec![UntaggedValue::int(72).into()]), + }] + } +} + +fn to_byte(value: &Value) -> Option { + match &value.value { + UntaggedValue::Primitive(Primitive::Int(num)) => Some( + UntaggedValue::Primitive(Primitive::Filesize(convert_number_to_u64(&Number::Int( + num.clone(), + )))) + .into_untagged_value(), + ), + _ => None, + } +} + +/// Calculate product of given values +pub fn product(values: &[Value], name: &Tag) -> Result { + let prod = reducer_for(Reduce::Product); + + let first = values.get(0).ok_or_else(|| { + ShellError::unexpected("Cannot perform aggregate math operation on empty data") + })?; + + match first { + v if v.is_filesize() => to_byte(&prod( + UntaggedValue::int(1).into_untagged_value(), + values + .iter() + .map(|v| match v { + Value { + value: UntaggedValue::Primitive(Primitive::Filesize(num)), + .. + } => UntaggedValue::int(*num as usize).into_untagged_value(), + other => other.clone(), + }) + .collect::>(), + )?) + .ok_or_else(|| { + ShellError::labeled_error( + "could not convert to decimal", + "could not convert to decimal", + &name.span, + ) + }), + + v if v.is_none() => prod( + UntaggedValue::int(1).into_untagged_value(), + values + .iter() + .map(|v| match v { + Value { + value: UntaggedValue::Primitive(Primitive::Nothing), + .. + } => UntaggedValue::int(1).into_untagged_value(), + other => other.clone(), + }) + .collect::>(), + ), + _ => prod(UntaggedValue::int(1).into_untagged_value(), values.to_vec()), + } +} + +#[cfg(test)] +mod tests { + use super::SubCommand; + + #[test] + fn examples_work_as_expected() { + use crate::examples::test as test_examples; + + test_examples(SubCommand {}) + } +} diff --git a/crates/nu-cli/src/commands/math/reducers.rs b/crates/nu-cli/src/commands/math/reducers.rs index faa5d6ba7d..02569743fd 100644 --- a/crates/nu-cli/src/commands/math/reducers.rs +++ b/crates/nu-cli/src/commands/math/reducers.rs @@ -47,6 +47,7 @@ pub fn reducer_for( )), Reduce::Minimum => Box::new(|_, values| min(values)), Reduce::Maximum => Box::new(|_, values| max(values)), + Reduce::Product => Box::new(|_, values| product(values)), } } @@ -54,6 +55,7 @@ pub enum Reduce { Summation, Minimum, Maximum, + Product, Default, } @@ -133,3 +135,34 @@ pub fn min(data: Vec) -> Result { tag: Tag::unknown(), }) } + +pub fn product(data: Vec) -> Result { + if data.is_empty() { + return Err(ShellError::unexpected(ERR_EMPTY_DATA)); + } + + let mut prod = UntaggedValue::int(1).into_untagged_value(); + for value in data { + match value.value { + UntaggedValue::Primitive(_) => { + prod = match compute_values(Operator::Multiply, &prod, &value) { + Ok(v) => v.into_untagged_value(), + Err((left_type, right_type)) => { + return Err(ShellError::coerce_error( + left_type.spanned_unknown(), + right_type.spanned_unknown(), + )) + } + }; + } + _ => { + return Err(ShellError::labeled_error( + "Attempted to compute the product of a value that cannot be multiplied.", + "value appears here", + value.tag.span, + )) + } + } + } + Ok(prod) +} diff --git a/docs/commands/math.md b/docs/commands/math.md index 6784b500dd..f61d3f3934 100644 --- a/docs/commands/math.md +++ b/docs/commands/math.md @@ -9,6 +9,7 @@ Currently the following functions are implemented: * `math max`: Finds the maximum within a list of numbers or tables * `math median`: Finds the median of a list of numbers or tables * `math sum`: Finds the sum of a list of numbers or tables +* `math product`: Finds the product of a list of numbers or tables However, the mathematical functions like `min` and `max` are more permissive and also work on `Dates`. @@ -90,6 +91,11 @@ To get the average of the file sizes in a directory, simply pipe the size column ───┴────────── ``` +```shell +> echo [2 3 3 4] | math product +72 +``` + ### Dates ```shell