Make mode subcommand: math mode (#2043)

* Update calculate to return a table when Value is a table

* impl mode subcommand for math

* add tests for math mode subcommand

* add table/row tests for math mode subcommand

* fix formatting
This commit is contained in:
Ali Mousa 2020-06-24 10:57:27 -07:00 committed by GitHub
parent 72f7406057
commit 93144a0132
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 487 additions and 337 deletions

693
Cargo.lock generated

File diff suppressed because it is too large Load Diff

View File

@ -351,6 +351,7 @@ pub fn create_default_context(
whole_stream_command(MathAverage), whole_stream_command(MathAverage),
whole_stream_command(MathMedian), whole_stream_command(MathMedian),
whole_stream_command(MathMinimum), whole_stream_command(MathMinimum),
whole_stream_command(MathMode),
whole_stream_command(MathMaximum), whole_stream_command(MathMaximum),
whole_stream_command(MathSummation), whole_stream_command(MathSummation),
// File format output // File format output

View File

@ -198,7 +198,9 @@ pub(crate) use lines::Lines;
pub(crate) use ls::Ls; pub(crate) use ls::Ls;
#[allow(unused_imports)] #[allow(unused_imports)]
pub(crate) use map_max_by::MapMaxBy; pub(crate) use map_max_by::MapMaxBy;
pub(crate) use math::{Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathSummation}; pub(crate) use math::{
Math, MathAverage, MathMaximum, MathMedian, MathMinimum, MathMode, MathSummation,
};
pub(crate) use merge::Merge; pub(crate) use merge::Merge;
pub(crate) use mkdir::Mkdir; pub(crate) use mkdir::Mkdir;
pub(crate) use mv::Move; pub(crate) use mv::Move;

View File

@ -35,11 +35,11 @@ impl WholeStreamCommand for Command {
mod tests { mod tests {
use super::*; use super::*;
use crate::commands::math::{ use crate::commands::math::{
avg::average, max::maximum, median::median, min::minimum, sum::summation, utils::calculate, avg::average, max::maximum, median::median, min::minimum, mode::mode, sum::summation,
utils::MathFunction, utils::calculate, utils::MathFunction,
}; };
use nu_plugin::row; use nu_plugin::row;
use nu_plugin::test_helpers::value::{decimal, int}; use nu_plugin::test_helpers::value::{decimal, int, table};
use nu_protocol::Value; use nu_protocol::Value;
#[test] #[test]
@ -74,6 +74,7 @@ mod tests {
Ok(int(10)), Ok(int(10)),
Ok(int(10)), Ok(int(10)),
Ok(int(10)), Ok(int(10)),
Ok(table(&vec![int(10)])),
Ok(int(10)), Ok(int(10)),
], ],
}, },
@ -86,6 +87,7 @@ mod tests {
Ok(int(10)), Ok(int(10)),
Ok(int(30)), Ok(int(30)),
Ok(int(20)), Ok(int(20)),
Ok(table(&vec![int(10), int(20), int(30)])),
Ok(int(60)), Ok(int(60)),
], ],
}, },
@ -98,6 +100,7 @@ mod tests {
Ok(int(10)), Ok(int(10)),
Ok(decimal(26.5)), Ok(decimal(26.5)),
Ok(decimal(26.5)), Ok(decimal(26.5)),
Ok(table(&vec![decimal(26.5)])),
Ok(decimal(63)), Ok(decimal(63)),
], ],
}, },
@ -110,6 +113,7 @@ mod tests {
Ok(int(-14)), Ok(int(-14)),
Ok(int(10)), Ok(int(10)),
Ok(int(-11)), Ok(int(-11)),
Ok(table(&vec![int(-14), int(-11), int(10)])),
Ok(int(-15)), Ok(int(-15)),
], ],
}, },
@ -122,6 +126,7 @@ mod tests {
Ok(decimal(-13.5)), Ok(decimal(-13.5)),
Ok(int(10)), Ok(int(10)),
Ok(decimal(-11.5)), Ok(decimal(-11.5)),
Ok(table(&vec![decimal(-13.5), decimal(-11.5), int(10)])),
Ok(decimal(-15)), Ok(decimal(-15)),
], ],
}, },
@ -139,6 +144,10 @@ mod tests {
Ok(row!["col1".to_owned() => int(1), "col2".to_owned() => int(5)]), Ok(row!["col1".to_owned() => int(1), "col2".to_owned() => int(5)]),
Ok(row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)]), Ok(row!["col1".to_owned() => int(4), "col2".to_owned() => int(8)]),
Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]), Ok(row!["col1".to_owned() => decimal(2.5), "col2".to_owned() => decimal(6.5)]),
Ok(row![
"col1".to_owned() => table(&vec![int(1), int(2), int(3), int(4)]),
"col2".to_owned() => table(&vec![int(5), int(6), int(7), int(8)])
]),
Ok(row!["col1".to_owned() => int(10), "col2".to_owned() => int(26)]), Ok(row!["col1".to_owned() => int(10), "col2".to_owned() => int(26)]),
], ],
}, },
@ -154,8 +163,7 @@ mod tests {
for tc in tt.iter() { for tc in tt.iter() {
let tc: &TestCase = tc; // Just for type annotations let tc: &TestCase = tc; // Just for type annotations
let math_functions: Vec<MathFunction> = let math_functions: Vec<MathFunction> =
vec![average, minimum, maximum, median, summation]; vec![average, minimum, maximum, median, mode, summation];
let results = math_functions let results = math_functions
.into_iter() .into_iter()
.map(|mf| calculate(&tc.values, &test_tag, mf)) .map(|mf| calculate(&tc.values, &test_tag, mf))

View File

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

View File

@ -0,0 +1,94 @@
use crate::commands::math::utils::run_with_function;
use crate::commands::WholeStreamCommand;
use crate::prelude::*;
use nu_errors::ShellError;
use nu_protocol::{Signature, UntaggedValue, Value};
use std::cmp::Ordering;
pub struct SubCommand;
#[async_trait]
impl WholeStreamCommand 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"
}
async fn run(
&self,
args: CommandArgs,
registry: &CommandRegistry,
) -> Result<OutputStream, ShellError> {
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,
},
mode,
)
.await
}
fn examples(&self) -> Vec<Example> {
vec![Example {
description: "Get the mode(s) of a list of numbers",
example: "echo [3 3 9 12 12 15] | math mode",
result: Some(vec![
UntaggedValue::int(3).into_untagged_value(),
UntaggedValue::int(12).into_untagged_value(),
]),
}]
}
}
pub fn mode(values: &[Value], name: &Tag) -> Result<Value, ShellError> {
let mut frequency_map = std::collections::HashMap::new();
for v in values {
let counter = frequency_map.entry(v.value.clone()).or_insert(0);
*counter += 1;
}
let mut max_freq = -1;
let mut modes = Vec::<Value>::new();
for (value, frequency) in frequency_map.iter() {
match max_freq.cmp(&frequency) {
Ordering::Less => {
max_freq = *frequency;
modes.clear();
modes.push(value.clone().into_value(name));
}
Ordering::Equal => {
modes.push(value.clone().into_value(name));
}
Ordering::Greater => (),
}
}
crate::commands::sort_by::sort(&mut modes, &[], name)?;
Ok(UntaggedValue::Table(modes).into_value(name))
}
#[cfg(test)]
mod tests {
use super::SubCommand;
#[test]
fn examples_work_as_expected() {
use crate::examples::test as test_examples;
test_examples(SubCommand {})
}
}

View File

@ -15,7 +15,17 @@ pub async fn run_with_function(
let values: Vec<Value> = input.drain_vec().await; let values: Vec<Value> = input.drain_vec().await;
let res = calculate(&values, &name, mf); let res = calculate(&values, &name, mf);
match res { match res {
Ok(v) => Ok(OutputStream::one(ReturnSuccess::value(v))), Ok(v) => {
if v.value.is_table() {
Ok(OutputStream::from(
v.table_entries()
.map(|v| ReturnSuccess::value(v.clone()))
.collect::<Vec<_>>(),
))
} else {
Ok(OutputStream::one(ReturnSuccess::value(v)))
}
}
Err(e) => Err(e), Err(e) => Err(e),
} }
} }