Convert sum command into subcommand of the math command (#2004)

* Convert sum command into subcommand of the math command

* Add bullet points to math.md documentation
This commit is contained in:
Joseph T. Lyons
2020-06-18 22:02:01 -04:00
committed by GitHub
parent 5f9de80d9b
commit 53a6e9f0bd
26 changed files with 175 additions and 143 deletions

View File

@ -352,7 +352,7 @@ pub fn create_default_context(
whole_stream_command(MathMedian),
whole_stream_command(MathMinimum),
whole_stream_command(MathMaximum),
whole_stream_command(Sum),
whole_stream_command(MathSummation),
// File format output
whole_stream_command(To),
whole_stream_command(ToBSON),

View File

@ -102,7 +102,6 @@ pub(crate) mod sort_by;
pub(crate) mod split;
pub(crate) mod split_by;
pub(crate) mod str_;
pub(crate) mod sum;
#[allow(unused)]
pub(crate) mod t_sort_by;
pub(crate) mod table;
@ -199,7 +198,7 @@ pub(crate) use lines::Lines;
pub(crate) use ls::Ls;
#[allow(unused_imports)]
pub(crate) use map_max_by::MapMaxBy;
pub(crate) use math::{Math, MathAverage, MathMaximum, MathMedian, MathMinimum};
pub(crate) use math::{Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathSummation};
pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir;
pub(crate) use mv::Move;
@ -236,7 +235,6 @@ pub(crate) use str_::{
Str, StrCapitalize, StrDowncase, StrFindReplace, StrSet, StrSubstring, StrToDatetime,
StrToDecimal, StrToInteger, StrTrim, StrUpcase,
};
pub(crate) use sum::Sum;
#[allow(unused_imports)]
pub(crate) use t_sort_by::TSortBy;
pub(crate) use table::Table;

View File

@ -56,7 +56,7 @@ impl WholeStreamCommand for Each {
},
Example {
description: "Echo the sum of each row",
example: "echo [[1 2] [3 4]] | each { echo $it | sum }",
example: "echo [[1 2] [3 4]] | each { echo $it | math sum }",
result: Some(vec![
UntaggedValue::int(3).into(),
UntaggedValue::int(7).into(),

View File

@ -22,7 +22,7 @@ impl WholeStreamCommand for SubCommand {
}
fn usage(&self) -> &str {
"Gets the average of a list of numbers"
"Finds the average of a list of numbers or tables"
}
async fn run(
@ -56,7 +56,7 @@ impl WholeStreamCommand for SubCommand {
}
pub fn average(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Sum);
let sum = reducer_for(Reduce::Summation);
let number = BigDecimal::from_usize(values.len()).ok_or_else(|| {
ShellError::labeled_error(

View File

@ -35,7 +35,7 @@ impl WholeStreamCommand for Command {
mod tests {
use super::*;
use crate::commands::math::{
average::average, max::maximum, min::minimum, utils::MathFunction,
average::average, max::maximum, min::minimum, sum::summation, utils::MathFunction,
};
use nu_plugin::test_helpers::value::{decimal, int};
use nu_protocol::Value;
@ -67,31 +67,41 @@ mod tests {
description: "Single value",
values: vec![int(10)],
expected_err: None,
expected_res: vec![Ok(decimal(10)), Ok(int(10)), Ok(int(10))],
expected_res: vec![Ok(decimal(10)), Ok(int(10)), Ok(int(10)), Ok(int(10))],
},
TestCase {
description: "Multiple Values",
values: vec![int(10), int(30), int(20)],
expected_err: None,
expected_res: vec![Ok(decimal(20)), Ok(int(10)), Ok(int(30))],
expected_res: vec![Ok(decimal(20)), Ok(int(10)), Ok(int(30)), Ok(int(60))],
},
TestCase {
description: "Mixed Values",
values: vec![int(10), decimal(26.5), decimal(26.5)],
expected_err: None,
expected_res: vec![Ok(decimal(21)), Ok(int(10)), Ok(decimal(26.5))],
expected_res: vec![
Ok(decimal(21)),
Ok(int(10)),
Ok(decimal(26.5)),
Ok(decimal(63)),
],
},
TestCase {
description: "Negative Values",
values: vec![int(10), int(-11), int(-14)],
expected_err: None,
expected_res: vec![Ok(decimal(-5)), Ok(int(-14)), Ok(int(10))],
expected_res: vec![Ok(decimal(-5)), Ok(int(-14)), Ok(int(10)), Ok(int(-15))],
},
TestCase {
description: "Mixed Negative Values",
values: vec![int(10), decimal(-11.5), decimal(-13.5)],
expected_err: None,
expected_res: vec![Ok(decimal(-5)), Ok(decimal(-13.5)), Ok(int(10))],
expected_res: vec![
Ok(decimal(-5)),
Ok(decimal(-13.5)),
Ok(int(10)),
Ok(decimal(-15)),
],
},
// TODO-Uncomment once I figure out how to structure tables
// TestCase {
@ -116,7 +126,7 @@ mod tests {
for tc in tt.iter() {
let tc: &TestCase = tc; // Just for type annotations
let math_functions: Vec<MathFunction> = vec![average, minimum, maximum];
let math_functions: Vec<MathFunction> = vec![average, minimum, maximum, summation];
let results = math_functions
.iter()
.map(|mf| mf(&tc.values, &test_tag))

View File

@ -18,7 +18,7 @@ impl WholeStreamCommand for SubCommand {
}
fn usage(&self) -> &str {
"Get the maximum of a list of numbers or tables"
"Finds the maximum within a list of numbers or tables"
}
async fn run(

View File

@ -121,7 +121,7 @@ pub fn median(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
fn compute_average(values: &[Value], name: impl Into<Tag>) -> Result<Value, ShellError> {
let name = name.into();
let sum = reducer_for(Reduce::Sum);
let sum = reducer_for(Reduce::Summation);
let number = BigDecimal::from_usize(2).ok_or_else(|| {
ShellError::labeled_error(
"could not convert to big decimal",

View File

@ -3,6 +3,7 @@ pub mod command;
pub mod max;
pub mod median;
pub mod min;
pub mod sum;
pub mod utils;
pub use average::SubCommand as MathAverage;
@ -10,3 +11,4 @@ pub use command::Command as Math;
pub use max::SubCommand as MathMaximum;
pub use median::SubCommand as MathMedian;
pub use min::SubCommand as MathMinimum;
pub use sum::SubCommand as MathSummation;

View File

@ -1,26 +1,25 @@
use crate::commands::math::utils::calculate;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use crate::utils::data_processing::{reducer_for, Reduce};
use nu_errors::ShellError;
use nu_protocol::{Dictionary, ReturnSuccess, Signature, UntaggedValue, Value};
use nu_protocol::{Dictionary, Signature, UntaggedValue, Value};
use num_traits::identities::Zero;
use indexmap::map::IndexMap;
pub struct Sum;
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand for Sum {
impl WholeStreamCommand for SubCommand {
fn name(&self) -> &str {
"sum"
"math sum"
}
fn signature(&self) -> Signature {
Signature::build("sum")
Signature::build("math sum")
}
fn usage(&self) -> &str {
"Sums the values."
"Finds the sum of a list of numbers or tables"
}
async fn run(
@ -28,16 +27,19 @@ impl WholeStreamCommand for Sum {
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
sum(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,
})
calculate(
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,
},
summation,
)
.await
}
@ -45,31 +47,28 @@ impl WholeStreamCommand for Sum {
vec![
Example {
description: "Sum a list of numbers",
example: "echo [1 2 3] | sum",
example: "echo [1 2 3] | math sum",
result: Some(vec![UntaggedValue::int(6).into()]),
},
Example {
description: "Get the disk usage for the current directory",
example: "ls --all --du | get size | sum",
example: "ls --all --du | get size | math sum",
result: None,
},
]
}
}
async fn sum(
RunnableContext { mut input, .. }: RunnableContext,
) -> Result<OutputStream, ShellError> {
let values: Vec<Value> = input.drain_vec().await;
let action = reducer_for(Reduce::Sum);
pub fn summation(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let sum = reducer_for(Reduce::Summation);
if values.iter().all(|v| v.is_primitive()) {
let total = action(Value::zero(), values)?;
Ok(OutputStream::one(ReturnSuccess::value(total)))
Ok(sum(Value::zero(), values.to_vec())?)
} else {
let mut column_values = IndexMap::new();
for value in values {
if let UntaggedValue::Row(row_dict) = value.value {
if let UntaggedValue::Row(row_dict) = value.value.clone() {
for (key, value) in row_dict.entries.iter() {
column_values
.entry(key.clone())
@ -80,32 +79,28 @@ async fn sum(
}
let mut column_totals = IndexMap::new();
for (col_name, col_vals) in column_values {
let sum = action(Value::zero(), col_vals);
match sum {
Ok(value) => {
column_totals.insert(col_name, value);
}
Err(err) => return Err(err),
};
let sum = sum(Value::zero(), col_vals)?;
column_totals.insert(col_name, sum);
}
Ok(OutputStream::one(ReturnSuccess::value(
UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_untagged_value(),
)))
Ok(UntaggedValue::Row(Dictionary {
entries: column_totals,
})
.into_value(name))
}
}
#[cfg(test)]
mod tests {
use super::Sum;
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(Sum {})
test_examples(SubCommand {})
}
}

View File

@ -288,14 +288,14 @@ pub fn reducer_for(
command: Reduce,
) -> Box<dyn Fn(Value, Vec<Value>) -> Result<Value, ShellError> + Send + Sync + 'static> {
match command {
Reduce::Sum | Reduce::Default => Box::new(formula(Value::zero(), Box::new(sum))),
Reduce::Summation | Reduce::Default => Box::new(formula(Value::zero(), Box::new(sum))),
Reduce::Minimum => Box::new(|_, values| min(values)),
Reduce::Maximum => Box::new(|_, values| max(values)),
}
}
pub enum Reduce {
Sum,
Summation,
Minimum,
Maximum,
Default,
@ -309,7 +309,7 @@ pub fn reduce(
let tag = tag.into();
let reduce_with = match reducer {
Some(cmd) if cmd == "sum" => reducer_for(Reduce::Sum),
Some(cmd) if cmd == "sum" => reducer_for(Reduce::Summation),
Some(cmd) if cmd == "min" => reducer_for(Reduce::Minimum),
Some(cmd) if cmd == "max" => reducer_for(Reduce::Maximum),
Some(_) | None => reducer_for(Reduce::Default),
@ -642,7 +642,7 @@ mod tests {
fn reducer_computes_given_a_sum_command() -> Result<(), ShellError> {
let subject = vec![int(1), int(1), int(1)];
let action = reducer_for(Reduce::Sum);
let action = reducer_for(Reduce::Summation);
assert_eq!(action(Value::zero(), subject)?, int(3));

View File

@ -4,7 +4,7 @@ use nu_test_support::nu;
fn drop_rows() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"echo '[{"foo": 3}, {"foo": 8}, {"foo": 4}]' | from json | drop 2 | get foo | sum | echo $it"#
r#"echo '[{"foo": 3}, {"foo": 8}, {"foo": 4}]' | from json | drop 2 | get foo | math sum | echo $it"#
);
assert_eq!(actual.out, "3");

View File

@ -5,7 +5,7 @@ fn each_works_separately() {
let actual = nu!(
cwd: "tests/fixtures/formats", pipeline(
r#"
echo [1 2 3] | each { echo $it 10 | sum } | to json | echo $it
echo [1 2 3] | each { echo $it 10 | math sum } | to json | echo $it
"#
));

View File

@ -22,7 +22,7 @@ fn adds_value_provided_if_column_is_empty() {
open likes.csv
| empty? likes 1
| get likes
| sum
| math sum
| echo $it
"#
));
@ -43,7 +43,7 @@ fn adds_value_provided_for_columns_that_are_empty() {
{"boost": 1, "check": {}},
{"boost": null, "check": ["" {} [] ""]}
]
"#,
)]);
@ -53,7 +53,7 @@ fn adds_value_provided_for_columns_that_are_empty() {
open checks.json
| empty? boost check 1
| get boost check
| sum
| math sum
| echo $it
"#
));

View File

@ -22,7 +22,7 @@ fn rows() {
open caballeros.csv
| keep 3
| get lucky_code
| sum
| math sum
| echo $it
"#
));

View File

@ -41,7 +41,7 @@ fn condition_is_met() {
| keep-until "Chicken Collection" == "Red Chickens"
| str to-int "31/04/2020"
| get "31/04/2020"
| sum
| math sum
| echo $it
"#
));

View File

@ -41,7 +41,7 @@ fn condition_is_met() {
| keep-while "Chicken Collection" != "Blue Chickens"
| str to-int "31/04/2020"
| get "31/04/2020"
| sum
| math sum
| echo $it
"#
));

View File

@ -33,7 +33,7 @@ fn row() {
| merge { open new_caballeros.csv }
| where country in: ["Guayaquil Ecuador" "New Zealand"]
| get luck
| sum
| math sum
| echo $it
"#
));

View File

@ -40,7 +40,7 @@ fn condition_is_met() {
| skip-until "Chicken Collection" == "Red Chickens"
| str to-int "31/04/2020"
| get "31/04/2020"
| sum
| math sum
| echo $it
"#
));

View File

@ -108,7 +108,7 @@ fn converts_to_decimal() {
echo "3.1, 0.0415"
| split row ","
| str to-decimal
| sum
| math sum
"#
));
@ -130,7 +130,7 @@ fn sets() {
cwd: dirs.test(), pipeline(
r#"
open sample.toml
| str set wykittenshell package.name
| str set wykittenshell package.name
| get package.name
| echo $it
"#

View File

@ -25,7 +25,7 @@ fn all() {
open meals.json
| get meals
| get calories
| sum
| math sum
| echo $it
"#
));
@ -53,7 +53,7 @@ fn outputs_zero_with_no_input() {
let actual = nu!(
cwd: dirs.test(), pipeline(
r#"
sum
math sum
| echo $it
"#
));
@ -74,7 +74,7 @@ fn compute_sum_of_individual_row() -> Result<(), String> {
for (column_name, expected_value) in answers_for_columns.iter() {
let actual = nu!(
cwd: "tests/fixtures/formats/",
format!("open sample-ps-output.json | select {} | sum | get {}", column_name, column_name)
format!("open sample-ps-output.json | select {} | math sum | get {}", column_name, column_name)
);
let result =
f64::from_str(&actual.out).map_err(|_| String::from("Failed to parse float."))?;
@ -95,7 +95,7 @@ fn compute_sum_of_table() -> Result<(), String> {
for (column_name, expected_value) in answers_for_columns.iter() {
let actual = nu!(
cwd: "tests/fixtures/formats/",
format!("open sample-ps-output.json | select cpu mem virtual | sum | get {}", column_name)
format!("open sample-ps-output.json | select cpu mem virtual | math sum | get {}", column_name)
);
let result =
f64::from_str(&actual.out).map_err(|_| String::from("Failed to parse float."))?;
@ -108,7 +108,7 @@ fn compute_sum_of_table() -> Result<(), String> {
fn sum_of_a_row_containing_a_table_is_an_error() {
let actual = nu!(
cwd: "tests/fixtures/formats/",
"open sample-sys-output.json | sum"
"open sample-sys-output.json | math sum"
);
assert!(actual
.err

View File

@ -14,7 +14,7 @@ fn filters_by_unit_size_comparison() {
fn filters_with_nothing_comparison() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"echo '[{"foo": 3}, {"foo": null}, {"foo": 4}]' | from json | get foo | compact | where $it > 1 | sum | echo $it"#
r#"echo '[{"foo": 3}, {"foo": null}, {"foo": 4}]' | from json | get foo | compact | where $it > 1 | math sum | echo $it"#
);
assert_eq!(actual.out, "7");
@ -24,7 +24,7 @@ fn filters_with_nothing_comparison() {
fn where_in_table() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"echo '[{"name": "foo", "size": 3}, {"name": "foo", "size": 2}, {"name": "bar", "size": 4}]' | from json | where name in: ["foo"] | get size | sum | echo $it"#
r#"echo '[{"name": "foo", "size": 3}, {"name": "foo", "size": 2}, {"name": "bar", "size": 4}]' | from json | where name in: ["foo"] | get size | math sum | echo $it"#
);
assert_eq!(actual.out, "5");
@ -34,7 +34,7 @@ fn where_in_table() {
fn where_not_in_table() {
let actual = nu!(
cwd: "tests/fixtures/formats",
r#"echo '[{"name": "foo", "size": 3}, {"name": "foo", "size": 2}, {"name": "bar", "size": 4}]' | from json | where name not-in: ["foo"] | get size | sum | echo $it"#
r#"echo '[{"name": "foo", "size": 3}, {"name": "foo", "size": 2}, {"name": "bar", "size": 4}]' | from json | where name not-in: ["foo"] | get size | math sum | echo $it"#
);
assert_eq!(actual.out, "4");

View File

@ -36,14 +36,14 @@ mod tests {
| from-csv
| get rusty_luck
| str --to-int
| sum
| math sum
| echo "$it"
"#,
);
assert_eq!(
actual,
r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str --to-int | sum | echo "$it""#
r#"open los_tres_amigos.txt | from-csv | get rusty_luck | str --to-int | math sum | echo "$it""#
);
}
}