Skip to main content

nu_command/math/
mode.rs

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