203 lines
6.0 KiB
Rust
203 lines
6.0 KiB
Rust
use crate::expressions::types::CellReferenceIndex;
|
|
use crate::{
|
|
calc_result::CalcResult, expressions::parser::Node, expressions::token::Error, model::Model,
|
|
};
|
|
|
|
impl<'a> Model<'a> {
|
|
// Helper to collect numeric values from the 2nd argument of RANK.*
|
|
fn collect_rank_values(
|
|
&mut self,
|
|
arg: &Node,
|
|
cell: CellReferenceIndex,
|
|
) -> Result<Vec<f64>, CalcResult> {
|
|
let values = match self.evaluate_node_in_context(arg, cell) {
|
|
CalcResult::Array(array) => match self.values_from_array(array) {
|
|
Ok(v) => v,
|
|
Err(e) => {
|
|
return Err(CalcResult::Error {
|
|
error: Error::VALUE,
|
|
origin: cell,
|
|
message: format!("Unsupported array argument: {}", e),
|
|
})
|
|
}
|
|
},
|
|
CalcResult::Range { left, right } => self.values_from_range(left, right)?,
|
|
CalcResult::Boolean(value) => {
|
|
if !matches!(arg, Node::ReferenceKind { .. }) {
|
|
vec![Some(if value { 1.0 } else { 0.0 })]
|
|
} else {
|
|
return Err(CalcResult::Error {
|
|
error: Error::NUM,
|
|
origin: cell,
|
|
message: "Unsupported argument type".to_string(),
|
|
});
|
|
}
|
|
}
|
|
_ => {
|
|
return Err(CalcResult::Error {
|
|
error: Error::NIMPL,
|
|
origin: cell,
|
|
message: "Unsupported argument type".to_string(),
|
|
})
|
|
}
|
|
};
|
|
|
|
let numeric_values: Vec<f64> = values.into_iter().flatten().collect();
|
|
Ok(numeric_values)
|
|
}
|
|
|
|
// RANK.EQ(number, ref, [order])
|
|
pub(crate) fn fn_rank_eq(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if !(2..=3).contains(&args.len()) {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
|
|
// number
|
|
let number = match self.get_number_no_bools(&args[0], cell) {
|
|
Ok(f) => f,
|
|
Err(e) => return e,
|
|
};
|
|
|
|
// ref
|
|
let mut values = match self.collect_rank_values(&args[1], cell) {
|
|
Ok(v) => v,
|
|
Err(e) => return e,
|
|
};
|
|
|
|
if values.is_empty() {
|
|
return CalcResult::Error {
|
|
error: Error::NUM,
|
|
origin: cell,
|
|
message: "No numeric values for RANK.EQ".to_string(),
|
|
};
|
|
}
|
|
|
|
// order: default 0 (descending)
|
|
let order = if args.len() == 2 {
|
|
0.0
|
|
} else {
|
|
match self.get_number_no_bools(&args[2], cell) {
|
|
Ok(f) => f,
|
|
Err(e) => return e,
|
|
}
|
|
};
|
|
|
|
values.retain(|v| !v.is_nan());
|
|
|
|
// "better" = greater (descending) or smaller (ascending)
|
|
let mut better = 0;
|
|
let mut equal = 0;
|
|
|
|
if order == 0.0 {
|
|
// descending
|
|
for v in &values {
|
|
if *v > number {
|
|
better += 1;
|
|
} else if *v == number {
|
|
equal += 1;
|
|
}
|
|
}
|
|
} else {
|
|
// ascending
|
|
for v in &values {
|
|
if *v < number {
|
|
better += 1;
|
|
} else if *v == number {
|
|
equal += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if equal == 0 {
|
|
return CalcResult::Error {
|
|
error: Error::NA,
|
|
origin: cell,
|
|
message: "Number not found in reference for RANK.EQ".to_string(),
|
|
};
|
|
}
|
|
|
|
let rank = (better as f64) + 1.0;
|
|
CalcResult::Number(rank)
|
|
}
|
|
|
|
// RANK.AVG(number, ref, [order])
|
|
pub(crate) fn fn_rank_avg(&mut self, args: &[Node], cell: CellReferenceIndex) -> CalcResult {
|
|
if !(2..=3).contains(&args.len()) {
|
|
return CalcResult::new_args_number_error(cell);
|
|
}
|
|
|
|
// number
|
|
let number = match self.get_number_no_bools(&args[0], cell) {
|
|
Ok(f) => f,
|
|
Err(e) => return e,
|
|
};
|
|
|
|
// ref
|
|
let mut values = match self.collect_rank_values(&args[1], cell) {
|
|
Ok(v) => v,
|
|
Err(e) => return e,
|
|
};
|
|
|
|
if values.is_empty() {
|
|
return CalcResult::Error {
|
|
error: Error::NUM,
|
|
origin: cell,
|
|
message: "No numeric values for RANK.AVG".to_string(),
|
|
};
|
|
}
|
|
|
|
// order: default 0 (descending)
|
|
let order = if args.len() == 2 {
|
|
0.0
|
|
} else {
|
|
match self.get_number_no_bools(&args[2], cell) {
|
|
Ok(f) => f,
|
|
Err(e) => return e,
|
|
}
|
|
};
|
|
|
|
values.retain(|v| !v.is_nan());
|
|
|
|
// > or < depending on order
|
|
let mut better = 0;
|
|
let mut equal = 0;
|
|
|
|
if order == 0.0 {
|
|
// descending
|
|
for v in &values {
|
|
if *v > number {
|
|
better += 1;
|
|
} else if *v == number {
|
|
equal += 1;
|
|
}
|
|
}
|
|
} else {
|
|
// ascending
|
|
for v in &values {
|
|
if *v < number {
|
|
better += 1;
|
|
} else if *v == number {
|
|
equal += 1;
|
|
}
|
|
}
|
|
}
|
|
|
|
if equal == 0 {
|
|
return CalcResult::Error {
|
|
error: Error::NA,
|
|
origin: cell,
|
|
message: "Number not found in reference for RANK.AVG".to_string(),
|
|
};
|
|
}
|
|
|
|
// For ties, average of the ranks. If the equal values occupy positions
|
|
// (better+1) ..= (better+equal), the average is:
|
|
// better + (equal + 1) / 2
|
|
let better_f = better as f64;
|
|
let equal_f = equal as f64;
|
|
let rank = better_f + (equal_f + 1.0) / 2.0;
|
|
|
|
CalcResult::Number(rank)
|
|
}
|
|
}
|