nu_command/math/
mode.rs

1use crate::math::utils::run_with_function;
2use nu_engine::command_prelude::*;
3use std::{cmp::Ordering, collections::HashMap};
4
5#[derive(Clone)]
6pub struct MathMode;
7
8#[derive(Hash, Eq, PartialEq, Debug)]
9enum NumberTypes {
10    Float,
11    Int,
12    Duration,
13    Filesize,
14}
15
16#[derive(Hash, Eq, PartialEq, Debug)]
17struct HashableType {
18    bytes: [u8; 8],
19    original_type: NumberTypes,
20}
21
22impl HashableType {
23    fn new(bytes: [u8; 8], original_type: NumberTypes) -> HashableType {
24        HashableType {
25            bytes,
26            original_type,
27        }
28    }
29}
30
31impl Command for MathMode {
32    fn name(&self) -> &str {
33        "math mode"
34    }
35
36    fn signature(&self) -> Signature {
37        Signature::build("math mode")
38            .input_output_types(vec![
39                (
40                    Type::List(Box::new(Type::Number)),
41                    Type::List(Box::new(Type::Number)),
42                ),
43                (
44                    Type::List(Box::new(Type::Duration)),
45                    Type::List(Box::new(Type::Duration)),
46                ),
47                (
48                    Type::List(Box::new(Type::Filesize)),
49                    Type::List(Box::new(Type::Filesize)),
50                ),
51                (Type::table(), Type::record()),
52            ])
53            .allow_variants_without_examples(true)
54            .category(Category::Math)
55    }
56
57    fn description(&self) -> &str {
58        "Returns the most frequent element(s) from a list of numbers or tables."
59    }
60
61    fn search_terms(&self) -> Vec<&str> {
62        vec!["common", "often"]
63    }
64
65    fn is_const(&self) -> bool {
66        true
67    }
68
69    fn run(
70        &self,
71        _engine_state: &EngineState,
72        _stack: &mut Stack,
73        call: &Call,
74        input: PipelineData,
75    ) -> Result<PipelineData, ShellError> {
76        run_with_function(call, input, mode)
77    }
78
79    fn run_const(
80        &self,
81        _working_set: &StateWorkingSet,
82        call: &Call,
83        input: PipelineData,
84    ) -> Result<PipelineData, ShellError> {
85        run_with_function(call, input, mode)
86    }
87
88    fn examples(&self) -> Vec<Example> {
89        vec![
90            Example {
91                description: "Compute the mode(s) of a list of numbers",
92                example: "[3 3 9 12 12 15] | math mode",
93                result: Some(Value::test_list(vec![
94                    Value::test_int(3),
95                    Value::test_int(12),
96                ])),
97            },
98            Example {
99                description: "Compute the mode(s) of the columns of a table",
100                example: "[{a: 1 b: 3} {a: 2 b: -1} {a: 1 b: 5}] | math mode",
101                result: Some(Value::test_record(record! {
102                        "a" => Value::list(vec![Value::test_int(1)], Span::test_data()),
103                        "b" => Value::list(
104                            vec![Value::test_int(-1), Value::test_int(3), Value::test_int(5)],
105                            Span::test_data(),
106                        ),
107                })),
108            },
109        ]
110    }
111}
112
113pub fn mode(values: &[Value], span: Span, head: Span) -> Result<Value, ShellError> {
114    //In e-q, Value doesn't implement Hash or Eq, so we have to get the values inside
115    // But f64 doesn't implement Hash, so we get the binary representation to use as
116    // key in the HashMap
117    let hashable_values = values
118        .iter()
119        .filter(|x| !x.as_float().is_ok_and(f64::is_nan))
120        .map(|val| match val {
121            Value::Int { val, .. } => Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Int)),
122            Value::Duration { val, .. } => {
123                Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Duration))
124            }
125            Value::Float { val, .. } => {
126                Ok(HashableType::new(val.to_ne_bytes(), NumberTypes::Float))
127            }
128            Value::Filesize { val, .. } => Ok(HashableType::new(
129                val.get().to_ne_bytes(),
130                NumberTypes::Filesize,
131            )),
132            Value::Error { error, .. } => Err(*error.clone()),
133            _ => Err(ShellError::UnsupportedInput {
134                msg: "Unable to give a result with this input".to_string(),
135                input: "value originates from here".into(),
136                msg_span: head,
137                input_span: span,
138            }),
139        })
140        .collect::<Result<Vec<HashableType>, ShellError>>()?;
141
142    let mut frequency_map = HashMap::new();
143    for v in hashable_values {
144        let counter = frequency_map.entry(v).or_insert(0);
145        *counter += 1;
146    }
147
148    let mut max_freq = -1;
149    let mut modes = Vec::<Value>::new();
150    for (value, frequency) in &frequency_map {
151        match max_freq.cmp(frequency) {
152            Ordering::Less => {
153                max_freq = *frequency;
154                modes.clear();
155                modes.push(recreate_value(value, head));
156            }
157            Ordering::Equal => {
158                modes.push(recreate_value(value, head));
159            }
160            Ordering::Greater => (),
161        }
162    }
163
164    modes.sort_by(|a, b| a.partial_cmp(b).unwrap_or(Ordering::Equal));
165    Ok(Value::list(modes, head))
166}
167
168fn recreate_value(hashable_value: &HashableType, head: Span) -> Value {
169    let bytes = hashable_value.bytes;
170    match &hashable_value.original_type {
171        NumberTypes::Int => Value::int(i64::from_ne_bytes(bytes), head),
172        NumberTypes::Float => Value::float(f64::from_ne_bytes(bytes), head),
173        NumberTypes::Duration => Value::duration(i64::from_ne_bytes(bytes), head),
174        NumberTypes::Filesize => Value::filesize(i64::from_ne_bytes(bytes), head),
175    }
176}
177
178#[cfg(test)]
179mod test {
180    use super::*;
181
182    #[test]
183    fn test_examples() {
184        use crate::test_examples;
185
186        test_examples(MathMode {})
187    }
188}