From 11965aaa2e9cf2b09c431e682cdc459b99de60fb Mon Sep 17 00:00:00 2001 From: Stephen Dolan Date: Sun, 2 Dec 2012 23:22:15 +0000 Subject: [PATCH] sort_by and group_by functions, tests and docs. --- builtin.c | 24 ++++++++++++ docs/content/3.manual/manual.yml | 64 ++++++++++++++++++++++++++++++++ testdata | 8 ++++ 3 files changed, 96 insertions(+) diff --git a/builtin.c b/builtin.c index ab13a2b..43cfe26 100644 --- a/builtin.c +++ b/builtin.c @@ -221,6 +221,26 @@ static jv f_sort(jv input){ } } +static jv f_sort_by_impl(jv input, jv keys) { + if (jv_get_kind(input) == JV_KIND_ARRAY && + jv_get_kind(keys) == JV_KIND_ARRAY && + jv_array_length(jv_copy(input)) == jv_array_length(jv_copy(keys))) { + return jv_sort(input, keys); + } else { + return type_error2(input, keys, "cannot be sorted, as they are not both arrays"); + } +} + +static jv f_group_by_impl(jv input, jv keys) { + if (jv_get_kind(input) == JV_KIND_ARRAY && + jv_get_kind(keys) == JV_KIND_ARRAY && + jv_array_length(jv_copy(input)) == jv_array_length(jv_copy(keys))) { + return jv_group(input, keys); + } else { + return type_error2(input, keys, "cannot be sorted, as they are not both arrays"); + } +} + static jv f_type(jv input) { jv out = jv_string(jv_kind_name(jv_get_kind(input))); jv_free(input); @@ -246,6 +266,8 @@ static struct cfunction function_list[] = { {(cfunction_ptr)f_type, "type", 1}, {(cfunction_ptr)f_add, "add", 1}, {(cfunction_ptr)f_sort, "sort", 1}, + {(cfunction_ptr)f_sort_by_impl, "_sort_by_impl", 2}, + {(cfunction_ptr)f_group_by_impl, "_group_by_impl", 2}, }; static struct symbol_table cbuiltins = {function_list, sizeof(function_list)/sizeof(function_list[0])}; @@ -271,6 +293,8 @@ static block bind_bytecoded_builtins(block b) { static const char* jq_builtins[] = { "def map(f): [.[] | f];", "def select(f): if f then . else empty end;", + "def sort_by(f): _sort_by_impl(map([f]));", + "def group_by(f): _group_by_impl(map([f]));", }; diff --git a/docs/content/3.manual/manual.yml b/docs/content/3.manual/manual.yml index 60679ba..4d22869 100644 --- a/docs/content/3.manual/manual.yml +++ b/docs/content/3.manual/manual.yml @@ -487,6 +487,55 @@ sections: input: '[1, "1", [1]]' output: ['"1"', '"1"', '"[1]"'] + - title: `sort, sort_by` + body: | + + The `sort` functions sorts its input, which must be an + array. Values are sorted in the following order: + + * `null` + * `false` + * `true` + * numbers + * strings, in alphabetical order (by unicode codepoint value) + * arrays, in lexical order + * objects + + The ordering for objects is a little complex: first they're + compared by comparing their sets of keys (as arrays in + sorted order), and if their keys are equal then the values + are compared key by key. + + `sort_by` may be used to sort by a particular field of an + object, or by applying any jq filter. `sort_by(foo)` + compares two elements by comparing the result of `foo` on + each element. + + examples: + - program: 'sort' + input: '[8,3,null,6]' + output: ['[null,3,6,8]'] + - program: 'sort_by(.foo)' + input: '[{"foo":4, "bar":10}, {"foo":3, "bar":100}, {"foo":2, "bar":1}]' + output: ['[{"foo":2, "bar":1}, {"foo":3, "bar":100}, {"foo":4, "bar":10}]'] + + - title: `group_by` + body: | + + `group_by(.foo)` takes as input an array, groups the + elements having the same `.foo` field into separate arrays, + and produces all of these arrays as elements of a larger + array, sorted by the value of the `.foo` field. + + Any jq expression, not just a field access, may be used in + place of `.foo`. The sorting order is the same as described + in the `sort` function above. + + examples: + - program: 'group_by(.foo)' + 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: `contains` body: | @@ -583,6 +632,21 @@ sections: end input: 2 output: ['"many"'] + + - title: `>, >=, <=, <` + body: | + + The comparison operators `>`, `>=`, `<=`, `<` return whether + their left argument is greater than, greater than or equal + to, less than or equal to or less than their right argument + (respectively). + + The ordering is the same as that described for `sort`, above. + + examples: + - program: '. < 5' + input: 2 + output: ['true'] - title: and/or/not body: | diff --git a/testdata b/testdata index 30df927..29bbe7a 100644 --- a/testdata +++ b/testdata @@ -433,3 +433,11 @@ false sort [42,[2,5,3,11],10,{"a":42,"b":2},{"a":42},true,2,[2,6],"hello",null,[2,5,6],{"a":[],"b":1},"abc","ab",[3,10],{},false,"abcd",null] [null,null,false,true,2,10,42,"ab","abc","abcd","hello",[2,5,3,11],[2,5,6],[2,6],[3,10],{},{"a":42},{"a":42,"b":2},{"a":[],"b":1}] + +(sort_by(.b) | sort_by(.a)), sort_by(.a, .b), sort_by(.b, .c), group_by(.b), group_by(.a + .b - .c == 2) +[{"a": 1, "b": 4, "c": 14}, {"a": 4, "b": 1, "c": 3}, {"a": 1, "b": 4, "c": 3}, {"a": 0, "b": 2, "c": 43}] +[{"a": 0, "b": 2, "c": 43}, {"a": 1, "b": 4, "c": 14}, {"a": 1, "b": 4, "c": 3}, {"a": 4, "b": 1, "c": 3}] +[{"a": 0, "b": 2, "c": 43}, {"a": 1, "b": 4, "c": 14}, {"a": 1, "b": 4, "c": 3}, {"a": 4, "b": 1, "c": 3}] +[{"a": 4, "b": 1, "c": 3}, {"a": 0, "b": 2, "c": 43}, {"a": 1, "b": 4, "c": 3}, {"a": 1, "b": 4, "c": 14}] +[[{"a": 4, "b": 1, "c": 3}], [{"a": 0, "b": 2, "c": 43}], [{"a": 1, "b": 4, "c": 14}, {"a": 1, "b": 4, "c": 3}]] +[[{"a": 1, "b": 4, "c": 14}, {"a": 0, "b": 2, "c": 43}], [{"a": 4, "b": 1, "c": 3}, {"a": 1, "b": 4, "c": 3}]] -- 2.40.0