Support simplified sort-map-by-value in the DSL (#1069)

* add shorthands for sorting maps by value

* unit-test files

* on-line help content
This commit is contained in:
John Kerl 2022-08-07 00:31:08 -04:00 committed by GitHub
parent a64133250c
commit c1a1589174
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 358 additions and 82 deletions

View file

@ -2593,7 +2593,7 @@ FUNCTIONS FOR FILTER/PUT
(class=math #args=1) Hyperbolic sine.
sort
(class=higher-order-functions #args=1-2) Given a map or array as first argument and string flags or function as optional second argument, returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse. If the second argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or > 0, using a and b's keys and values.
(class=higher-order-functions #args=1-2) Given a map or array as first argument and string flags or function as optional second argument, returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse. An additional "v" in that string means sort maps by value, rather than by key. If the second argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or > 0, using a and b's keys and values.
Examples:
Default sorting: sort([3,"A",1,"B",22]) returns [1, 3, 20, "A", "B"].
Note that this is numbers before strings.
@ -2604,6 +2604,9 @@ FUNCTIONS FOR FILTER/PUT
Natural sorting: sort(["a1","a10","a100","a2","a20","a200"], "t") returns ["a1", "a2", "a10", "a20", "a100", "a200"].
Array with function: sort([5,2,3,1,4], func(a,b) {return b <=> a}) returns [5,4,3,2,1].
Map with function: sort({"c":2,"a":3,"b":1}, func(ak,av,bk,bv) {return bv <=> av}) returns {"a":3,"c":2,"b":1}.
Map without function: sort({"c":2,"a":3,"b":1}) returns {"a":3,"b":1,"c":2}.
Map without function: sort({"c":2,"a":3,"b":1}, "v") returns {"b":1,"c":2,"a":3}.
Map without function: sort({"c":2,"a":3,"b":1}, "vnr") returns {"a":3,"c":2,"b":1}.
splita
(class=conversion #args=2) Splits string into array with type inference. First argument is string to split; second is the separator to split on.

View file

@ -2572,7 +2572,7 @@ FUNCTIONS FOR FILTER/PUT
(class=math #args=1) Hyperbolic sine.
sort
(class=higher-order-functions #args=1-2) Given a map or array as first argument and string flags or function as optional second argument, returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse. If the second argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or > 0, using a and b's keys and values.
(class=higher-order-functions #args=1-2) Given a map or array as first argument and string flags or function as optional second argument, returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse. An additional "v" in that string means sort maps by value, rather than by key. If the second argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or > 0, using a and b's keys and values.
Examples:
Default sorting: sort([3,"A",1,"B",22]) returns [1, 3, 20, "A", "B"].
Note that this is numbers before strings.
@ -2583,6 +2583,9 @@ FUNCTIONS FOR FILTER/PUT
Natural sorting: sort(["a1","a10","a100","a2","a20","a200"], "t") returns ["a1", "a2", "a10", "a20", "a100", "a200"].
Array with function: sort([5,2,3,1,4], func(a,b) {return b <=> a}) returns [5,4,3,2,1].
Map with function: sort({"c":2,"a":3,"b":1}, func(ak,av,bk,bv) {return bv <=> av}) returns {"a":3,"c":2,"b":1}.
Map without function: sort({"c":2,"a":3,"b":1}) returns {"a":3,"b":1,"c":2}.
Map without function: sort({"c":2,"a":3,"b":1}, "v") returns {"b":1,"c":2,"a":3}.
Map without function: sort({"c":2,"a":3,"b":1}, "vnr") returns {"a":3,"c":2,"b":1}.
splita
(class=conversion #args=2) Splits string into array with type inference. First argument is string to split; second is the separator to split on.

View file

@ -716,7 +716,7 @@ Map example: select({"a":1, "b":3, "c":5}, func(k,v) {return v >= 3}) returns {"
### sort
<pre class="pre-non-highlight-non-pair">
sort (class=higher-order-functions #args=1-2) Given a map or array as first argument and string flags or function as optional second argument, returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse. If the second argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or > 0, using a and b's keys and values.
sort (class=higher-order-functions #args=1-2) Given a map or array as first argument and string flags or function as optional second argument, returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse. An additional "v" in that string means sort maps by value, rather than by key. If the second argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or > 0, using a and b's keys and values.
Examples:
Default sorting: sort([3,"A",1,"B",22]) returns [1, 3, 20, "A", "B"].
Note that this is numbers before strings.
@ -727,6 +727,9 @@ Case-folded descending: sort(["E","a","c","B","d"], "cr") returns ["E", "d", "c"
Natural sorting: sort(["a1","a10","a100","a2","a20","a200"], "t") returns ["a1", "a2", "a10", "a20", "a100", "a200"].
Array with function: sort([5,2,3,1,4], func(a,b) {return b <=> a}) returns [5,4,3,2,1].
Map with function: sort({"c":2,"a":3,"b":1}, func(ak,av,bk,bv) {return bv <=> av}) returns {"a":3,"c":2,"b":1}.
Map without function: sort({"c":2,"a":3,"b":1}) returns {"a":3,"b":1,"c":2}.
Map without function: sort({"c":2,"a":3,"b":1}, "v") returns {"b":1,"c":2,"a":3}.
Map without function: sort({"c":2,"a":3,"b":1}, "vnr") returns {"a":3,"c":2,"b":1}.
</pre>
## Math functions

View file

@ -302,7 +302,9 @@ a b c
<b>mlr -n put '</b>
<b> end {</b>
<b> # Sort map with custom function: default ordering on values</b>
<b> print sort({"c":2, "a": 3, "b": 1}, func(ak,av,bk,bv){return av <=> bv});</b>
<b> print sort({"c":2, "a": 3, "b": 1}, "v");</b>
<b> # Same:</b>
<b> print sort({"c":2, "a": 3, "b": 1}, func(ak,av,bk,bv) {return av <=> bv});</b>
<b> }</b>
<b>'</b>
</pre>
@ -312,12 +314,19 @@ a b c
"c": 2,
"a": 3
}
{
"b": 1,
"c": 2,
"a": 3
}
</pre>
<pre class="pre-highlight-in-pair">
<b>mlr -n put '</b>
<b> end {</b>
<b> # Sort map with custom function: reverse-default ordering on values</b>
<b> print sort({"c":2, "a": 3, "b": 1}, "vr");</b>
<b> # Same:</b>
<b> print sort({"c":2, "a": 3, "b": 1}, func(ak,av,bk,bv){return bv <=> av});</b>
<b> }</b>
<b>'</b>
@ -328,6 +337,11 @@ a b c
"c": 2,
"b": 1
}
{
"a": 3,
"c": 2,
"b": 1
}
</pre>
<pre class="pre-highlight-in-pair">

View file

@ -128,7 +128,9 @@ GENMD-RUN-COMMAND
mlr -n put '
end {
# Sort map with custom function: default ordering on values
print sort({"c":2, "a": 3, "b": 1}, func(ak,av,bk,bv){return av <=> bv});
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
@ -137,6 +139,8 @@ 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});
}
'

View file

@ -1848,12 +1848,14 @@ key and value, and map-element key and value; it should return the updated accum
name: "sort",
class: FUNC_CLASS_HOFS,
help: `Given a map or array as first argument and string flags or function as optional second argument,
returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and
then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can
contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural
sort order. An additional "r" in that string is for reverse. If the second argument is a function, then for
arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively;
for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or
returns a sorted copy of the input. With one argument, sorts array elements with numbers first
numerically and then strings lexically, and map elements likewise by map keys. If the second
argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for
case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse.
An additional "v" in that string means sort maps by value, rather than by key. If the second
argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or >
0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av,
bk, and bv, again returning < 0, 0, or
> 0, using a and b's keys and values.`,
examples: []string{
`Default sorting: sort([3,"A",1,"B",22]) returns [1, 3, 20, "A", "B"].`,
@ -1865,6 +1867,9 @@ for maps the function should take four arguments ak, av, bk, and bv, again retur
`Natural sorting: sort(["a1","a10","a100","a2","a20","a200"], "t") returns ["a1", "a2", "a10", "a20", "a100", "a200"].`,
`Array with function: sort([5,2,3,1,4], func(a,b) {return b <=> a}) returns [5,4,3,2,1].`,
`Map with function: sort({"c":2,"a":3,"b":1}, func(ak,av,bk,bv) {return bv <=> av}) returns {"a":3,"c":2,"b":1}.`,
`Map without function: sort({"c":2,"a":3,"b":1}) returns {"a":3,"b":1,"c":2}.`,
`Map without function: sort({"c":2,"a":3,"b":1}, "v") returns {"b":1,"c":2,"a":3}.`,
`Map without function: sort({"c":2,"a":3,"b":1}, "vnr") returns {"a":3,"c":2,"b":1}.`,
},
variadicFuncWithState: SortHOF,
minimumVariadicArity: 1,

View file

@ -526,16 +526,16 @@ func SortHOF(
if inputs[0].IsArray() {
return sortA(inputs[0], "")
} else if inputs[0].IsMap() {
return sortMK(inputs[0], "")
return sortM(inputs[0], "")
} else {
return mlrval.ERROR
}
} else if inputs[1].IsString() {
} else if inputs[1].IsStringOrVoid() {
if inputs[0].IsArray() {
return sortA(inputs[0], inputs[1].String())
} else if inputs[0].IsMap() {
return sortMK(inputs[0], inputs[1].String())
return sortM(inputs[0], inputs[1].String())
} else {
return mlrval.ERROR
}
@ -572,9 +572,10 @@ const (
// decodeSortFlags maps strings like "cr" in the second argument to sort
// into sortType=sortTypeCaseFold and reverse=true, etc.
func decodeSortFlags(flags string) (tSortType, bool) {
var sortType tSortType = sortTypeNumerical
reverse := false
func decodeSortFlags(flags string) (sortType tSortType, reverse bool, byMapValue bool) {
sortType = sortTypeNumerical
reverse = false
byMapValue = false
for _, c := range flags {
switch c {
case 'n':
@ -587,9 +588,11 @@ func decodeSortFlags(flags string) (tSortType, bool) {
sortType = sortTypeNatural
case 'r':
reverse = true
case 'v':
byMapValue = true
}
}
return sortType, reverse
return sortType, reverse, byMapValue
}
// sortA implements sort on array, with string flags rather than callback UDF.
@ -603,7 +606,8 @@ func sortA(
output := input1.Copy()
sortType, reverse := decodeSortFlags(flags)
// byMapValue is ignored for sorting arrays
sortType, reverse, _ := decodeSortFlags(flags)
a := output.GetArray()
switch sortType {
@ -668,8 +672,8 @@ func sortANatural(array []*mlrval.Mlrval, reverse bool) {
}
}
// sortA implements sort on map, with string flags rather than callback UDF.
func sortMK(
// sortM implements sort on map, with string flags rather than callback UDF.
func sortM(
input1 *mlrval.Mlrval,
flags string,
) *mlrval.Mlrval {
@ -678,99 +682,153 @@ func sortMK(
return mlrval.ERROR
}
// Copy the keys to an array for sorting.
// TODO: make a helper function and share with BIF_get_keys
// Get sort-flags, if provided
sortType, reverse, byMapValue := decodeSortFlags(flags)
// Copy the entries to an array for sorting.
n := inmap.FieldCount
keys := make([]string, n)
entries := make([]mlrval.MlrmapEntryForArray, n)
i := 0
for pe := inmap.Head; pe != nil; pe = pe.Next {
keys[i] = pe.Key
entries[i].Key = pe.Key
entries[i].Value = pe.Value // pointer alias for now until new map at end of this function
i++
}
// Get sort-flags, if provided
sortType, reverse := decodeSortFlags(flags)
// Do the key-sort
switch sortType {
case sortTypeNumerical:
sortMKNumerical(keys, reverse)
sortMNumerical(entries, reverse, byMapValue)
case sortTypeLexical:
sortMKLexical(keys, reverse)
sortMLexical(entries, reverse, byMapValue)
case sortTypeCaseFold:
sortMKCaseFold(keys, reverse)
sortMCaseFold(entries, reverse, byMapValue)
case sortTypeNatural:
sortMKNatural(keys, reverse)
sortMNatural(entries, reverse, byMapValue)
}
// Make a new map with keys in the new sort order.
// Make a new map with entries in the new sort order.
outmap := mlrval.NewMlrmap()
for i := int64(0); i < n; i++ {
key := keys[i]
outmap.PutCopy(key, inmap.Get(key))
entry := entries[i]
outmap.PutCopy(entry.Key, entry.Value)
}
return mlrval.FromMap(outmap)
}
func sortMKNumerical(array []string, reverse bool) {
if !reverse {
sort.Slice(array, func(i, j int) bool {
na, erra := strconv.ParseFloat(array[i], 64)
nb, errb := strconv.ParseFloat(array[j], 64)
if erra == nil && errb == nil {
return na < nb
} else {
return array[i] < array[j]
}
})
func sortMNumerical(array []mlrval.MlrmapEntryForArray, reverse bool, byMapValue bool) {
if !byMapValue {
if !reverse {
sort.Slice(array, func(i, j int) bool {
na, erra := strconv.ParseFloat(array[i].Key, 64)
nb, errb := strconv.ParseFloat(array[j].Key, 64)
if erra == nil && errb == nil {
return na < nb
} else {
return array[i].Key < array[j].Key
}
})
} else {
sort.Slice(array, func(i, j int) bool {
na, erra := strconv.ParseFloat(array[i].Key, 64)
nb, errb := strconv.ParseFloat(array[j].Key, 64)
if erra == nil && errb == nil {
return na > nb
} else {
return array[i].Key > array[j].Key
}
})
}
} else {
sort.Slice(array, func(i, j int) bool {
na, erra := strconv.ParseFloat(array[i], 64)
nb, errb := strconv.ParseFloat(array[j], 64)
if erra == nil && errb == nil {
return na > nb
} else {
return array[i] > array[j]
}
})
if !reverse {
sort.Slice(array, func(i, j int) bool {
return mlrval.LessThan(array[i].Value, array[j].Value)
})
} else {
sort.Slice(array, func(i, j int) bool {
return mlrval.LessThan(array[j].Value, array[i].Value)
})
}
}
}
func sortMKLexical(array []string, reverse bool) {
if !reverse {
// Or sort.Strings(keys) would work here as well.
sort.Slice(array, func(i, j int) bool {
return array[i] < array[j]
})
func sortMLexical(array []mlrval.MlrmapEntryForArray, reverse bool, byMapValue bool) {
if !byMapValue {
if !reverse {
// Or sort.Strings(keys) would work here as well.
sort.Slice(array, func(i, j int) bool {
return array[i].Key < array[j].Key
})
} else {
sort.Slice(array, func(i, j int) bool {
return array[i].Key > array[j].Key
})
}
} else {
sort.Slice(array, func(i, j int) bool {
return array[i] > array[j]
})
if !reverse {
sort.Slice(array, func(i, j int) bool {
return array[i].Value.String() < array[j].Value.String()
})
} else {
sort.Slice(array, func(i, j int) bool {
return array[i].Value.String() > array[j].Value.String()
})
}
}
}
func sortMKCaseFold(array []string, reverse bool) {
if !reverse {
sort.Slice(array, func(i, j int) bool {
return strings.ToLower(array[i]) < strings.ToLower(array[j])
})
func sortMCaseFold(array []mlrval.MlrmapEntryForArray, reverse bool, byMapValue bool) {
if !byMapValue {
if !reverse {
sort.Slice(array, func(i, j int) bool {
return strings.ToLower(array[i].Key) < strings.ToLower(array[j].Key)
})
} else {
sort.Slice(array, func(i, j int) bool {
return strings.ToLower(array[i].Key) > strings.ToLower(array[j].Key)
})
}
} else {
sort.Slice(array, func(i, j int) bool {
return strings.ToLower(array[i]) > strings.ToLower(array[j])
})
if !reverse {
sort.Slice(array, func(i, j int) bool {
return strings.ToLower(array[i].Value.String()) < strings.ToLower(array[j].Value.String())
})
} else {
sort.Slice(array, func(i, j int) bool {
return strings.ToLower(array[i].Value.String()) > strings.ToLower(array[j].Value.String())
})
}
}
}
func sortMKNatural(array []string, reverse bool) {
if !reverse {
sort.Slice(array, func(i, j int) bool {
return natsort.Compare(strings.ToLower(array[i]), strings.ToLower(array[j]))
})
func sortMNatural(array []mlrval.MlrmapEntryForArray, reverse bool, byMapValue bool) {
if !byMapValue {
if !reverse {
sort.Slice(array, func(i, j int) bool {
return natsort.Compare(strings.ToLower(array[i].Key), strings.ToLower(array[j].Key))
})
} else {
sort.Slice(array, func(i, j int) bool {
return natsort.Compare(strings.ToLower(array[j].Key), strings.ToLower(array[i].Key))
})
}
} else {
sort.Slice(array, func(i, j int) bool {
return natsort.Compare(strings.ToLower(array[j]), strings.ToLower(array[i]))
})
if !reverse {
sort.Slice(array, func(i, j int) bool {
return natsort.Compare(
strings.ToLower(array[i].Value.String()),
strings.ToLower(array[j].Value.String()),
)
})
} else {
sort.Slice(array, func(i, j int) bool {
return natsort.Compare(
strings.ToLower(array[j].Value.String()),
strings.ToLower(array[i].Value.String()),
)
})
}
}
}

View file

@ -83,6 +83,13 @@ type MlrmapEntry struct {
Next *MlrmapEntry
}
// MlrmapEntryForArray is for use by sorting routines where the Prev/Next pointers
// are irrelevant as well as ephemeral
type MlrmapEntryForArray struct {
Key string
Value *Mlrval
}
// Only used for sorting, map-to-pairs-array and pairs-array-to-map contexts.
type MlrmapPair struct {
Key string

View file

@ -2572,7 +2572,7 @@ FUNCTIONS FOR FILTER/PUT
(class=math #args=1) Hyperbolic sine.
sort
(class=higher-order-functions #args=1-2) Given a map or array as first argument and string flags or function as optional second argument, returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse. If the second argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or > 0, using a and b's keys and values.
(class=higher-order-functions #args=1-2) Given a map or array as first argument and string flags or function as optional second argument, returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse. An additional "v" in that string means sort maps by value, rather than by key. If the second argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or > 0, using a and b's keys and values.
Examples:
Default sorting: sort([3,"A",1,"B",22]) returns [1, 3, 20, "A", "B"].
Note that this is numbers before strings.
@ -2583,6 +2583,9 @@ FUNCTIONS FOR FILTER/PUT
Natural sorting: sort(["a1","a10","a100","a2","a20","a200"], "t") returns ["a1", "a2", "a10", "a20", "a100", "a200"].
Array with function: sort([5,2,3,1,4], func(a,b) {return b <=> a}) returns [5,4,3,2,1].
Map with function: sort({"c":2,"a":3,"b":1}, func(ak,av,bk,bv) {return bv <=> av}) returns {"a":3,"c":2,"b":1}.
Map without function: sort({"c":2,"a":3,"b":1}) returns {"a":3,"b":1,"c":2}.
Map without function: sort({"c":2,"a":3,"b":1}, "v") returns {"b":1,"c":2,"a":3}.
Map without function: sort({"c":2,"a":3,"b":1}, "vnr") returns {"a":3,"c":2,"b":1}.
splita
(class=conversion #args=2) Splits string into array with type inference. First argument is string to split; second is the separator to split on.

View file

@ -3955,7 +3955,7 @@ Map example: select({"a":1, "b":3, "c":5}, func(k,v) {return v >= 3}) returns {"
.RS 0
.\}
.nf
(class=higher-order-functions #args=1-2) Given a map or array as first argument and string flags or function as optional second argument, returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse. If the second argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or > 0, using a and b's keys and values.
(class=higher-order-functions #args=1-2) Given a map or array as first argument and string flags or function as optional second argument, returns a sorted copy of the input. With one argument, sorts array elements with numbers first numerically and then strings lexically, and map elements likewise by map keys. If the second argument is a string, it can contain any of "f" for lexical ("n" is for the above default), "c" for case-folded lexical, or "t" for natural sort order. An additional "r" in that string is for reverse. An additional "v" in that string means sort maps by value, rather than by key. If the second argument is a function, then for arrays it should take two arguments a and b, returning < 0, 0, or > 0 as a < b, a == b, or a > b respectively; for maps the function should take four arguments ak, av, bk, and bv, again returning < 0, 0, or > 0, using a and b's keys and values.
Examples:
Default sorting: sort([3,"A",1,"B",22]) returns [1, 3, 20, "A", "B"].
Note that this is numbers before strings.
@ -3966,6 +3966,9 @@ Case-folded descending: sort(["E","a","c","B","d"], "cr") returns ["E", "d", "c"
Natural sorting: sort(["a1","a10","a100","a2","a20","a200"], "t") returns ["a1", "a2", "a10", "a20", "a100", "a200"].
Array with function: sort([5,2,3,1,4], func(a,b) {return b <=> a}) returns [5,4,3,2,1].
Map with function: sort({"c":2,"a":3,"b":1}, func(ak,av,bk,bv) {return bv <=> av}) returns {"a":3,"c":2,"b":1}.
Map without function: sort({"c":2,"a":3,"b":1}) returns {"a":3,"b":1,"c":2}.
Map without function: sort({"c":2,"a":3,"b":1}, "v") returns {"b":1,"c":2,"a":3}.
Map without function: sort({"c":2,"a":3,"b":1}, "vnr") returns {"a":3,"c":2,"b":1}.
.fi
.if n \{\
.RE

View file

@ -0,0 +1 @@
mlr --json --from ${CASEDIR}/input put -f ${CASEDIR}/mlr

View file

View file

@ -0,0 +1,143 @@
[
{
"map": {
"1": 6,
"2": 5,
"3": 4
},
"default": {
"1": 6,
"2": 5,
"3": 4
},
"reverse": {
"1": 6,
"2": 5,
"3": 4
},
"numup": {
"3": 4,
"2": 5,
"1": 6
},
"numdown": {
"1": 6,
"2": 5,
"3": 4
},
"lexup": {
"3": 4,
"2": 5,
"1": 6
},
"lexdown": {
"1": 6,
"2": 5,
"3": 4
},
"foldup": {
"3": 4,
"2": 5,
"1": 6
},
"folddown": {
"1": 6,
"2": 5,
"3": 4
}
},
{
"map": {
"1": 2,
"2": 10,
"3": 1
},
"default": {
"1": 2,
"2": 10,
"3": 1
},
"reverse": {
"2": 10,
"1": 2,
"3": 1
},
"numup": {
"3": 1,
"1": 2,
"2": 10
},
"numdown": {
"2": 10,
"1": 2,
"3": 1
},
"lexup": {
"3": 1,
"2": 10,
"1": 2
},
"lexdown": {
"1": 2,
"2": 10,
"3": 1
},
"foldup": {
"3": 1,
"2": 10,
"1": 2
},
"folddown": {
"1": 2,
"2": 10,
"3": 1
}
},
{
"map": {
"1": "apple",
"2": "Ball",
"3": "cat"
},
"default": {
"1": "apple",
"2": "Ball",
"3": "cat"
},
"reverse": {
"3": "cat",
"1": "apple",
"2": "Ball"
},
"numup": {
"2": "Ball",
"1": "apple",
"3": "cat"
},
"numdown": {
"3": "cat",
"1": "apple",
"2": "Ball"
},
"lexup": {
"2": "Ball",
"1": "apple",
"3": "cat"
},
"lexdown": {
"3": "cat",
"1": "apple",
"2": "Ball"
},
"foldup": {
"1": "apple",
"2": "Ball",
"3": "cat"
},
"folddown": {
"3": "cat",
"2": "Ball",
"1": "apple"
}
}
]

View file

@ -0,0 +1,21 @@
{
"map": {
"1": 6,
"2": 5,
"3": 4
}
}
{
"map": {
"1": 2,
"2": 10,
"3": 1
}
}
{
"map": {
"1": "apple",
"2": "Ball",
"3": "cat"
}
}

View file

@ -0,0 +1,8 @@
$default = sort($map);
$reverse = sort($map, "vr");
$numup = sort($map, "vn");
$numdown = sort($map, "vnr");
$lexup = sort($map, "vf");
$lexdown = sort($map, "vfr");
$foldup = sort($map, "vc");
$folddown = sort($map, "vcr");