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