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 columns = columns
79 .into_iter()
80 .map(|col| PathMember::string(col, false, Casing::Sensitive, call.head))
81 .collect();
82 let mapper = Box::new(item_mapper_by_col(columns));
83
84 let vec: Vec<_> = input.into_iter().collect();
85 uniq(engine_state, stack, call, vec, mapper, metadata)
86 }
87
88 fn examples(&self) -> Vec<Example<'_>> {
89 vec![
90 Example {
91 description: "Get rows from table filtered by column uniqueness.",
92 example: "[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 7]] | uniq-by fruit",
93 result: Some(Value::test_list(vec![
94 Value::test_record(record! {
95 "fruit" => Value::test_string("apple"),
96 "count" => Value::test_int(9),
97 }),
98 Value::test_record(record! {
99 "fruit" => Value::test_string("pear"),
100 "count" => Value::test_int(3),
101 }),
102 Value::test_record(record! {
103 "fruit" => Value::test_string("orange"),
104 "count" => Value::test_int(7),
105 }),
106 ])),
107 },
108 Example {
109 description: "Get rows from table filtered by column uniqueness, keeping the last occurrence of each duplicate.",
110 example: "[[fruit count]; [apple 9] [apple 2] [pear 3] [orange 7]] | uniq-by fruit --keep-last",
111 result: Some(Value::test_list(vec![
112 Value::test_record(record! {
113 "fruit" => Value::test_string("apple"),
114 "count" => Value::test_int(2),
115 }),
116 Value::test_record(record! {
117 "fruit" => Value::test_string("pear"),
118 "count" => Value::test_int(3),
119 }),
120 Value::test_record(record! {
121 "fruit" => Value::test_string("orange"),
122 "count" => Value::test_int(7),
123 }),
124 ])),
125 },
126 ]
127 }
128}
129
130fn item_mapper_by_col(
131 columns: Vec<PathMember>,
132) -> impl Fn(crate::ItemMapperState) -> Result<crate::ValueCounter, ShellError> {
133 move |ms: crate::ItemMapperState| -> Result<crate::ValueCounter, ShellError> {
134 let item_column_values = columns
137 .iter()
138 .map(|column| {
139 ms.item
140 .follow_cell_path(std::slice::from_ref(column))
141 .map(|value| value.into_owned())
142 })
143 .collect::<Result<Vec<_>, _>>()?;
144
145 let col_vals = Value::list(item_column_values, ms.head);
146
147 Ok(crate::ValueCounter::new_vals_to_compare(
148 ms.item,
149 ms.flag_ignore_case,
150 col_vals,
151 ms.index,
152 ms.head,
153 ))
154 }
155}
156
157#[cfg(test)]
158mod test {
159 use super::*;
160
161 #[test]
162 fn test_examples() -> nu_test_support::Result {
163 nu_test_support::test().examples(UniqBy)
164 }
165}