mirror of
https://github.com/nushell/nushell.git
synced 2025-08-09 07:05:47 +02:00
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:
@ -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),
|
||||
|
@ -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;
|
||||
|
@ -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(),
|
||||
|
@ -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(
|
||||
|
@ -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))
|
||||
|
@ -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(
|
||||
|
@ -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",
|
||||
|
@ -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;
|
||||
|
@ -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 {})
|
||||
}
|
||||
}
|
@ -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));
|
||||
|
||||
|
Reference in New Issue
Block a user