mirror of
https://github.com/johnkerl/miller.git
synced 2026-01-23 02:14:13 +00:00
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:
parent
a64133250c
commit
c1a1589174
15 changed files with 358 additions and 82 deletions
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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">
|
||||
|
|
|
|||
|
|
@ -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});
|
||||
}
|
||||
'
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -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()),
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1
test/cases/dsl-sorts/sortmv/cmd
Normal file
1
test/cases/dsl-sorts/sortmv/cmd
Normal file
|
|
@ -0,0 +1 @@
|
|||
mlr --json --from ${CASEDIR}/input put -f ${CASEDIR}/mlr
|
||||
0
test/cases/dsl-sorts/sortmv/experr
Normal file
0
test/cases/dsl-sorts/sortmv/experr
Normal file
143
test/cases/dsl-sorts/sortmv/expout
Normal file
143
test/cases/dsl-sorts/sortmv/expout
Normal 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"
|
||||
}
|
||||
}
|
||||
]
|
||||
21
test/cases/dsl-sorts/sortmv/input
Normal file
21
test/cases/dsl-sorts/sortmv/input
Normal 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"
|
||||
}
|
||||
}
|
||||
8
test/cases/dsl-sorts/sortmv/mlr
Normal file
8
test/cases/dsl-sorts/sortmv/mlr
Normal 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");
|
||||
Loading…
Add table
Add a link
Reference in a new issue