Skip to main content

nu_command/filters/
combinations.rs

1use nu_engine::command_prelude::*;
2use nu_protocol::{ListStream, Signals};
3
4#[derive(Clone)]
5pub struct Combinations;
6
7impl Command for Combinations {
8    fn name(&self) -> &str {
9        "combinations"
10    }
11
12    fn signature(&self) -> Signature {
13        Signature::build("combinations")
14            .input_output_types(vec![
15                (
16                    Type::List(Box::new(Type::Any)),
17                    Type::List(Box::new(Type::List(Box::new(Type::Any)))),
18                ),
19                (Type::table(), Type::table()),
20            ])
21            .required("k", SyntaxShape::Int, "The size of each combination (k).")
22            .category(Category::Filters)
23    }
24
25    fn description(&self) -> &str {
26        "Generates all combinations of size k from the input list."
27    }
28
29    fn search_terms(&self) -> Vec<&str> {
30        vec!["choose", "subset", "combinatorics"]
31    }
32
33    fn run(
34        &self,
35        engine_state: &EngineState,
36        stack: &mut Stack,
37        call: &Call,
38        mut input: PipelineData,
39    ) -> Result<PipelineData, ShellError> {
40        let head = call.head;
41        let k: usize = call.req(engine_state, stack, 0)?;
42        let metadata = input.take_metadata();
43
44        let vals: Vec<Value> = input.into_iter().collect();
45        let signals = engine_state.signals().clone();
46
47        if k == 0 {
48            // C(n, 0) = 1: the empty combination.
49            return Ok(PipelineData::Value(
50                Value::list(vec![Value::list(vec![], head)], head),
51                metadata,
52            ));
53        }
54
55        let iter = CombinationsIter::new(vals, k, signals.clone(), head);
56        let stream = ListStream::new(iter, head, signals);
57        Ok(PipelineData::ListStream(stream, metadata))
58    }
59
60    fn examples(&self) -> Vec<Example<'static>> {
61        vec![
62            Example {
63                example: "[1 2 3] | combinations 2",
64                description: "Generate all 2-combinations",
65                result: Some(Value::test_list(vec![
66                    Value::test_list(vec![Value::test_int(1), Value::test_int(2)]),
67                    Value::test_list(vec![Value::test_int(1), Value::test_int(3)]),
68                    Value::test_list(vec![Value::test_int(2), Value::test_int(3)]),
69                ])),
70            },
71            Example {
72                example: "[[a] [b] [c]] | combinations 2",
73                description: "Generate combinations of lists",
74                result: Some(Value::test_list(vec![
75                    Value::test_list(vec![
76                        Value::test_list(vec![Value::test_string("a")]),
77                        Value::test_list(vec![Value::test_string("b")]),
78                    ]),
79                    Value::test_list(vec![
80                        Value::test_list(vec![Value::test_string("a")]),
81                        Value::test_list(vec![Value::test_string("c")]),
82                    ]),
83                    Value::test_list(vec![
84                        Value::test_list(vec![Value::test_string("b")]),
85                        Value::test_list(vec![Value::test_string("c")]),
86                    ]),
87                ])),
88            },
89            Example {
90                example: "[1 2] | combinations 3",
91                description: "k > n yields an empty list",
92                result: Some(Value::test_list(vec![])),
93            },
94        ]
95    }
96}
97
98/// A streaming iterator that yields all k-combinations from a list.
99///
100/// Combinations are produced in lexicographic order of their element indices.
101/// The algorithm advances a vector of indices from the rightmost position,
102/// incrementing the first index that can be increased without exceeding
103/// `n - (k - i)`. This is the classic combinatorial number system.
104struct CombinationsIter {
105    n: usize,
106    k: usize,
107    indices: Vec<usize>,
108    values: Vec<Value>,
109    done: bool,
110    signals: Signals,
111    span: Span,
112}
113
114impl CombinationsIter {
115    fn new(values: Vec<Value>, k: usize, signals: Signals, span: Span) -> Self {
116        let n = values.len();
117        let done = k == 0 || k > n;
118        let indices: Vec<usize> = if done { vec![] } else { (0..k).collect() };
119        Self {
120            n,
121            k,
122            indices,
123            values,
124            done,
125            signals,
126            span,
127        }
128    }
129}
130
131impl Iterator for CombinationsIter {
132    type Item = Value;
133
134    fn next(&mut self) -> Option<Self::Item> {
135        if self.done {
136            return None;
137        }
138
139        if self.signals.interrupted() {
140            return None;
141        }
142
143        let combination: Vec<Value> = self
144            .indices
145            .iter()
146            .map(|&i| self.values[i].clone())
147            .collect();
148        let result = Some(Value::list(combination, self.span));
149
150        // Advance to the next combination in lexicographic order.
151        let mut i = self.k.wrapping_sub(1);
152        while i < self.k && self.indices[i] == self.n - self.k + i {
153            if i == 0 {
154                self.done = true;
155                return result;
156            }
157            i -= 1;
158        }
159
160        self.indices[i] += 1;
161        for j in (i + 1)..self.k {
162            self.indices[j] = self.indices[j - 1] + 1;
163        }
164
165        result
166    }
167}
168
169#[cfg(test)]
170mod test {
171    use super::Combinations;
172    use nu_test_support::prelude::*;
173
174    #[test]
175    fn test_examples() -> nu_test_support::Result {
176        nu_test_support::test().examples(Combinations)
177    }
178
179    #[test]
180    fn combinations_k2() -> Result {
181        let result: Value = test().run("[1 2 3] | combinations 2")?;
182        assert_eq!(
183            result,
184            Value::test_list(vec![
185                Value::test_list(vec![Value::test_int(1), Value::test_int(2)]),
186                Value::test_list(vec![Value::test_int(1), Value::test_int(3)]),
187                Value::test_list(vec![Value::test_int(2), Value::test_int(3)]),
188            ])
189        );
190        Ok(())
191    }
192
193    #[test]
194    fn combinations_k0() -> Result {
195        let result: Value = test().run("[1 2 3] | combinations 0")?;
196        assert_eq!(result, Value::test_list(vec![Value::test_list(vec![])]));
197        Ok(())
198    }
199
200    #[test]
201    fn combinations_k1() -> Result {
202        let result: Value = test().run("[1 2 3] | combinations 1")?;
203        assert_eq!(
204            result,
205            Value::test_list(vec![
206                Value::test_list(vec![Value::test_int(1)]),
207                Value::test_list(vec![Value::test_int(2)]),
208                Value::test_list(vec![Value::test_int(3)]),
209            ])
210        );
211        Ok(())
212    }
213
214    #[test]
215    fn combinations_k_equals_n() -> Result {
216        let result: Value = test().run("[1 2 3] | combinations 3")?;
217        assert_eq!(
218            result,
219            Value::test_list(vec![Value::test_list(vec![
220                Value::test_int(1),
221                Value::test_int(2),
222                Value::test_int(3),
223            ])])
224        );
225        Ok(())
226    }
227
228    #[test]
229    fn combinations_k_greater_than_n() -> Result {
230        test()
231            .run("[1 2] | combinations 3")
232            .expect_value_eq(Value::test_list(vec![]))
233    }
234
235    #[test]
236    fn combinations_empty_list() -> Result {
237        test()
238            .run("[] | combinations 2")
239            .expect_value_eq(Value::test_list(vec![]))
240    }
241
242    #[test]
243    fn combinations_strings() -> Result {
244        let result: Value = test().run("[a b c] | combinations 2")?;
245        assert_eq!(
246            result,
247            Value::test_list(vec![
248                Value::test_list(vec![Value::test_string("a"), Value::test_string("b")]),
249                Value::test_list(vec![Value::test_string("a"), Value::test_string("c")]),
250                Value::test_list(vec![Value::test_string("b"), Value::test_string("c")]),
251            ])
252        );
253        Ok(())
254    }
255
256    #[test]
257    fn combinations_k0_empty_input() -> Result {
258        // C(0,0) = 1: the empty combination is still valid.
259        let result: Value = test().run("[] | combinations 0")?;
260        assert_eq!(result, Value::test_list(vec![Value::test_list(vec![])]));
261        Ok(())
262    }
263}