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 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}