nu_command/filters/
difference.rs1use super::utils;
2use nu_engine::command_prelude::*;
3use std::collections::HashSet;
4
5#[derive(Clone)]
6pub struct Difference;
7
8impl Command for Difference {
9 fn name(&self) -> &str {
10 "difference"
11 }
12
13 fn signature(&self) -> Signature {
14 Signature::build("difference")
15 .input_output_types(vec![
16 (
17 Type::List(Box::new(Type::Any)),
18 Type::List(Box::new(Type::Any)),
19 ),
20 (Type::table(), Type::table()),
21 ])
22 .required(
23 "other",
24 SyntaxShape::List(Box::new(SyntaxShape::Any)),
25 "The other list to subtract from the input.",
26 )
27 .category(Category::Filters)
28 }
29
30 fn description(&self) -> &str {
31 "Returns a list of unique elements in the input that are not present in the other list."
32 }
33
34 fn search_terms(&self) -> Vec<&str> {
35 vec!["subtract", "minus", "exclude", "remove"]
36 }
37
38 fn run(
39 &self,
40 engine_state: &EngineState,
41 stack: &mut Stack,
42 call: &Call,
43 mut input: PipelineData,
44 ) -> Result<PipelineData, ShellError> {
45 let head = call.head;
46 let metadata = input.take_metadata();
47
48 let other_vals = utils::extract_other_list(engine_state, stack, call, head)?;
49
50 let other_set: HashSet<String> = other_vals
51 .iter()
52 .map(|v| utils::value_to_key(engine_state, v, head))
53 .collect::<Result<HashSet<_>, _>>()?;
54
55 let signals = engine_state.signals().clone();
56 let mut seen: HashSet<String> = HashSet::new();
57 let mut result = Vec::new();
58
59 for val in input {
60 signals.check(&head)?;
61 let key = utils::value_to_key(engine_state, &val, head)?;
62 if !other_set.contains(&key) && seen.insert(key) {
63 result.push(val);
64 }
65 }
66
67 Ok(PipelineData::Value(Value::list(result, head), metadata))
68 }
69
70 fn examples(&self) -> Vec<Example<'static>> {
71 vec![
72 Example {
73 example: "[1 2 3 4] | difference [3 4 5 6]",
74 description: "Return the difference of two lists",
75 result: Some(Value::test_list(vec![
76 Value::test_int(1),
77 Value::test_int(2),
78 ])),
79 },
80 Example {
81 example: "[1 1 2 3] | difference [2 3]",
82 description: "Difference with duplicates in input",
83 result: Some(Value::test_list(vec![Value::test_int(1)])),
84 },
85 Example {
86 example: "[{a:1} {a:2} {a:3}] | difference [{a:2} {a:4}]",
87 description: "Difference of two tables",
88 result: Some(Value::test_list(vec![
89 Value::test_record(record!("a" => Value::test_int(1))),
90 Value::test_record(record!("a" => Value::test_int(3))),
91 ])),
92 },
93 ]
94 }
95}
96
97#[cfg(test)]
98mod test {
99 use super::Difference;
100 use nu_protocol::record;
101 use nu_test_support::prelude::*;
102
103 #[test]
104 fn test_examples() -> nu_test_support::Result {
105 nu_test_support::test().examples(Difference)
106 }
107
108 #[test]
109 fn difference_basic() -> Result {
110 test()
111 .run("[1 2 3 4] | difference [3 4 5 6]")
112 .expect_value_eq([1, 2])
113 }
114
115 #[test]
116 fn difference_no_common() -> Result {
117 test()
118 .run("[1 2 3] | difference [4 5 6]")
119 .expect_value_eq([1, 2, 3])
120 }
121
122 #[test]
123 fn difference_all_common() -> Result {
124 test()
125 .run("[1 2 3] | difference [1 2 3]")
126 .expect_value_eq(Value::test_list(vec![]))
127 }
128
129 #[test]
130 fn difference_empty_input() -> Result {
131 test()
132 .run("[] | difference [1 2 3]")
133 .expect_value_eq(Value::test_list(vec![]))
134 }
135
136 #[test]
137 fn difference_empty_other() -> Result {
138 test()
139 .run("[1 2 3] | difference []")
140 .expect_value_eq([1, 2, 3])
141 }
142
143 #[test]
144 fn difference_dedups_output() -> Result {
145 test()
146 .run("[1 1 2 3] | difference [2]")
147 .expect_value_eq([1, 3])
148 }
149
150 #[test]
151 fn difference_preserves_input_order() -> Result {
152 test()
153 .run("[c a b d] | difference [a d] | str join '-'")
154 .expect_value_eq("c-b")
155 }
156
157 #[test]
158 fn difference_tables() -> Result {
159 let result: Value = test().run("[{a:1} {a:2} {a:3}] | difference [{a:2} {a:4}]")?;
160 assert_eq!(
161 result,
162 Value::test_list(vec![
163 Value::test_record(record!("a" => Value::test_int(1))),
164 Value::test_record(record!("a" => Value::test_int(3))),
165 ])
166 );
167 Ok(())
168 }
169
170 #[test]
171 fn difference_mixed_types() -> Result {
172 test()
173 .run("[1 a 2.5 true] | difference [2.5 b]")
174 .expect_value_eq((1, "a", true))
175 }
176
177 #[test]
178 fn difference_other_not_a_list() {
179 let result: nu_test_support::Result = test().run("[1 2] | difference 42");
180 assert!(result.is_err());
181 }
182}