1use nu_engine::command_prelude::*;
2use nu_protocol::ListStream;
3use std::num::NonZeroUsize;
4
5#[derive(Clone)]
6pub struct Window;
7
8impl Command for Window {
9 fn name(&self) -> &str {
10 "window"
11 }
12
13 fn signature(&self) -> Signature {
14 Signature::build("window")
15 .input_output_types(vec![(
16 Type::list(Type::Any),
17 Type::list(Type::list(Type::Any)),
18 )])
19 .required("window_size", SyntaxShape::Int, "The size of each window.")
20 .named(
21 "stride",
22 SyntaxShape::Int,
23 "The number of rows to slide over between windows.",
24 Some('s'),
25 )
26 .switch(
27 "remainder",
28 "Yield last chunks even if they have fewer elements than size.",
29 Some('r'),
30 )
31 .category(Category::Filters)
32 }
33
34 fn description(&self) -> &str {
35 "Creates a sliding window of `window_size` that slide by n rows/elements across input."
36 }
37
38 fn extra_description(&self) -> &str {
39 "This command will error if `window_size` or `stride` are negative or zero."
40 }
41
42 fn examples(&self) -> Vec<Example<'_>> {
43 vec![
44 Example {
45 example: "[1 2 3 4] | window 2",
46 description: "A sliding window of two elements.",
47 result: Some(Value::test_list(vec![
48 Value::test_list(vec![Value::test_int(1), Value::test_int(2)]),
49 Value::test_list(vec![Value::test_int(2), Value::test_int(3)]),
50 Value::test_list(vec![Value::test_int(3), Value::test_int(4)]),
51 ])),
52 },
53 Example {
54 example: "[1, 2, 3, 4, 5, 6, 7, 8] | window 2 --stride 3",
55 description: "A sliding window of two elements, with a stride of 3.",
56 result: Some(Value::test_list(vec![
57 Value::test_list(vec![Value::test_int(1), Value::test_int(2)]),
58 Value::test_list(vec![Value::test_int(4), Value::test_int(5)]),
59 Value::test_list(vec![Value::test_int(7), Value::test_int(8)]),
60 ])),
61 },
62 Example {
63 example: "[1, 2, 3, 4, 5] | window 3 --stride 3 --remainder",
64 description: "A sliding window of equal stride that includes remainder. Equivalent to chunking.",
65 result: Some(Value::test_list(vec![
66 Value::test_list(vec![
67 Value::test_int(1),
68 Value::test_int(2),
69 Value::test_int(3),
70 ]),
71 Value::test_list(vec![Value::test_int(4), Value::test_int(5)]),
72 ])),
73 },
74 ]
75 }
76
77 fn run(
78 &self,
79 engine_state: &EngineState,
80 stack: &mut Stack,
81 call: &Call,
82 input: PipelineData,
83 ) -> Result<PipelineData, ShellError> {
84 let input = input.into_stream_or_original(engine_state);
85 let head = call.head;
86 let window_size: Value = call.req(engine_state, stack, 0)?;
87 let stride: Option<Value> = call.get_flag(engine_state, stack, "stride")?;
88 let remainder = call.has_flag(engine_state, stack, "remainder")?;
89
90 let size =
91 usize::try_from(window_size.as_int()?).map_err(|_| ShellError::NeedsPositiveValue {
92 span: window_size.span(),
93 })?;
94
95 let size = NonZeroUsize::try_from(size).map_err(|_| ShellError::IncorrectValue {
96 msg: "`window_size` cannot be zero".into(),
97 val_span: window_size.span(),
98 call_span: head,
99 })?;
100
101 let stride = if let Some(stride_val) = stride {
102 let stride = usize::try_from(stride_val.as_int()?).map_err(|_| {
103 ShellError::NeedsPositiveValue {
104 span: stride_val.span(),
105 }
106 })?;
107
108 NonZeroUsize::try_from(stride).map_err(|_| ShellError::IncorrectValue {
109 msg: "`stride` cannot be zero".into(),
110 val_span: stride_val.span(),
111 call_span: head,
112 })?
113 } else {
114 NonZeroUsize::MIN
115 };
116
117 if remainder && size == stride {
118 super::chunks::chunks(engine_state, input, size, head)
119 } else if stride >= size {
120 match input {
121 PipelineData::Value(Value::List { vals, .. }, metadata) => {
122 let chunks = WindowGapIter::new(vals, size, stride, remainder, head);
123 let stream = ListStream::new(chunks, head, engine_state.signals().clone());
124 Ok(PipelineData::list_stream(stream, metadata))
125 }
126 PipelineData::ListStream(stream, metadata) => {
127 let stream = stream
128 .modify(|iter| WindowGapIter::new(iter, size, stride, remainder, head));
129 Ok(PipelineData::list_stream(stream, metadata))
130 }
131 input => Err(input.unsupported_input_error("list", head)),
132 }
133 } else {
134 match input {
135 PipelineData::Value(Value::List { vals, .. }, metadata) => {
136 let chunks = WindowOverlapIter::new(vals, size, stride, remainder, head);
137 let stream = ListStream::new(chunks, head, engine_state.signals().clone());
138 Ok(PipelineData::list_stream(stream, metadata))
139 }
140 PipelineData::ListStream(stream, metadata) => {
141 let stream = stream
142 .modify(|iter| WindowOverlapIter::new(iter, size, stride, remainder, head));
143 Ok(PipelineData::list_stream(stream, metadata))
144 }
145 input => Err(input.unsupported_input_error("list", head)),
146 }
147 }
148 }
149}
150
151struct WindowOverlapIter<I: Iterator<Item = Value>> {
152 iter: I,
153 window: Vec<Value>,
154 stride: usize,
155 remainder: bool,
156 span: Span,
157}
158
159impl<I: Iterator<Item = Value>> WindowOverlapIter<I> {
160 fn new(
161 iter: impl IntoIterator<IntoIter = I>,
162 size: NonZeroUsize,
163 stride: NonZeroUsize,
164 remainder: bool,
165 span: Span,
166 ) -> Self {
167 Self {
168 iter: iter.into_iter(),
169 window: Vec::with_capacity(size.into()),
170 stride: stride.into(),
171 remainder,
172 span,
173 }
174 }
175}
176
177impl<I: Iterator<Item = Value>> Iterator for WindowOverlapIter<I> {
178 type Item = Value;
179
180 fn next(&mut self) -> Option<Self::Item> {
181 let len = if self.window.is_empty() {
182 self.window.capacity()
183 } else {
184 self.stride
185 };
186
187 self.window.extend((&mut self.iter).take(len));
188
189 if self.window.len() == self.window.capacity()
190 || (self.remainder && !self.window.is_empty())
191 {
192 let mut next = Vec::with_capacity(self.window.len());
193 next.extend(self.window.iter().skip(self.stride).cloned());
194 let window = std::mem::replace(&mut self.window, next);
195 Some(Value::list(window, self.span))
196 } else {
197 None
198 }
199 }
200}
201
202struct WindowGapIter<I: Iterator<Item = Value>> {
203 iter: I,
204 size: usize,
205 skip: usize,
206 first: bool,
207 remainder: bool,
208 span: Span,
209}
210
211impl<I: Iterator<Item = Value>> WindowGapIter<I> {
212 fn new(
213 iter: impl IntoIterator<IntoIter = I>,
214 size: NonZeroUsize,
215 stride: NonZeroUsize,
216 remainder: bool,
217 span: Span,
218 ) -> Self {
219 let size = size.into();
220 Self {
221 iter: iter.into_iter(),
222 size,
223 skip: stride.get() - size,
224 first: true,
225 remainder,
226 span,
227 }
228 }
229}
230
231impl<I: Iterator<Item = Value>> Iterator for WindowGapIter<I> {
232 type Item = Value;
233
234 fn next(&mut self) -> Option<Self::Item> {
235 let mut window = Vec::with_capacity(self.size);
236 window.extend(
237 (&mut self.iter)
238 .skip(if self.first { 0 } else { self.skip })
239 .take(self.size),
240 );
241
242 self.first = false;
243
244 if window.len() == self.size || (self.remainder && !window.is_empty()) {
245 Some(Value::list(window, self.span))
246 } else {
247 None
248 }
249 }
250}
251
252#[cfg(test)]
253mod test {
254 use super::*;
255
256 #[test]
257 fn test_examples() -> nu_test_support::Result {
258 nu_test_support::test().examples(Window)
259 }
260}