Imports name this capability’s declared dependencies, which fune builds next to it in your project; each one links to its page.
use super::funejson::Value; ← the fune runtime: the JSON value the test vectors use; fune build keeps it only where a signature takes one
const MAX_SAFE: i128 = 9_007_199_254_740_991;
fn gcd(a: i64, b: i64) -> i64 {
let (mut a, mut b) = (a.abs(), b.abs());
while b != 0 {
let t = a % b;
a = b;
b = t;
}
a
}
fn fraction(numerator: i64, denominator: i64) -> Fraction {
let g = match gcd(numerator, denominator) {
0 => 1,
g => g,
};
Fraction {
numerator: numerator / g,
denominator: denominator / g,
}
}
/// Mean, median and mode of a list of integers.
///
/// Mean and median are exact fractions; their float forms come from a single
/// correctly rounded division, so every language returns the same double.
///
/// # Panics
/// Panics on an empty list, a value outside the safe integer range, or a sum
/// or median outside it.
pub fn mean_median_mode(values: &[i64]) -> CentralTendency {
if values.is_empty() {
panic!("values must not be empty");
}
let mut total: i128 = 0;
for &v in values {
if (v as i128).abs() > MAX_SAFE {
panic!(
"values must be safe integers (magnitude at most 9007199254740991), received {}",
v
);
}
total += v as i128;
}
if total.abs() > MAX_SAFE {
panic!("sum exceeds the safe integer range");
}
let sum = total as i64;
let count = values.len();
let mut sorted = values.to_vec();
sorted.sort_unstable();
let median = if count % 2 == 1 {
Fraction {
numerator: sorted[(count - 1) / 2],
denominator: 1,
}
} else {
let pair = sorted[count / 2 - 1] as i128 + sorted[count / 2] as i128;
if pair % 2 == 0 {
Fraction {
numerator: (pair / 2) as i64,
denominator: 1,
}
} else {
if pair.abs() > MAX_SAFE {
panic!("median exceeds the safe integer range");
}
Fraction {
numerator: pair as i64,
denominator: 2,
}
}
};
// Runs of equal values in the sorted copy give frequencies in ascending
// value order, so the modes come out sorted without a second sort.
let mut modes: Vec<i64> = Vec::new();
let mut mode_frequency = 0usize;
let mut i = 0usize;
while i < count {
let mut j = i;
while j < count && sorted[j] == sorted[i] {
j += 1;
}
let run = j - i;
if run > mode_frequency {
mode_frequency = run;
modes = vec![sorted[i]];
} else if run == mode_frequency {
modes.push(sorted[i]);
}
i = j;
}
CentralTendency {
count: count as i64,
sum,
mean: fraction(sum, count as i64),
mean_value: sum as f64 / count as f64 + 0.0,
median_value: median.numerator as f64 / median.denominator as f64 + 0.0,
median,
modes,
mode_frequency: mode_frequency as i64,
}
}
pub fn fraction_to_value(f: &Fraction) -> Value {
Value::obj(vec![
("numerator", Value::Int(f.numerator)),
("denominator", Value::Int(f.denominator)),
])
}
pub fn central_tendency_to_value(c: &CentralTendency) -> Value {
Value::obj(vec![
("count", Value::Int(c.count)),
("sum", Value::Int(c.sum)),
("mean", fraction_to_value(&c.mean)),
("meanValue", Value::Float(c.mean_value)),
("median", fraction_to_value(&c.median)),
("medianValue", Value::Float(c.median_value)),
("modes", Value::Arr(c.modes.iter().map(|m| Value::Int(*m)).collect())),
("modeFrequency", Value::Int(c.mode_frequency)),
])
}
pub fn fune_vector(args: &[Value]) -> Value {
// Refuse what the typed signature cannot hold, with the wording TypeScript
// and Python use, rather than let the conversion below quietly change it.
for v in args[0].as_arr() {
if !matches!(v, Value::Int(_)) && !matches!(v, Value::Float(f) if f.fract() == 0.0) {
panic!("values must be integers, received {:?}", v);
}
}
let values: Vec<i64> = args[0].as_arr().iter().map(|v| v.as_i64()).collect();
central_tendency_to_value(&mean_median_mode(&values))
}