nu_command/filters/
union.rs1use super::utils;
2use nu_engine::command_prelude::*;
3use std::collections::HashSet;
4
5#[derive(Clone)]
6pub struct Union;
7
8impl Command for Union {
9 fn name(&self) -> &str {
10 "union"
11 }
12
13 fn signature(&self) -> Signature {
14 Signature::build("union")
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 union with.",
26 )
27 .category(Category::Filters)
28 }
29
30 fn description(&self) -> &str {
31 "Returns a list of unique elements from both the input and the provided list."
32 }
33
34 fn search_terms(&self) -> Vec<&str> {
35 vec!["merge", "deduplicate", "combine"]
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 input_vals: Vec<Value> = input.into_iter().collect();
49 let other_vals = utils::extract_other_list(engine_state, stack, call, head)?;
50
51 let signals = engine_state.signals().clone();
52 let total_capacity = input_vals.len() + other_vals.len();
53 let mut seen: HashSet<String> = HashSet::with_capacity(total_capacity);
54 let mut result = Vec::with_capacity(total_capacity);
55
56 for val in input_vals {
57 signals.check(&head)?;
58 if seen.insert(utils::value_to_key(engine_state, &val, head)?) {
59 result.push(val);
60 }
61 }
62
63 for val in other_vals {
64 signals.check(&head)?;
65 if seen.insert(utils::value_to_key(engine_state, &val, head)?) {
66 result.push(val);
67 }
68 }
69
70 Ok(PipelineData::Value(Value::list(result, head), metadata))
71 }
72
73 fn examples(&self) -> Vec<Example<'static>> {
74 vec![
75 Example {
76 example: "[1 2 3 4] | union [3 4 5 6]",
77 description: "Return the union of two lists",
78 result: Some(Value::test_list(vec![
79 Value::test_int(1),
80 Value::test_int(2),
81 Value::test_int(3),
82 Value::test_int(4),
83 Value::test_int(5),
84 Value::test_int(6),
85 ])),
86 },
87 Example {
88 example: "[1 1 2 3] | union [2 3 4]",
89 description: "Union with duplicates in input",
90 result: Some(Value::test_list(vec![
91 Value::test_int(1),
92 Value::test_int(2),
93 Value::test_int(3),
94 Value::test_int(4),
95 ])),
96 },
97 Example {
98 example: "[{a:1} {a:2}] | union [{a:2} {a:3}]",
99 description: "Union of two tables (dedup rows)",
100 result: Some(Value::test_list(vec![
101 Value::test_record(record!("a" => Value::test_int(1))),
102 Value::test_record(record!("a" => Value::test_int(2))),
103 Value::test_record(record!("a" => Value::test_int(3))),
104 ])),
105 },
106 ]
107 }
108}
109
110#[cfg(test)]
111mod test {
112 use super::Union;
113 use nu_protocol::record;
114 use nu_test_support::prelude::*;
115
116 #[test]
117 fn test_examples() -> nu_test_support::Result {
118 nu_test_support::test().examples(Union)
119 }
120
121 #[test]
122 fn union_basic() -> Result {
123 test()
124 .run("[1 2 3 4] | union [3 4 5 6]")
125 .expect_value_eq([1, 2, 3, 4, 5, 6])
126 }
127
128 #[test]
129 fn union_dedups_input() -> Result {
130 test()
131 .run("[1 1 2 3] | union [2 3 4]")
132 .expect_value_eq([1, 2, 3, 4])
133 }
134
135 #[test]
136 fn union_dedups_both() -> Result {
137 test()
138 .run("[1 2 2 3] | union [2 3 3 4]")
139 .expect_value_eq([1, 2, 3, 4])
140 }
141
142 #[test]
143 fn union_empty_input() -> Result {
144 test().run("[] | union [1 2 3]").expect_value_eq([1, 2, 3])
145 }
146
147 #[test]
148 fn union_empty_other() -> Result {
149 test().run("[1 2 3] | union []").expect_value_eq([1, 2, 3])
150 }
151
152 #[test]
153 fn union_both_empty() -> Result {
154 test()
155 .run("[] | union []")
156 .expect_value_eq(Value::test_list(vec![]))
157 }
158
159 #[test]
160 fn union_preserves_input_order() -> Result {
161 test()
162 .run("[c a b] | union [d e f] | str join '-'")
163 .expect_value_eq("c-a-b-d-e-f")
164 }
165
166 #[test]
167 fn union_with_other_overlap_first() -> Result {
168 test()
170 .run("[1 2 3] | union [1 2 3 4 5]")
171 .expect_value_eq([1, 2, 3, 4, 5])
172 }
173
174 #[test]
175 fn union_tables() -> Result {
176 let result: Value = test().run("[{a:1} {a:2}] | union [{a:2} {a:3}]")?;
177 assert_eq!(
178 result,
179 Value::test_list(vec![
180 Value::test_record(record!("a" => Value::test_int(1))),
181 Value::test_record(record!("a" => Value::test_int(2))),
182 Value::test_record(record!("a" => Value::test_int(3))),
183 ])
184 );
185 Ok(())
186 }
187
188 #[test]
189 fn union_mixed_types() -> Result {
190 test()
191 .run("[1 a 2.5] | union [2.5 b 3]")
192 .expect_value_eq((1, "a", 2.5f64, "b", 3))
193 }
194}