nu_command/filters/
uniq_by.rs1pub use super::uniq;
2use nu_engine::command_prelude::*;
3use nu_protocol::{ast::PathMember, casing::Casing};
4
5#[derive(Clone)]
6pub struct UniqBy;
7
8impl Command for UniqBy {
9 fn name(&self) -> &str {
10 "uniq-by"
11 }
12
13 fn signature(&self) -> Signature {
14 Signature::build("uniq-by")
15 .input_output_types(vec![
16 (Type::table(), Type::table()),
17 (
18 Type::List(Box::new(Type::Any)),
19 Type::List(Box::new(Type::Any)),
20 ),
21 ])
22 .rest("columns", SyntaxShape::Any, "The column(s) to filter by.")
23 .switch(
24 "count",
25 "Return a table containing the distinct input values together with their counts.",
26 Some('c'),
27 )
28 .switch(
29 "keep-last",
30 "Return the last occurrence of each unique value instead of the first.",
31 Some('l'),
32 )
33 .switch(
34 "repeated",
35 "Return the input values that occur more than once.",
36 Some('d'),
37 )
38 .switch(
39 "ignore-case",
40 "Ignore differences in case when comparing input values.",
41 Some('i'),
42 )
43 .switch(
44 "unique",
45 "Return the input values that occur once only.",
46 Some('u'),
47 )
48 .allow_variants_without_examples(true)
49 .category(Category::Filters)
50 }
51
52 fn description(&self) -> &str {
53 "Return the distinct values in the input by the given column(s)."
54 }
55
56 fn search_terms(&self) -> Vec<&str> {
57 vec!["distinct", "deduplicate"]
58 }
59
60 fn run(
61 &self,
62 engine_state: &EngineState,
63 stack: &mut Stack,
64 call: &Call,
65 mut input: PipelineData,
66 ) -> Result<PipelineData, ShellError> {
67 let columns: Vec<String> = call.rest(engine_state, stack, 0)?;
68
69 if columns.is_empty() {
70 return Err(ShellError::MissingParameter {
71 param_name: "columns".into(),
72 span: call.head,
73 });
74 }
75
76 let metadata = input.take_metadata();
77
78 let vec: Vec<_> = input.into_iter().collect();
79 match validate(&vec, &columns, call.head) {
80 Ok(_) => {}
81 Err(err) => {
82 return Err(err);
83 }
84 }
85
86 let mapper = Box::new(item_mapper_by_col(columns));
87
88 uniq(engine_state, stack, call, vec, mapper, metadata)
89 }
90
91 fn examples(&self) -> Vec<Example<'_>> {
92 vec![
93 Example {
94 description: "Get rows from table filtered by column uniqueness.",
95 example: "[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 7]] | uniq-by fruit",
96 result: Some(Value::test_list(vec![
97 Value::test_record(record! {
98 "fruit" => Value::test_string("apple"),
99 "count" => Value::test_int(9),
100 }),
101 Value::test_record(record! {
102 "fruit" => Value::test_string("pear"),
103 "count" => Value::test_int(3),
104 }),
105 Value::test_record(record! {
106 "fruit" => Value::test_string("orange"),
107 "count" => Value::test_int(7),
108 }),
109 ])),
110 },
111 Example {
112 description: "Get rows from table filtered by column uniqueness, keeping the last occurrence of each duplicate.",
113 example: "[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 7]] | uniq-by fruit --keep-last",
114 result: Some(Value::test_list(vec![
115 Value::test_record(record! {
116 "fruit" => Value::test_string("apple"),
117 "count" => Value::test_int(2),
118 }),
119 Value::test_record(record! {
120 "fruit" => Value::test_string("pear"),
121 "count" => Value::test_int(3),
122 }),
123 Value::test_record(record! {
124 "fruit" => Value::test_string("orange"),
125 "count" => Value::test_int(7),
126 }),
127 ])),
128 },
129 ]
130 }
131}
132
133fn validate(vec: &[Value], columns: &[String], span: Span) -> Result<(), ShellError> {
134 for v in vec {
139 for col in columns {
140 let member = PathMember::string(col.clone(), false, Casing::Sensitive, span);
141 v.follow_cell_path(std::slice::from_ref(&member))?;
142 }
143 }
144
145 Ok(())
146}
147
148fn get_data_by_columns(columns: &[String], item: &Value) -> Vec<Value> {
149 columns
150 .iter()
151 .filter_map(|col| item.get_data_by_key(col))
152 .collect::<Vec<_>>()
153}
154
155fn item_mapper_by_col(cols: Vec<String>) -> impl Fn(crate::ItemMapperState) -> crate::ValueCounter {
156 let columns = cols;
157
158 Box::new(move |ms: crate::ItemMapperState| -> crate::ValueCounter {
159 let item_column_values = get_data_by_columns(&columns, &ms.item);
160
161 let col_vals = Value::list(item_column_values, ms.head);
162
163 crate::ValueCounter::new_vals_to_compare(
164 ms.item,
165 ms.flag_ignore_case,
166 col_vals,
167 ms.index,
168 ms.head,
169 )
170 })
171}
172
173#[cfg(test)]
174mod test {
175 use super::*;
176
177 #[test]
178 fn test_examples() -> nu_test_support::Result {
179 nu_test_support::test().examples(UniqBy)
180 }
181}