mirror of
https://github.com/johnkerl/miller.git
synced 2026-07-20 18:10:07 +00:00
415 lines
12 KiB
Markdown
415 lines
12 KiB
Markdown
# Sorting
|
|
|
|
Miller gives you three ways to sort your data:
|
|
|
|
* The [`sort`](reference-verbs.md#sort) verb lets you sort records (rows) by various fields (columns).
|
|
* The [`sort-within-records`](reference-verbs.md#sort-within-records) verb lets you sort fields within records.
|
|
* The [`sort`](reference-dsl-builtin-functions.md#sort) DSL function gives you more customizable options for sorting data either within fields or across records. (See also the [higher-order-functions page](reference-dsl-higher-order-functions.md) for related information.)
|
|
|
|
## Sorting records: the sort verb
|
|
|
|
The `sort` verb (see [its documentation](reference-verbs.md#sort) for more
|
|
information) reorders entire records within the data stream. You can sort
|
|
lexically (with or without case-folding) or numerically, ascending or
|
|
descending; and you can sort primary by one column, then secondarily by
|
|
another, etc.
|
|
|
|
Input data:
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --c2p cat example.csv
|
|
GENMD-EOF
|
|
|
|
Sorted numerically ascending by rate:
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --c2p sort -n rate example.csv
|
|
GENMD-EOF
|
|
|
|
Sorted lexically ascending by color; then, within each color, numerically descending by quantity:
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --c2p sort -f color -nr quantity example.csv
|
|
GENMD-EOF
|
|
|
|
## Sorting fields within records: the sort-within-records verb
|
|
|
|
The `sort-within-records` verb (see [its
|
|
documentation](reference-verbs.md#sort-within-records) for more information)
|
|
leaves records in their original order in the data stream, but reorders fields
|
|
within each record. A typical use-case is for given all records the same column-ordering,
|
|
in particular for converting JSON to CSV (or other tabular formats):
|
|
|
|
GENMD-RUN-COMMAND
|
|
cat data/sort-within-records.json
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --ijson --opprint cat data/sort-within-records.json
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --ijson --opprint sort-within-records data/sort-within-records.json
|
|
GENMD-EOF
|
|
|
|
## The sort function by example
|
|
|
|
* It returns a sorted copy of an input array or map.
|
|
* Without second argument, uses the natural ordering.
|
|
* With second which is string, takes sorting flags from it: `"f"` for lexical or `"c"` for case-folded lexical, and/or `"r"` for reverse/descending.
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr -n put '
|
|
end {
|
|
# Sort array with natural ordering
|
|
print sort([5,2,3,1,4]);
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr -n put '
|
|
end {
|
|
# Sort array with reverse-natural ordering
|
|
print sort([5,2,3,1,4], "r");
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr -n put '
|
|
end {
|
|
# Sort array with custom function: natural ordering
|
|
print sort([5,2,3,1,4], func(a,b) { return a <=> b});
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr -n put '
|
|
end {
|
|
# Sort array with custom function: reverse-natural ordering
|
|
print sort([5,2,3,1,4], func(a,b) { return b <=> a});
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr -n put '
|
|
end {
|
|
# Sort map with natural ordering on keys
|
|
print sort({"c":2, "a": 3, "b": 1});
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr -n put '
|
|
end {
|
|
# Sort map with reverse-natural ordering on keys
|
|
print sort({"c":2, "a": 3, "b": 1}, "r");
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr -n put '
|
|
end {
|
|
# Sort map with custom function: natural ordering on values
|
|
print sort({"c":2, "a": 3, "b": 1}, func(ak,av,bk,bv){return av <=> bv});
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr -n put '
|
|
end {
|
|
# Sort map with custom function: reverse-natural ordering on values
|
|
print sort({"c":2, "a": 3, "b": 1}, func(ak,av,bk,bv){return bv <=> av});
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
In the rest of this page we'll look more closely at these variants.
|
|
|
|
## Simple sorting of arrays
|
|
|
|
Using the [`sort`](reference-dsl-builtin-functions.md#sort) function, you can
|
|
get a copy of an array, sorted by its values -- optionally, with reversed
|
|
order, and/or lexical/case-folded sorting. The first argument is an array to be
|
|
sorted. The optional second argument is a string containing any of the
|
|
characters `n` for numeric (the default anyway), `f` for lexical, or `c` for
|
|
case-folded lexical, and `r` for reverse. Note that `sort` does not modify
|
|
its argument; it returns a sorted copy.
|
|
|
|
Also note that all the flags to `sort` allow you to operate on arrays which
|
|
contain strings, floats, and booleans; if you need to sort an array whose
|
|
values are themselves maps or arrays, you'll need `sort` with function argument
|
|
as described further down in this page.
|
|
|
|
GENMD-RUN-COMMAND
|
|
cat data/sorta-example.csv
|
|
GENMD-EOF
|
|
|
|
Default sort is numerical ascending:
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --c2p --from data/sorta-example.csv put '
|
|
$values = splita($values, ";");
|
|
$values = sort($values); # default flags
|
|
$values = joinv($values, ";");
|
|
'
|
|
GENMD-EOF
|
|
|
|
Use the `"r"` flag for reverse, which is numerical descending:
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --c2p --from data/sorta-example.csv put '
|
|
$values = splita($values, ";");
|
|
$values = sort($values, "r"); # 'r' flag for reverse sort
|
|
$values = joinv($values, ";");
|
|
'
|
|
GENMD-EOF
|
|
|
|
Use the `"f"` flag for lexical ascending sort (and `"fr"` would lexical descending):
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --c2p --from data/sorta-example.csv put '
|
|
$values = splita($values, ";");
|
|
$values = sort($values, "f"); # 'f' flag for lexical sort
|
|
$values = joinv($values, ";");
|
|
'
|
|
GENMD-EOF
|
|
|
|
Without and with case-folding:
|
|
|
|
GENMD-RUN-COMMAND
|
|
cat data/sorta-example-text.csv
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --c2p --from data/sorta-example-text.csv put '
|
|
$values = splita($values, ";");
|
|
if (NR == 1) {
|
|
$values = sort($values, "f"); # 'f' flag for (non-folded) lexical sort
|
|
} else {
|
|
$values = sort($values, "c"); # 'c' flag for case-folded lexical sort
|
|
}
|
|
$values = joinv($values, ";");
|
|
'
|
|
GENMD-EOF
|
|
|
|
## Simple sorting of maps within records
|
|
|
|
Using the [`sort`](reference-dsl-builtin-functions.md#sort) function, you
|
|
can sort a map by its keys.
|
|
|
|
Since `sort` only gives you options for sorting a map by its keys, if you want
|
|
to sort a map by its values you'll need `sort` with function argument as
|
|
described further down in this page.
|
|
|
|
Also note that, unlike the `sort-within-record` verb with its `-r` flag,
|
|
`sort` doesn't recurse into submaps and sort those.
|
|
|
|
GENMD-RUN-COMMAND
|
|
cat data/server-log.json
|
|
GENMD-EOF
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --json --from data/server-log.json put '
|
|
$req = sort($req); # Ascending here
|
|
$res = sort($res, "r"); # Descending here
|
|
'
|
|
GENMD-EOF
|
|
|
|
## Simple sorting of maps across records
|
|
|
|
As discussed in the page on
|
|
[operating on all records](operating-on-all-records.md), while Miller is normally
|
|
[streaming](streaming-and-memory.md) (we operate on one record at a time), we
|
|
can accumulate records in an array-valued or map-valued
|
|
[out-of-stream variable](reference-dsl-variables.md#out-of-stream-variables),
|
|
then operate on that record-list in an `end` block. This includes the possibility
|
|
of accumulating records in a map, then sorting the map.
|
|
|
|
Using the `f` flag we're sorting the map keys (1-up NR) lexically, so we
|
|
have 1, then 10, then 2:
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --icsv --opprint --from example.csv put -q '
|
|
begin {
|
|
@records = {}; # Define as a map
|
|
}
|
|
$nr = NR;
|
|
@records[NR] = $*; # Accumulate
|
|
end {
|
|
@records = sort(@records, "f");
|
|
for (_, record in @records) {
|
|
emit1 record;
|
|
}
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
## Custom sorting of arrays within records
|
|
|
|
Using the [`sort`](reference-dsl-builtin-functions.md#sort) function, you
|
|
can sort an array by its values, using another function (which you specify --
|
|
see the [page on user-defined functions](reference-dsl-user-defined-functions.md))
|
|
for comparing elements.
|
|
|
|
* Your function must take two arguments, which will range over various pairs of values in your array;
|
|
* It must return a number which is negative, zero, or positive depending on whether you want the first argument to sort less than, equal to, or greater than the second, respectively.
|
|
|
|
For example, let's use the following input data. Instead of having an array, it
|
|
has some semicolon-delimited data in a field which we can split and sort:
|
|
|
|
GENMD-RUN-COMMAND
|
|
cat data/sortaf-example.csv
|
|
GENMD-EOF
|
|
|
|
In the following example we sort data in several ways -- the first two just
|
|
recaptiulate (for reference) what `sort` with default flags already does; the third is novel:
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --icsv --ojson --from data/sortaf-example.csv put '
|
|
|
|
# Same as sort($values)
|
|
func forward(a,b) {
|
|
return a <=> b
|
|
}
|
|
|
|
# Same as sort($values, "r")
|
|
func reverse(a,b) {
|
|
return b <=> a
|
|
}
|
|
|
|
# Custom sort
|
|
func even_then_odd(a,b) {
|
|
ax = a % 2;
|
|
bx = b % 2;
|
|
if (ax == bx) {
|
|
return a <=> b
|
|
} elif (bx == 1) {
|
|
return -1
|
|
} else {
|
|
return 1
|
|
}
|
|
}
|
|
|
|
split_values = splita($values, ";");
|
|
$forward = sort(split_values, forward);
|
|
$reverse = sort(split_values, reverse);
|
|
$even_then_odd = sort(split_values, even_then_odd);
|
|
'
|
|
GENMD-EOF
|
|
|
|
## Custom sorting of arrays across records
|
|
|
|
As noted above, we can use the
|
|
[operating-on-all-records](operating-on-all-records.md) paradigm
|
|
to accumulate records in an array-valued or map-valued
|
|
[out-of-stream variable](reference-dsl-variables.md#out-of-stream-variables),
|
|
then operate on that record-list in an `end` block. This includes the possibility
|
|
of accumulating records in an array, then sorting the array.
|
|
|
|
Note that here the array elements are maps, so the `a` and `b` arguments to our
|
|
functions are maps -- and we have to access the `index` field using either
|
|
`a["index"]` and `b["index"]`, or (using the [dot operator for
|
|
indexing](reference-dsl-operators.md#the-double-purpose-dot-operator))
|
|
`a.index` and `b.index`.
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --icsv --opprint --from example.csv put -q '
|
|
# Sort primarily ascending on the shape field, then secondarily
|
|
# descending numeric on the index field.
|
|
func cmp(a, b) {
|
|
cmp1 = a.shape <=> b.shape;
|
|
if (cmp1 != 0) {
|
|
return cmp1
|
|
} else {
|
|
return b.index <=> a.index;
|
|
}
|
|
}
|
|
begin {
|
|
@records = []; # Define as an array, else auto-create will make a map
|
|
}
|
|
@records[NR] = $*; # Accumulate
|
|
end {
|
|
@records = sort(@records, cmp);
|
|
for (record in @records) {
|
|
emit1 record;
|
|
}
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
## Custom sorting of maps within records
|
|
|
|
Using the [`sort`](reference-dsl-builtin-functions.md#sort) function, you
|
|
can sort a map using a function which you specify (see the [page on
|
|
user-defined functions](reference-dsl-user-defined-functions.md)) for comparing
|
|
keys and/or values.
|
|
|
|
* Your function must take four arguments, which will range over various pairs of key-value pairs in your map;
|
|
* It must return a number which is negative, zero, or positive depending on whether you want the first argument to sort less than, equal to, or greater than the second, respectively.
|
|
|
|
For example, we can sort ascending or descending by map key or map value:
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr -n put -q '
|
|
func f1(ak, av, bk, bv) {
|
|
return ak <=> bk
|
|
}
|
|
func f2(ak, av, bk, bv) {
|
|
return bk <=> ak
|
|
}
|
|
func f3(ak, av, bk, bv) {
|
|
return av <=> bv
|
|
}
|
|
func f4(ak, av, bk, bv) {
|
|
return bv <=> av
|
|
}
|
|
end {
|
|
x = {
|
|
"c":1,
|
|
"a":3,
|
|
"b":2,
|
|
};
|
|
|
|
print sort(x, f1);
|
|
print sort(x, f2);
|
|
print sort(x, f3);
|
|
print sort(x, f4);
|
|
}
|
|
'
|
|
GENMD-EOF
|
|
|
|
## Custom sorting of maps across records
|
|
|
|
We can modify our above example just a bit, where we accumulate records in a map rather than
|
|
an array. Here the map keys will be `NR` values `"1"`, `"2"`, etc.
|
|
|
|
Why would we do this? When we're operating across all records and keeping all
|
|
of them -- densely -- accumulating them in an array is fine. If we're only
|
|
taking a subset -- sparsely -- and we want to retain the original `NR` as keys,
|
|
using a map is handy, since we don't need continguous keys.
|
|
|
|
GENMD-RUN-COMMAND
|
|
mlr --icsv --opprint --from example.csv put -q '
|
|
# Sort descending numeric on the index field
|
|
func cmp(ak, av, bk, bv) {
|
|
return bv.index <=> av.index
|
|
}
|
|
begin {
|
|
@records = {}; # Define as a map
|
|
}
|
|
@records[NR] = $*; # Accumulate
|
|
end {
|
|
@records = sort(@records, cmp);
|
|
for (_, record in @records) {
|
|
emit1 record;
|
|
}
|
|
}
|
|
'
|
|
GENMD-EOF
|