3
\$\begingroup\$

I am a bit concerned about style (functions, variable names, spacings, etc.). I am also not sure about whether I should return error or panic. What do you think?

//SortStructs sorts user-made structs, given that "a" is a pointer to slice of structs
//and key's type (which is name of a field by which struct would be sorted) is one of the basic GO types
//which will be sorted in ascending order when asc is true and the other way around, fields must be exported
func SortStructs(a interface{}, key string, asc bool) error {
    var (
        fName    string
        fPos     int
        valSlice []reflect.Value
    )

    if a == nil {
        return errors.New("mysort: given interface is empty")
    }

    structSlicePointer := reflect.ValueOf(a)
    if structSlicePointer.Kind() != reflect.Ptr {
        return errors.New("mysort: given interface is not pointer")
    } else if structSlicePointer.Elem().Kind() != reflect.Slice {
        return errors.New("mysort: given interface is not pointer to slice")
    }

    //append single structs here
    for i := 0; i < structSlicePointer.Elem().Len(); i++ {
        valSlice = append(valSlice, structSlicePointer.Elem().Index(i))
    }

    if valSlice[0].Kind() != reflect.Struct {
        return errors.New("mysort: interface is not a struct")
    }

    //search for key here
    sl := valSlice[0]
    for ; fPos < sl.NumField(); fPos++ { //range over fields and search for match with key
        fName = sl.Type().Field(fPos).Name
        if fName == key {
            break
        }
    }

    if fPos == sl.NumField() && fName != key {
        return errors.New("mysort: key not found")
    } else if !basicGoType(sl.FieldByName(key)) {
        return errors.New("mysort: key is not a basic go type")
    }

    sortReflect(valSlice, 0, len(valSlice)-1, key, asc)
    return nil
}

func basicGoType(a reflect.Value) bool {
    return a.Kind() == reflect.Bool ||
        a.Kind() == reflect.Int || a.Kind() == reflect.Int8 || a.Kind() == reflect.Int16 || a.Kind() == reflect.Int32 || a.Kind() == reflect.Int64 ||
        a.Kind() == reflect.Uint || a.Kind() == reflect.Uint8 || a.Kind() == reflect.Uint16 || a.Kind() == reflect.Uint32 || a.Kind() == reflect.Uint64 ||
        a.Kind() == reflect.Complex64 || a.Kind() == reflect.Complex128 || a.Kind() == reflect.Float32 || a.Kind() == reflect.Float64 || a.Kind() == reflect.String
}

func aLessThanBReflect(a reflect.Value, b reflect.Value) bool {
    switch a.Kind() {
    case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
        int1 := a.Int()
        int2 := b.Int()
        return int1 < int2
    case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
        uint1 := a.Uint()
        uint2 := b.Uint()
        return uint1 < uint2
    case reflect.Bool:
        bool1 := a.Bool()
        bool2 := b.Bool()
        return !bool1 && bool2
    case reflect.Float32, reflect.Float64:
        float1 := a.Float()
        float2 := b.Float()
        return float1 < float2
    case reflect.Complex64, reflect.Complex128:
        complex1 := a.Complex()
        complex2 := b.Complex()
        if real(complex1) == real(complex2) {
            return imag(complex1) < imag(complex2)
        }
        return real(complex1) < real(complex2)
    case reflect.String:
        str1 := a.String()
        str2 := b.String()
        return str1 < str2
    }
    return false
}

//This is Hoare partition scheme adapted for this code
func partitionReflect(a []reflect.Value, low, high int, key string, asc bool) int {
    pivot := a[int((high+low)/2)]
    low -= 1
    high += 1

    for {
        if asc {
            low += 1
            for a[low].FieldByName(key) != pivot.FieldByName(key) && aLessThanBReflect(a[low].FieldByName(key), pivot.FieldByName(key)) {
                low += 1
            }
            high -= 1
            for a[high].FieldByName(key) != pivot.FieldByName(key) && !aLessThanBReflect(a[high].FieldByName(key), pivot.FieldByName(key)) {
                high -= 1
            }
        } else {
            low += 1
            for a[low].FieldByName(key) != pivot.FieldByName(key) && !aLessThanBReflect(a[low].FieldByName(key), pivot.FieldByName(key)) {
                low += 1
            }
            high -= 1
            for a[high].FieldByName(key) != pivot.FieldByName(key) && aLessThanBReflect(a[high].FieldByName(key), pivot.FieldByName(key)) {
                high -= 1
            }
        }

        if low >= high {
            return high
        }

        //allocate memory for struct and copy a[low]'s value here
        //couldn't do temp := a[low].Interface() because it shared 1 memory adress
        temp := reflect.New(a[low].Type()).Interface()
        temp = a[low].Interface()
        a[low].Set(a[high])
        a[high].Set(reflect.ValueOf(temp))
    }
}

func sortReflect(a []reflect.Value, low, high int, key string, asc bool) {
    if low < high {
        p := partitionReflect(a, low, high, key, asc)
        sortReflect(a, low, p, key, asc)
        sortReflect(a, p+1, high, key, asc)
    }
}
```
\$\endgroup\$
2
  • \$\begingroup\$ Did you write this just for fun to learn reflection, or is it something you intend to use in real code? The use of reflection for this kind of thing is rare and frowned on in Go, because it's error prone (errors happen at runtime, not compile time) and slow. You can just rewrite it with a one-liner call to sort.Slice, like this. \$\endgroup\$ Commented Jun 8, 2021 at 1:30
  • 1
    \$\begingroup\$ I wrote it just for the sake of it, I wanted to implement sorting myself \$\endgroup\$ Commented Jun 8, 2021 at 7:47

0

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.