Skip to content

Instantly share code, notes, and snippets.

@connesc
Last active December 8, 2022 22:39
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save connesc/d6b87cbacae13d4fd58763724049da58 to your computer and use it in GitHub Desktop.
Save connesc/d6b87cbacae13d4fd58763724049da58 to your computer and use it in GitHub Desktop.
A window function for jq
# Context and benchmarks are available here: https://github.com/stedolan/jq/issues/1577#issuecomment-355756466
def window(values; $size; $step):
def checkparam(name; value): if (value | isnormal) and value > 0 and (value | floor) == value then . else error("window \(name) must be a positive integer") end;
checkparam("size"; $size)
| checkparam("step"; $step)
# We need to detect the end of the loop in order to produce the terminal partial group (if any).
# For that purpose, we introduce an artificial null sentinel, and wrap the input values into singleton arrays in order to distinguish them.
| foreach ((values | [.]), null) as $item (
{index: -1, items: [], ready: false};
(.index + 1) as $index
# Extract items that must be reused from the previous iteration
| if (.ready | not) then .items
elif $step >= $size or $item == null then []
else .items[-($size - $step):]
end
# Append the current item unless it must be skipped
| if ($index % $step) < $size then . + $item
else .
end
| {$index, items: ., ready: (length == $size or ($item == null and length > 0))};
if .ready then .items else empty end
);
def _nwise($n): window(.[]; $n; $n);
include "window"; window(empty; 1; 1)
null
include "window"; window(range(0; 3); 1; 1)
null
[0]
[1]
[2]
include "window"; window(range(0; 3); 2; 1)
null
[0, 1]
[1, 2]
include "window"; window(range(0; 3); 3; 1)
null
[0, 1, 2]
include "window"; window(range(0; 3); 4; 1)
null
[0, 1, 2]
include "window"; window(range(0; 3); 1; 2)
null
[0]
[2]
include "window"; window(range(0; 3); 2; 2)
null
[0, 1]
[2]
include "window"; window(range(0; 3); 1; 3)
null
[0]
include "window"; window(range(0; 3); 1; 4)
null
[0]
include "window"; window(42; 2; 1)
null
[42]
include "window"; window(4, 2; 2; 1)
null
[4, 2]
include "window"; window(range(0; 10); 3; 2)
null
[0, 1, 2]
[2, 3, 4]
[4, 5, 6]
[6, 7, 8]
[8, 9]
include "window"; window(range(0; 10); 3; 3)
null
[0, 1, 2]
[3, 4, 5]
[6, 7, 8]
[9]
include "window"; window(range(0; 10); 3; 4)
null
[0, 1, 2]
[4, 5, 6]
[8, 9]
include "window"; window(range(0; 10); 3; 5)
null
[0, 1, 2]
[5, 6, 7]
include "window"; window(range(0; 10); 3; 7)
null
[0, 1, 2]
[7, 8, 9]
include "window"; (0, -1, 1.5, infinite, nan, true, false, null, "1") | try (window(empty; .; 1), false) catch true
null
true
true
true
true
true
true
true
true
true
include "window"; (0, -1, 1.5, infinite, nan, true, false, null, "1") | try (window(empty; 1; .), false) catch true
null
true
true
true
true
true
true
true
true
true
def window(values; $size; $step):
def checkparam(name; value): if (value | isnormal) and value > 0 and (value | floor) == value then . else error("window \(name) must be a positive integer") end;
checkparam("size"; $size)
| checkparam("step"; $step)
# Here we are using the finalize form of foreach, which is proposed in #1577.
# This does not compile, see "window_finalize_converted" for a working equivalent.
| foreach values as $item (
{index: -1, items: [], ready: false};
(.index + 1) as $index
# Extract items that must be reused from the previous iteration
| if (.ready | not) then .items
elif $step >= $size then []
else .items[-($size - $step):]
end
# Append the current item unless it must be skipped
| if ($index % $step) < $size then . + [$item]
else .
end
| {$index, items: ., ready: (length == $size)};
if .ready then .items else empty end;
if .ready or (.items | length) == 0 then empty else .items end
);
def window(values; $size; $step):
def checkparam(name; value): if (value | isnormal) and value > 0 and (value | floor) == value then . else error("window \(name) must be a positive integer") end;
checkparam("size"; $size)
| checkparam("step"; $step)
# This is a working equivalent of "window_finalize", which illustrates how the finalize form of foreach can be simulated.
| foreach ((values | [.]), null) as $boxed (
{index: -1, items: [], ready: false};
if $boxed == null then .
else $boxed[0] as $item
| (.index + 1) as $index
# Extract items that must be reused from the previous iteration
| if (.ready | not) then .items
elif $step >= $size then []
else .items[-($size - $step):]
end
# Append the current item unless it must be skipped
| if ($index % $step) < $size then . + [$item]
else .
end
| {$index, items: ., ready: (length == $size)}
end;
if $boxed == null then
if .ready or (.items | length) == 0 then empty
else .items
end
else $boxed[0] as $item
| if .ready then .items else empty end
end
);
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment