mirror of
https://github.com/nushell/nushell.git
synced 2025-01-08 23:40:17 +01:00
Last three math commands, eval
, variance
and stddev
(#292)
* MathEval Variance and Stddev * Fix tests and linting * Typo
This commit is contained in:
parent
345b51b272
commit
c7d159a0f3
23
Cargo.lock
generated
23
Cargo.lock
generated
@ -404,6 +404,12 @@ dependencies = [
|
|||||||
"tempfile",
|
"tempfile",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "fnv"
|
||||||
|
version = "1.0.7"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "getrandom"
|
name = "getrandom"
|
||||||
version = "0.2.3"
|
version = "0.2.3"
|
||||||
@ -545,6 +551,16 @@ dependencies = [
|
|||||||
"autocfg",
|
"autocfg",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "meval"
|
||||||
|
version = "0.2.0"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "f79496a5651c8d57cd033c5add8ca7ee4e3d5f7587a4777484640d9cb60392d9"
|
||||||
|
dependencies = [
|
||||||
|
"fnv",
|
||||||
|
"nom",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "miette"
|
name = "miette"
|
||||||
version = "3.2.0"
|
version = "3.2.0"
|
||||||
@ -620,6 +636,12 @@ dependencies = [
|
|||||||
"memoffset",
|
"memoffset",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "nom"
|
||||||
|
version = "1.2.4"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "a5b8c256fd9471521bcb84c3cdba98921497f1a331cbc15b8030fc63b82050ce"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ntapi"
|
name = "ntapi"
|
||||||
version = "0.3.6"
|
version = "0.3.6"
|
||||||
@ -665,6 +687,7 @@ dependencies = [
|
|||||||
"dialoguer",
|
"dialoguer",
|
||||||
"glob",
|
"glob",
|
||||||
"lscolors",
|
"lscolors",
|
||||||
|
"meval",
|
||||||
"nu-engine",
|
"nu-engine",
|
||||||
"nu-json",
|
"nu-json",
|
||||||
"nu-parser",
|
"nu-parser",
|
||||||
|
@ -30,6 +30,7 @@ bytesize = "1.1.0"
|
|||||||
dialoguer = "0.9.0"
|
dialoguer = "0.9.0"
|
||||||
rayon = "1.5.1"
|
rayon = "1.5.1"
|
||||||
titlecase = "1.1.0"
|
titlecase = "1.1.0"
|
||||||
|
meval = "0.2.0"
|
||||||
|
|
||||||
[features]
|
[features]
|
||||||
trash-support = ["trash"]
|
trash-support = ["trash"]
|
||||||
|
@ -66,6 +66,7 @@ pub fn create_default_context() -> EngineState {
|
|||||||
MathAvg,
|
MathAvg,
|
||||||
MathCeil,
|
MathCeil,
|
||||||
MathFloor,
|
MathFloor,
|
||||||
|
MathEval,
|
||||||
MathMax,
|
MathMax,
|
||||||
MathMedian,
|
MathMedian,
|
||||||
MathMin,
|
MathMin,
|
||||||
@ -73,7 +74,9 @@ pub fn create_default_context() -> EngineState {
|
|||||||
MathProduct,
|
MathProduct,
|
||||||
MathRound,
|
MathRound,
|
||||||
MathSqrt,
|
MathSqrt,
|
||||||
|
MathStddev,
|
||||||
MathSum,
|
MathSum,
|
||||||
|
MathVariance,
|
||||||
Mkdir,
|
Mkdir,
|
||||||
Module,
|
Module,
|
||||||
Mv,
|
Mv,
|
||||||
|
117
crates/nu-command/src/math/eval.rs
Normal file
117
crates/nu-command/src/math/eval.rs
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
use nu_engine::CallExt;
|
||||||
|
use nu_protocol::ast::Call;
|
||||||
|
use nu_protocol::engine::{Command, EngineState, Stack};
|
||||||
|
use nu_protocol::{
|
||||||
|
Example, PipelineData, ShellError, Signature, Span, Spanned, SyntaxShape, Value,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct SubCommand;
|
||||||
|
|
||||||
|
impl Command for SubCommand {
|
||||||
|
fn name(&self) -> &str {
|
||||||
|
"math eval"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Evaluate a math expression into a number"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("math eval").optional(
|
||||||
|
"math expression",
|
||||||
|
SyntaxShape::String,
|
||||||
|
"the math expression to evaluate",
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
let spanned_expr: Option<Spanned<String>> = call.opt(engine_state, stack, 0)?;
|
||||||
|
eval(spanned_expr, input, engine_state)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![Example {
|
||||||
|
description: "Evalulate math in the pipeline",
|
||||||
|
example: "'10 / 4' | math eval",
|
||||||
|
result: Some(Value::Float {
|
||||||
|
val: 2.5,
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
}]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn eval(
|
||||||
|
spanned_expr: Option<Spanned<String>>,
|
||||||
|
input: PipelineData,
|
||||||
|
engine_state: &EngineState,
|
||||||
|
) -> Result<PipelineData, ShellError> {
|
||||||
|
if let Some(expr) = spanned_expr {
|
||||||
|
match parse(&expr.item, &expr.span) {
|
||||||
|
Ok(value) => Ok(PipelineData::Value(value)),
|
||||||
|
Err(err) => Err(ShellError::UnsupportedInput(
|
||||||
|
format!("Math evaluation error: {}", err),
|
||||||
|
expr.span,
|
||||||
|
)),
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if let PipelineData::Value(Value::Nothing { .. }) = input {
|
||||||
|
return Ok(input);
|
||||||
|
}
|
||||||
|
input.map(
|
||||||
|
move |val| {
|
||||||
|
if let Ok(string) = val.as_string() {
|
||||||
|
match parse(&string, &val.span().unwrap_or_else(|_| Span::unknown())) {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(err) => Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
format!("Math evaluation error: {}", err),
|
||||||
|
val.span().unwrap_or_else(|_| Span::unknown()),
|
||||||
|
),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Value::Error {
|
||||||
|
error: ShellError::UnsupportedInput(
|
||||||
|
"Expected a string from pipeline".to_string(),
|
||||||
|
val.span().unwrap_or_else(|_| Span::unknown()),
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
engine_state.ctrlc.clone(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse(math_expression: &str, span: &Span) -> Result<Value, String> {
|
||||||
|
let mut ctx = meval::Context::new();
|
||||||
|
ctx.var("tau", std::f64::consts::TAU);
|
||||||
|
match meval::eval_str_with_context(math_expression, &ctx) {
|
||||||
|
Ok(num) if num.is_infinite() || num.is_nan() => Err("cannot represent result".to_string()),
|
||||||
|
Ok(num) => Ok(Value::Float {
|
||||||
|
val: num,
|
||||||
|
span: *span,
|
||||||
|
}),
|
||||||
|
Err(error) => Err(error.to_string().to_lowercase()),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
@ -2,6 +2,7 @@ mod abs;
|
|||||||
mod avg;
|
mod avg;
|
||||||
mod ceil;
|
mod ceil;
|
||||||
pub mod command;
|
pub mod command;
|
||||||
|
mod eval;
|
||||||
mod floor;
|
mod floor;
|
||||||
mod max;
|
mod max;
|
||||||
mod median;
|
mod median;
|
||||||
@ -11,13 +12,16 @@ mod product;
|
|||||||
mod reducers;
|
mod reducers;
|
||||||
mod round;
|
mod round;
|
||||||
mod sqrt;
|
mod sqrt;
|
||||||
|
mod stddev;
|
||||||
mod sum;
|
mod sum;
|
||||||
mod utils;
|
mod utils;
|
||||||
|
mod variance;
|
||||||
|
|
||||||
pub use abs::SubCommand as MathAbs;
|
pub use abs::SubCommand as MathAbs;
|
||||||
pub use avg::SubCommand as MathAvg;
|
pub use avg::SubCommand as MathAvg;
|
||||||
pub use ceil::SubCommand as MathCeil;
|
pub use ceil::SubCommand as MathCeil;
|
||||||
pub use command::MathCommand as Math;
|
pub use command::MathCommand as Math;
|
||||||
|
pub use eval::SubCommand as MathEval;
|
||||||
pub use floor::SubCommand as MathFloor;
|
pub use floor::SubCommand as MathFloor;
|
||||||
pub use max::SubCommand as MathMax;
|
pub use max::SubCommand as MathMax;
|
||||||
pub use median::SubCommand as MathMedian;
|
pub use median::SubCommand as MathMedian;
|
||||||
@ -26,4 +30,6 @@ pub use mode::SubCommand as MathMode;
|
|||||||
pub use product::SubCommand as MathProduct;
|
pub use product::SubCommand as MathProduct;
|
||||||
pub use round::SubCommand as MathRound;
|
pub use round::SubCommand as MathRound;
|
||||||
pub use sqrt::SubCommand as MathSqrt;
|
pub use sqrt::SubCommand as MathSqrt;
|
||||||
|
pub use stddev::SubCommand as MathStddev;
|
||||||
pub use sum::SubCommand as MathSum;
|
pub use sum::SubCommand as MathSum;
|
||||||
|
pub use variance::SubCommand as MathVariance;
|
||||||
|
85
crates/nu-command/src/math/stddev.rs
Normal file
85
crates/nu-command/src/math/stddev.rs
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
use super::variance::compute_variance as variance;
|
||||||
|
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 stddev"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("math stddev").switch(
|
||||||
|
"sample",
|
||||||
|
"calculate sample standard deviation",
|
||||||
|
Some('s'),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Finds the stddev of a list of numbers or tables"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let sample = call.has_flag("sample");
|
||||||
|
run_with_function(call, input, compute_stddev(sample))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get the stddev of a list of numbers",
|
||||||
|
example: "[1 2 3 4 5] | math stddev",
|
||||||
|
result: Some(Value::Float {
|
||||||
|
val: std::f64::consts::SQRT_2,
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the sample stddev of a list of numbers",
|
||||||
|
example: "[1 2 3 4 5] | math stddev -s",
|
||||||
|
result: Some(Value::Float {
|
||||||
|
val: 1.5811388300841898,
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute_stddev(sample: bool) -> impl Fn(&[Value], &Span) -> Result<Value, ShellError> {
|
||||||
|
move |values: &[Value], span: &Span| {
|
||||||
|
let variance = variance(sample)(values, span);
|
||||||
|
match variance {
|
||||||
|
Ok(Value::Float { val, span }) => Ok(Value::Float { val: val.sqrt(), span }),
|
||||||
|
Ok(Value::Int { val, span }) => Ok(Value::Float { val: (val as f64).sqrt(), span }),
|
||||||
|
Err(ShellError::UnsupportedInput(_, err_span)) => Err(ShellError::UnsupportedInput(
|
||||||
|
"Attempted to compute the standard deviation with an item that cannot be used for that.".to_string(),
|
||||||
|
err_span,
|
||||||
|
)),
|
||||||
|
other => other
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
@ -2,12 +2,10 @@ use nu_protocol::ast::Call;
|
|||||||
use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Span, Value};
|
use nu_protocol::{IntoPipelineData, PipelineData, ShellError, Span, Value};
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
pub type MathFunction = fn(values: &[Value], span: &Span) -> Result<Value, ShellError>;
|
|
||||||
|
|
||||||
pub fn run_with_function(
|
pub fn run_with_function(
|
||||||
call: &Call,
|
call: &Call,
|
||||||
input: PipelineData,
|
input: PipelineData,
|
||||||
mf: MathFunction,
|
mf: impl Fn(&[Value], &Span) -> Result<Value, ShellError>,
|
||||||
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
let name = call.head;
|
let name = call.head;
|
||||||
let res = calculate(input, name, mf);
|
let res = calculate(input, name, mf);
|
||||||
@ -20,7 +18,7 @@ pub fn run_with_function(
|
|||||||
fn helper_for_tables(
|
fn helper_for_tables(
|
||||||
values: PipelineData,
|
values: PipelineData,
|
||||||
name: Span,
|
name: Span,
|
||||||
mf: MathFunction,
|
mf: impl Fn(&[Value], &Span) -> Result<Value, ShellError>,
|
||||||
) -> Result<Value, ShellError> {
|
) -> Result<Value, ShellError> {
|
||||||
// If we are not dealing with Primitives, then perhaps we are dealing with a table
|
// If we are not dealing with Primitives, then perhaps we are dealing with a table
|
||||||
// Create a key for each column name
|
// Create a key for each column name
|
||||||
@ -63,7 +61,11 @@ fn helper_for_tables(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn calculate(values: PipelineData, name: Span, mf: MathFunction) -> Result<Value, ShellError> {
|
pub fn calculate(
|
||||||
|
values: PipelineData,
|
||||||
|
name: Span,
|
||||||
|
mf: impl Fn(&[Value], &Span) -> Result<Value, ShellError>,
|
||||||
|
) -> Result<Value, ShellError> {
|
||||||
match values {
|
match values {
|
||||||
PipelineData::Stream(_) => helper_for_tables(values, name, mf),
|
PipelineData::Stream(_) => helper_for_tables(values, name, mf),
|
||||||
PipelineData::Value(Value::List { ref vals, .. }) => match &vals[..] {
|
PipelineData::Value(Value::List { ref vals, .. }) => match &vals[..] {
|
||||||
|
126
crates/nu-command/src/math/variance.rs
Normal file
126
crates/nu-command/src/math/variance.rs
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
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 variance"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn signature(&self) -> Signature {
|
||||||
|
Signature::build("math variance").switch("sample", "calculate sample variance", Some('s'))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn usage(&self) -> &str {
|
||||||
|
"Finds the variance of a list of numbers or tables"
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(
|
||||||
|
&self,
|
||||||
|
_engine_state: &EngineState,
|
||||||
|
_stack: &mut Stack,
|
||||||
|
call: &Call,
|
||||||
|
input: PipelineData,
|
||||||
|
) -> Result<nu_protocol::PipelineData, nu_protocol::ShellError> {
|
||||||
|
let sample = call.has_flag("sample");
|
||||||
|
run_with_function(call, input, compute_variance(sample))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn examples(&self) -> Vec<Example> {
|
||||||
|
vec![
|
||||||
|
Example {
|
||||||
|
description: "Get the variance of a list of numbers",
|
||||||
|
example: "echo [1 2 3 4 5] | math variance",
|
||||||
|
result: Some(Value::Float {
|
||||||
|
val: 2.0,
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
Example {
|
||||||
|
description: "Get the sample variance of a list of numbers",
|
||||||
|
example: "[1 2 3 4 5] | math variance -s",
|
||||||
|
result: Some(Value::Float {
|
||||||
|
val: 2.5,
|
||||||
|
span: Span::unknown(),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn sum_of_squares(values: &[Value], span: &Span) -> Result<Value, ShellError> {
|
||||||
|
let n = Value::Int {
|
||||||
|
val: values.len() as i64,
|
||||||
|
span: *span,
|
||||||
|
};
|
||||||
|
let mut sum_x = Value::Int {
|
||||||
|
val: 0,
|
||||||
|
span: *span,
|
||||||
|
};
|
||||||
|
let mut sum_x2 = Value::Int {
|
||||||
|
val: 0,
|
||||||
|
span: *span,
|
||||||
|
};
|
||||||
|
for value in values {
|
||||||
|
let v = match &value {
|
||||||
|
Value::Int { .. }
|
||||||
|
| Value::Float { .. } => {
|
||||||
|
Ok(value)
|
||||||
|
},
|
||||||
|
_ => Err(ShellError::UnsupportedInput(
|
||||||
|
"Attempted to compute the sum of squared values of a value that cannot be summed or squared.".to_string(),
|
||||||
|
value.span().unwrap_or_else(|_| Span::unknown()),
|
||||||
|
))
|
||||||
|
}?;
|
||||||
|
let v_squared = &v.mul(*span, v)?;
|
||||||
|
sum_x2 = sum_x2.add(*span, v_squared)?;
|
||||||
|
sum_x = sum_x.add(*span, v)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
let sum_x_squared = sum_x.mul(*span, &sum_x)?;
|
||||||
|
let sum_x_squared_div_n = sum_x_squared.div(*span, &n)?;
|
||||||
|
|
||||||
|
let ss = sum_x2.sub(*span, &sum_x_squared_div_n)?;
|
||||||
|
|
||||||
|
Ok(ss)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn compute_variance(sample: bool) -> impl Fn(&[Value], &Span) -> Result<Value, ShellError> {
|
||||||
|
move |values: &[Value], span: &Span| {
|
||||||
|
let n = if sample {
|
||||||
|
values.len() - 1
|
||||||
|
} else {
|
||||||
|
values.len()
|
||||||
|
};
|
||||||
|
let sum_of_squares = sum_of_squares(values, span);
|
||||||
|
let ss = match sum_of_squares {
|
||||||
|
Err(ShellError::UnsupportedInput(_, err_span)) => Err(ShellError::UnsupportedInput(
|
||||||
|
"Attempted to compute the variance with an item that cannot be used for that."
|
||||||
|
.to_string(),
|
||||||
|
err_span,
|
||||||
|
)),
|
||||||
|
other => other,
|
||||||
|
}?;
|
||||||
|
let n = Value::Int {
|
||||||
|
val: n as i64,
|
||||||
|
span: *span,
|
||||||
|
};
|
||||||
|
ss.div(*span, &n)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod test {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_examples() {
|
||||||
|
use crate::test_examples;
|
||||||
|
|
||||||
|
test_examples(SubCommand {})
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user