diff --git a/docs/content/manual/manual.yml b/docs/content/manual/manual.yml index 183e4a5d45..ae02b31221 100644 --- a/docs/content/manual/manual.yml +++ b/docs/content/manual/manual.yml @@ -1426,10 +1426,15 @@ sections: input: '[{"foo":1, "bar":10}, {"foo":3, "bar":100}, {"foo":1, "bar":1}]' output: ['[[{"foo":1, "bar":10}, {"foo":1, "bar":1}], [{"foo":3, "bar":100}]]'] - - title: "`min`, `max`, `min_by(path_exp)`, `max_by(path_exp)`" + - title: "`min`, `max`, `min(stream)`, `max(stream)`, `min_by(path_exp)`, `max_by(path_exp)`" body: | - Find the minimum or maximum element of the input array. + `min` finds the minimum element of the input array, and + `min(stream)` finds the minimum item in the stream. + + `max` and `max(stream)` similarly find the maximum element. + + `min(empty)` and `max(empty)` both emit nothing. The `min_by(path_exp)` and `max_by(path_exp)` functions allow you to specify a particular field or property to examine, e.g. @@ -1439,10 +1444,15 @@ sections: - program: 'min' input: '[5,4,2,7]' output: ['2'] + + - program: 'min(1,2,3,0.1)' + input: null + output: ['0.1'] + - program: 'max_by(.foo)' input: '[{"foo":1, "bar":14}, {"foo":2, "bar":3}]' output: ['{"foo":2, "bar":3}'] - + - title: "`unique`, `unique_by(path_exp)`" body: | diff --git a/src/builtin.jq b/src/builtin.jq index 146a64a36b..9dc732ed86 100644 --- a/src/builtin.jq +++ b/src/builtin.jq @@ -1,3 +1,4 @@ +def first(g): label $out | g | ., break $out; def halt_error: halt_error(5); def error(msg): msg|error; def map(f): [.[] | f]; @@ -6,6 +7,19 @@ def sort_by(f): _sort_by_impl(map([f])); def group_by(f): _group_by_impl(map([f])); def unique: group_by(.) | map(.[0]); def unique_by(f): group_by(f) | map(.[0]); +# max(s) and min(s) use boxing technique for the sake of `input`: +def max(s): + reduce (s|[.]) as $x (null; + if . == null then $x + else if $x > . then $x end # for speed + end ) + | select(.)[0]; +def min(s): + reduce (s|[.]) as $x (null; + if . == null then $x + else if $x < . then $x end # for speed + end ) + | select(.)[0]; def max_by(f): _max_by_impl(map([f])); def min_by(f): _min_by_impl(map([f])); def add: reduce .[] as $x (null; . + $x); @@ -154,7 +168,6 @@ def range($init; $upto; $by): if $by > 0 then $init|while(. < $upto; . + $by) elif $by < 0 then $init|while(. > $upto; . + $by) else empty end; -def first(g): label $out | g | ., break $out; def isempty(g): first((g|false), true); def all(generator; condition): isempty(generator|condition and empty); def any(generator; condition): isempty(generator|condition or empty)|not; @@ -181,14 +194,8 @@ def combinations(n): | combinations; # transpose a possibly jagged matrix, quickly; # rows are padded with nulls so the result is always rectangular. -def transpose: - if . == [] then [] - else . as $in - | (map(length) | max) as $max - | length as $length - | reduce range(0; $max) as $j - ([]; . + [reduce range(0;$length) as $i ([]; . + [ $in[$i][$j] ] )] ) - end; +# Using map(length) turns out to be faster than using max/1 +def transpose: [range(0; map(length)|max) as $i | [.[][$i]]]; def in(xs): . as $x | xs | has($x); def inside(xs): . as $x | xs | contains($x); def repeat(exp): diff --git a/tests/jq.test b/tests/jq.test index 4e693452bd..fb03d2f383 100644 --- a/tests/jq.test +++ b/tests/jq.test @@ -1352,6 +1352,15 @@ unique [] [null,null,null,null] +[min(1,2,0.1), max(1,2,0.1)] +null +[0.1,2] + + +[min(empty),max(empty)] +null +[] + .foo[.baz] {"foo":{"bar":4},"baz":"bar"} 4