# 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),
numerically, or naturally (see
[https://en.wikipedia.org/wiki/Natural_sort_order](https://en.wikipedia.org/wiki/Natural_sort_order)
or [https://github.com/facette/natsort](https://github.com/facette/natsort) for more about natural
sorting); ascending or descending; and you can sort primarily 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

Example of natural sort, adapted from [https://github.com/facette/natsort](https://github.com/facette/natsort):

GENMD-RUN-COMMAND
mlr --c2p cat data/natsort.csv
GENMD-EOF

GENMD-RUN-COMMAND
mlr --c2p sort -t name data/natsort.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

The Miller DSL has a [`sort`](reference-dsl-builtin-functions.md#sort) function:

* It returns a sorted copy of an input array or map.
* Without second argument, uses Miller's default ordering which is numbers numerically, then strings lexically.
* With second which is string, takes sorting flags from it: `"f"` for lexical or `"c"` for case-folded lexical, or `"t"` for natural sort order. An additional `"r"` in this string is for reverse/descending.

GENMD-RUN-COMMAND
mlr -n put '
  end {
    # Sort array with default ordering
    print sort([5,2,3,1,4]);
  }
'
GENMD-EOF

GENMD-RUN-COMMAND
mlr -n put '
  end {
    # Sort array with reverse-default ordering
    print sort([5,2,3,1,4], "r");
  }
'
GENMD-EOF

GENMD-RUN-COMMAND
mlr -n put '
  end {
    # Sort array with custom function: another way to get default 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: another way to get reverse-default 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 default ordering on keys
    print sort({"c":2, "a": 3, "b": 1});
  }
'
GENMD-EOF

GENMD-RUN-COMMAND
mlr -n put '
  end {
    # Sort map with reverse-default 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: default ordering on values
    print sort({"c":2, "a": 3, "b": 1}, "v");
    # Same:
    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-default ordering on values
    print sort({"c":2, "a": 3, "b": 1}, "vr");
    # Same:
    print sort({"c":2, "a": 3, "b": 1}, func(ak,av,bk,bv){return bv <=> av});
  }
'
GENMD-EOF

GENMD-RUN-COMMAND
mlr -n put '
  end {
    # Natural sort
    print sort(["a1","a10","a100","a2","a20","a200"], "t");
  }
'
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
recapitulate (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 contiguous 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
