Compare commits

..

7 Commits

Author SHA1 Message Date
Eric Coissac 578445f38a Release 4.4.45 2026-06-02 15:01:32 +02:00
Eric Coissac 2edb33ad08 fix: correct duplicated typos in GVal function names
Corrects duplicated typos in the registered GVal function names, changing "which_maxwhichmax" and "which_minwhichmin" to "which_max" and "which_min". The underlying obiutils.WhichMax/WhichMin logic and its int-to-float64 index conversion remain unchanged.
2026-06-02 15:00:48 +02:00
coissac b108d4978e Merge pull request #118 from metabarcoding/push-twztopkvxyop
Release 4.4.44
2026-06-02 14:42:00 +02:00
Eric Coissac e9210e28a3 Release 4.4.44 2026-06-02 14:40:09 +02:00
Eric Coissac 13a93fce11 feat: add which_max and which_min to retrieve extreme element indices
Implement reflection-based WhichMax and WhichMin to dynamically find the index or key of the maximum/minimum element in slices, arrays, or maps. Functions validate orderability, handle empty collections, and dispatch via reflect.Kind. Expose as which_max and which_min GVal functions, with float64 type assertions for compatibility and preserved error handling.
2026-06-02 14:39:31 +02:00
Eric Coissac 14064c919e fix(obiutils): correctly unwrap interface values in min/max
Introduces an `unwrapInterface` reflection helper to dereference `interface{}`-wrapped values before type validation. Updates slice and map iteration loops in min/max functions to apply this helper, ensuring `isOrderedKind` accurately identifies underlying concrete types instead of incorrectly rejecting `reflect.Interface` elements.
2026-06-02 14:34:00 +02:00
coissac 1dfd68aa6d Merge pull request #117 from metabarcoding/push-wvlmzvomslzv
Release 4.4.43
2026-06-01 14:14:40 +02:00
4 changed files with 144 additions and 10 deletions
+1 -1
View File
@@ -3,7 +3,7 @@ package obioptions
// Version is automatically updated by the Makefile from version.txt
// The patch number (third digit) is incremented on each push to the repository
var _Version = "Release 4.4.43"
var _Version = "Release 4.4.45"
// Version returns the version of the obitools package.
//
+14
View File
@@ -141,6 +141,20 @@ var OBILang = gval.NewLanguage(
gval.Function("max", func(args ...interface{}) (interface{}, error) {
return obiutils.Max(args[0])
}),
gval.Function("whichmax", func(args ...interface{}) (interface{}, error) {
result, err := obiutils.WhichMax(args[0])
if idx, ok := result.(int); ok {
return float64(idx), nil
}
return result, err
}),
gval.Function("whichmin", func(args ...interface{}) (interface{}, error) {
result, err := obiutils.WhichMin(args[0])
if idx, ok := result.(int); ok {
return float64(idx), nil
}
return result, err
}),
gval.Function("filtermin", func(args ...interface{}) (interface{}, error) {
return obiutils.FilterMin(args[0], args[1])
+128 -8
View File
@@ -309,7 +309,7 @@ func saturatingSubValues(a, b reflect.Value) (reflect.Value, error) {
func maxFromIterable(v reflect.Value) (interface{}, error) {
var best reflect.Value
for i := 0; i < v.Len(); i++ {
elem := v.Index(i)
elem := unwrapInterface(v.Index(i))
if !isOrderedKind(elem.Kind()) {
return nil, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
@@ -324,7 +324,7 @@ func maxFromIterable(v reflect.Value) (interface{}, error) {
func minFromIterable(v reflect.Value) (interface{}, error) {
var minVal reflect.Value
for i := 0; i < v.Len(); i++ {
elem := v.Index(i)
elem := unwrapInterface(v.Index(i))
if !isOrderedKind(elem.Kind()) {
return nil, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
@@ -339,7 +339,7 @@ func filterMinFromIterable(v reflect.Value, minimum interface{}) (interface{}, e
minVal := reflect.ValueOf(minimum)
result := reflect.MakeSlice(v.Type(), 0, v.Len())
for i := 0; i < v.Len(); i++ {
elem := v.Index(i)
elem := unwrapInterface(v.Index(i))
if !isOrderedKind(elem.Kind()) {
return nil, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
@@ -354,7 +354,7 @@ func filterMaxFromIterable(v reflect.Value, maximum interface{}) (interface{}, e
maxVal := reflect.ValueOf(maximum)
result := reflect.MakeSlice(v.Type(), 0, v.Len())
for i := 0; i < v.Len(); i++ {
elem := v.Index(i)
elem := unwrapInterface(v.Index(i))
if !isOrderedKind(elem.Kind()) {
return nil, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
@@ -365,11 +365,121 @@ func filterMaxFromIterable(v reflect.Value, maximum interface{}) (interface{}, e
return result.Interface(), nil
}
// whichMaxFromIterable returns the index of the maximum element in a slice/array.
func whichMaxFromIterable(v reflect.Value) (int, error) {
var best reflect.Value
bestIdx := 0
for i := 0; i < v.Len(); i++ {
elem := unwrapInterface(v.Index(i))
if !isOrderedKind(elem.Kind()) {
return 0, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
if i == 0 || greater(elem, best) {
best = elem
bestIdx = i
}
}
return bestIdx, nil
}
// whichMinFromIterable returns the index of the minimum element in a slice/array.
func whichMinFromIterable(v reflect.Value) (int, error) {
var minVal reflect.Value
minIdx := 0
for i := 0; i < v.Len(); i++ {
elem := unwrapInterface(v.Index(i))
if !isOrderedKind(elem.Kind()) {
return 0, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
if i == 0 || less(elem, minVal) {
minVal = elem
minIdx = i
}
}
return minIdx, nil
}
// whichMaxFromMap returns the key associated with the maximum value in a map.
func whichMaxFromMap(v reflect.Value) (interface{}, error) {
var best reflect.Value
var bestKey reflect.Value
first := true
for _, key := range v.MapKeys() {
elem := unwrapInterface(v.MapIndex(key))
if !isOrderedKind(elem.Kind()) {
return nil, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
if first || greater(elem, best) {
best = elem
bestKey = key
first = false
}
}
return bestKey.Interface(), nil
}
// whichMinFromMap returns the key associated with the minimum value in a map.
func whichMinFromMap(v reflect.Value) (interface{}, error) {
var minVal reflect.Value
var minKey reflect.Value
first := true
for _, key := range v.MapKeys() {
elem := unwrapInterface(v.MapIndex(key))
if !isOrderedKind(elem.Kind()) {
return nil, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
if first || less(elem, minVal) {
minVal = elem
minKey = key
first = false
}
}
return minKey.Interface(), nil
}
// WhichMax returns the key (for a map) or index (for a slice/array) of the maximum value.
func WhichMax(data interface{}) (interface{}, error) {
v := reflect.ValueOf(data)
switch v.Kind() {
case reflect.Slice, reflect.Array:
if v.Len() == 0 {
return nil, errors.New("empty slice or array")
}
return whichMaxFromIterable(v)
case reflect.Map:
if v.Len() == 0 {
return nil, errors.New("empty map")
}
return whichMaxFromMap(v)
default:
return nil, fmt.Errorf("unsupported type: %s", v.Kind())
}
}
// WhichMin returns the key (for a map) or index (for a slice/array) of the minimum value.
func WhichMin(data interface{}) (interface{}, error) {
v := reflect.ValueOf(data)
switch v.Kind() {
case reflect.Slice, reflect.Array:
if v.Len() == 0 {
return nil, errors.New("empty slice or array")
}
return whichMinFromIterable(v)
case reflect.Map:
if v.Len() == 0 {
return nil, errors.New("empty map")
}
return whichMinFromMap(v)
default:
return nil, fmt.Errorf("unsupported type: %s", v.Kind())
}
}
func filterMinFromMap(v reflect.Value, minimum interface{}) (interface{}, error) {
minVal := reflect.ValueOf(minimum)
result := reflect.MakeMap(v.Type())
for _, key := range v.MapKeys() {
elem := v.MapIndex(key)
elem := unwrapInterface(v.MapIndex(key))
if !isOrderedKind(elem.Kind()) {
return nil, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
@@ -384,7 +494,7 @@ func filterMaxFromMap(v reflect.Value, maximum interface{}) (interface{}, error)
maxVal := reflect.ValueOf(maximum)
result := reflect.MakeMap(v.Type())
for _, key := range v.MapKeys() {
elem := v.MapIndex(key)
elem := unwrapInterface(v.MapIndex(key))
if !isOrderedKind(elem.Kind()) {
return nil, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
@@ -400,7 +510,7 @@ func maxFromMap(v reflect.Value) (interface{}, error) {
var best reflect.Value
first := true
for _, key := range v.MapKeys() {
elem := v.MapIndex(key)
elem := unwrapInterface(v.MapIndex(key))
if !isOrderedKind(elem.Kind()) {
return nil, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
@@ -417,7 +527,7 @@ func minFromMap(v reflect.Value) (interface{}, error) {
var minVal reflect.Value
first := true
for _, key := range v.MapKeys() {
elem := v.MapIndex(key)
elem := unwrapInterface(v.MapIndex(key))
if !isOrderedKind(elem.Kind()) {
return nil, fmt.Errorf("unsupported element type: %s", elem.Kind())
}
@@ -440,6 +550,16 @@ func isNumericKind(k reflect.Kind) bool {
}
}
// unwrapInterface returns v.Elem() when v holds an interface value, otherwise v unchanged.
// This is necessary when iterating map[string]interface{} or []interface{} via reflection:
// the element Kind is reflect.Interface, not the underlying concrete type.
func unwrapInterface(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Interface {
return v.Elem()
}
return v
}
// isOrderedKind reports whether k supports comparison ordering.
func isOrderedKind(k reflect.Kind) bool {
switch k {
+1 -1
View File
@@ -1 +1 @@
4.4.43
4.4.45