fix(sorting): 🐛 fix unchanged sorting result
This commit is contained in:
parent
ef9cf48fc1
commit
b53183d884
5 changed files with 358 additions and 103 deletions
|
|
@ -10,6 +10,7 @@ import (
|
|||
"recipe-manager/models"
|
||||
"recipe-manager/services/logger"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
|
@ -17,6 +18,8 @@ import (
|
|||
"reflect"
|
||||
|
||||
"go.uber.org/zap"
|
||||
"golang.org/x/text/collate"
|
||||
"golang.org/x/text/language"
|
||||
)
|
||||
|
||||
type RecipeWithTimeStamps struct {
|
||||
|
|
@ -42,6 +45,133 @@ type DefaultByCountry struct {
|
|||
DefaultFileVersion int
|
||||
}
|
||||
|
||||
// sorting
|
||||
type recipe01Sorter struct {
|
||||
recipe01s []models.Recipe01
|
||||
byKey func(r1, r2 *models.Recipe01) bool
|
||||
}
|
||||
|
||||
// Len implements sort.Interface.
|
||||
func (r *recipe01Sorter) Len() int {
|
||||
return len(r.recipe01s)
|
||||
}
|
||||
|
||||
// Less implements sort.Interface.
|
||||
func (r *recipe01Sorter) Less(i int, j int) bool {
|
||||
return r.byKey(&r.recipe01s[i], &r.recipe01s[j])
|
||||
}
|
||||
|
||||
// Swap implements sort.Interface.
|
||||
func (r *recipe01Sorter) Swap(i int, j int) {
|
||||
r.recipe01s[i], r.recipe01s[j] = r.recipe01s[j], r.recipe01s[i]
|
||||
}
|
||||
|
||||
type ByRecipe01Field func(r1, r2 *models.Recipe01) bool
|
||||
|
||||
func (by ByRecipe01Field) Sort(recipe01s []models.Recipe01) {
|
||||
sorter_inst := &recipe01Sorter{
|
||||
recipe01s: recipe01s,
|
||||
byKey: by,
|
||||
}
|
||||
|
||||
sort.Sort(sorter_inst)
|
||||
// set back to cache
|
||||
|
||||
}
|
||||
|
||||
func SortRecipe01ByFieldName(field string, ascending bool) func(r1, r2 *models.Recipe01) bool {
|
||||
return func(r1, r2 *models.Recipe01) bool {
|
||||
// get value by reflect
|
||||
v1 := reflect.Indirect(reflect.ValueOf(r1)).FieldByName(field)
|
||||
v2 := reflect.Indirect(reflect.ValueOf(r2)).FieldByName(field)
|
||||
|
||||
asAscending := false
|
||||
|
||||
// ensure type is string
|
||||
if v1.Kind() == reflect.String {
|
||||
// check field name
|
||||
if field == "LastChange" {
|
||||
// special case, do date parser
|
||||
// parse date
|
||||
layout := "02-Jan-2006 15:04:05"
|
||||
|
||||
// noLeftLastChange := false
|
||||
// noRightLastChange := false
|
||||
time1, err := time.Parse(layout, v1.String())
|
||||
if err != nil {
|
||||
// noLeftLastChange = true
|
||||
}
|
||||
|
||||
time2, err := time.Parse(layout, v2.String())
|
||||
if err != nil {
|
||||
// noRightLastChange = true
|
||||
}
|
||||
|
||||
asAscending = time1.Before(time2)
|
||||
|
||||
} else {
|
||||
asAscending = string(v1.String()) < string(v2.String())
|
||||
}
|
||||
}
|
||||
|
||||
if ascending {
|
||||
return asAscending
|
||||
}
|
||||
|
||||
// descending
|
||||
return !asAscending
|
||||
}
|
||||
}
|
||||
|
||||
func partition(recipe01s []models.Recipe01, field string, low int, high int, isDate bool) ([]models.Recipe01, int) {
|
||||
pivot := recipe01s[high]
|
||||
|
||||
pivotField := reflect.Indirect(reflect.ValueOf(pivot)).FieldByName(field)
|
||||
|
||||
init := low
|
||||
|
||||
for iter := low; iter < high; iter++ {
|
||||
currentFieldIter := reflect.Indirect(reflect.ValueOf(recipe01s[iter])).FieldByName(field)
|
||||
|
||||
if !isDate {
|
||||
if string(currentFieldIter.String()) < string(pivotField.String()) {
|
||||
recipe01s[init], recipe01s[iter] = recipe01s[iter], recipe01s[init]
|
||||
init++
|
||||
}
|
||||
} else {
|
||||
// parse date
|
||||
layout := "02-Jan-2006 15:04:05"
|
||||
timePivot, _ := time.Parse(layout, pivotField.String())
|
||||
timeIter, _ := time.Parse(layout, currentFieldIter.String())
|
||||
|
||||
if timeIter.Before(timePivot) {
|
||||
recipe01s[init], recipe01s[iter] = recipe01s[iter], recipe01s[init]
|
||||
init++
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
recipe01s[init], recipe01s[high] = recipe01s[high], recipe01s[init]
|
||||
return recipe01s, init
|
||||
}
|
||||
|
||||
func quickSortRecipe01(recipe01s []models.Recipe01, field string, low int, high int) []models.Recipe01 {
|
||||
isDate := false
|
||||
if field == "LastChange" {
|
||||
isDate = true
|
||||
}
|
||||
|
||||
if low < high {
|
||||
var pivotIdx int
|
||||
recipe01s, pivotIdx = partition(recipe01s, field, low, high, isDate)
|
||||
recipe01s = quickSortRecipe01(recipe01s, field, low, pivotIdx-1)
|
||||
recipe01s = quickSortRecipe01(recipe01s, field, pivotIdx+1, high)
|
||||
}
|
||||
|
||||
return recipe01s
|
||||
}
|
||||
|
||||
var (
|
||||
countries = []helpers.CountryName{{
|
||||
CountryID: "tha",
|
||||
|
|
@ -228,12 +358,17 @@ func (d *Data) GetRecipe(countryID, filename string) *models.Recipe {
|
|||
var cached_original_recipe models.Recipe
|
||||
err := d.redisClient.GetKeyTo(filename, &cached_original_recipe)
|
||||
|
||||
// for index, v := range cached_original_recipe.Recipe01 {
|
||||
// fmt.Println(index, " ", v.ProductCode)
|
||||
// }
|
||||
|
||||
if err == nil && d.redisClient.HealthCheck() == nil {
|
||||
d.taoLogger.Log.Debug("GetRecipe.NoReturnUpdated", zap.Any("target", filename))
|
||||
return &cached_original_recipe
|
||||
}
|
||||
|
||||
// if equal, OK
|
||||
d.taoLogger.Log.Debug("GetRecipe.NoReturnUpdatedRedisOffline", zap.Any("target", filename))
|
||||
return d.CurrentRecipe[countryID]
|
||||
}
|
||||
|
||||
|
|
@ -921,114 +1056,213 @@ func (d *Data) GetCountryIDByName(countryName string) (string, error) {
|
|||
|
||||
// ------ sorting ------
|
||||
// FIXME: sorting not working
|
||||
func (d *Data) SortRecipe(countryID, filename string, sort_by string) (error, []string) {
|
||||
func (d *Data) SortRecipe(countryID, filename string, sort_by string, ascending bool) {
|
||||
// Get recipe
|
||||
recipe := d.GetRecipe(countryID, filename)
|
||||
|
||||
// error code
|
||||
errorCode := 0
|
||||
emptiedComparators := make([]string, 0)
|
||||
// define default language priority
|
||||
collator := collate.New(language.Thai)
|
||||
|
||||
// Sort
|
||||
switch sort_by {
|
||||
case "Product Code":
|
||||
slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int {
|
||||
|
||||
if a.ProductCode == "" || b.ProductCode == "" {
|
||||
errorCode = 1
|
||||
emptiedComparators = append(emptiedComparators, a.ProductCode+" !compare! "+b.ProductCode)
|
||||
slices.SortStableFunc(recipe.Recipe01, func(a, b models.Recipe01) int {
|
||||
switch sort_by {
|
||||
case "Name":
|
||||
av := reflect.Indirect(reflect.ValueOf(a)).FieldByName("Name")
|
||||
bv := reflect.Indirect(reflect.ValueOf(b)).FieldByName("Name")
|
||||
// add collator
|
||||
if !ascending {
|
||||
return collator.CompareString(bv.String(), av.String())
|
||||
}
|
||||
|
||||
return strings.Compare(a.ProductCode, b.ProductCode)
|
||||
})
|
||||
case "Name":
|
||||
slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int {
|
||||
|
||||
if a.Name == "" || b.Name == "" {
|
||||
errorCode = 2
|
||||
emptiedComparators = append(emptiedComparators, a.Name+" !compare! "+b.Name)
|
||||
return collator.CompareString(av.String(), bv.String())
|
||||
case "Other Name":
|
||||
av := reflect.Indirect(reflect.ValueOf(a)).FieldByName("OtherName")
|
||||
bv := reflect.Indirect(reflect.ValueOf(b)).FieldByName("OtherName")
|
||||
// add collator
|
||||
if !ascending {
|
||||
return collator.CompareString(bv.String(), av.String())
|
||||
}
|
||||
|
||||
return strings.Compare(a.Name, b.Name)
|
||||
})
|
||||
case "Other Name":
|
||||
slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int {
|
||||
|
||||
if a.OtherName == "" || b.OtherName == "" {
|
||||
errorCode = 3
|
||||
emptiedComparators = append(emptiedComparators, a.OtherName+" !compare! "+b.OtherName)
|
||||
return collator.CompareString(av.String(), bv.String())
|
||||
case "Description":
|
||||
av := reflect.Indirect(reflect.ValueOf(a)).FieldByName("Description")
|
||||
bv := reflect.Indirect(reflect.ValueOf(b)).FieldByName("Description")
|
||||
// add collator
|
||||
if !ascending {
|
||||
return collator.CompareString(bv.String(), av.String())
|
||||
}
|
||||
|
||||
return strings.Compare(a.OtherName, b.OtherName)
|
||||
})
|
||||
case "Description":
|
||||
slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int {
|
||||
|
||||
if a.Description == "" || b.Description == "" {
|
||||
errorCode = 4
|
||||
emptiedComparators = append(emptiedComparators, a.Description+" !compare! "+b.Description)
|
||||
return collator.CompareString(av.String(), bv.String())
|
||||
case "Other Description":
|
||||
av := reflect.Indirect(reflect.ValueOf(a)).FieldByName("OtherDescription")
|
||||
bv := reflect.Indirect(reflect.ValueOf(b)).FieldByName("OtherDescription")
|
||||
// add collator
|
||||
if !ascending {
|
||||
return collator.CompareString(bv.String(), av.String())
|
||||
}
|
||||
return collator.CompareString(av.String(), bv.String())
|
||||
case "Product Code":
|
||||
av := reflect.Indirect(reflect.ValueOf(a)).FieldByName("ProductCode")
|
||||
bv := reflect.Indirect(reflect.ValueOf(b)).FieldByName("ProductCode")
|
||||
|
||||
return strings.Compare(a.Description, b.Description)
|
||||
})
|
||||
case "Other Description":
|
||||
slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int {
|
||||
// // split by '-'
|
||||
// codes_a := strings.Split(av.String(), "-")
|
||||
// codes_b := strings.Split(bv.String(), "-")
|
||||
|
||||
if a.OtherDescription == "" || b.OtherDescription == "" {
|
||||
errorCode = 5
|
||||
emptiedComparators = append(emptiedComparators, a.OtherDescription+" !compare! "+b.OtherDescription)
|
||||
}
|
||||
// // ensure productCode len
|
||||
// if len(codes_a) == len(codes_b) {
|
||||
// for iter := 0; iter > len(codes_a)-1; iter++ {
|
||||
// num_a, _ := strconv.Atoi(codes_a[iter])
|
||||
// num_b, _ := strconv.Atoi(codes_b[iter])
|
||||
|
||||
return strings.Compare(a.OtherDescription, b.OtherDescription)
|
||||
})
|
||||
case "Last Updated":
|
||||
slices.SortFunc(recipe.Recipe01, func(a, b models.Recipe01) int {
|
||||
// parse date
|
||||
layout := "02-Jan-2006 15:04:05"
|
||||
// if num_a < num_b {
|
||||
// if !ascending {
|
||||
// return 1
|
||||
// }
|
||||
// return -1
|
||||
// } else if num_b < num_a {
|
||||
// if !ascending {
|
||||
// return -1
|
||||
// }
|
||||
// return 1
|
||||
// } else {
|
||||
// continue
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
prea := strings.ReplaceAll(av.String(), "-", "")
|
||||
preb := strings.ReplaceAll(bv.String(), "-", "")
|
||||
|
||||
if a.LastChange == "" || b.LastChange == "" {
|
||||
errorCode = 6
|
||||
emptiedComparators = append(emptiedComparators, a.ProductCode+":"+a.LastChange+" !compare! "+b.ProductCode+":"+b.LastChange)
|
||||
}
|
||||
inta, _ := strconv.Atoi(prea)
|
||||
intb, _ := strconv.Atoi(preb)
|
||||
|
||||
timeA, err := time.Parse(layout, a.LastChange)
|
||||
if err != nil {
|
||||
// fmt.Println("Parse error! not in layout format: ", a.LastChange)
|
||||
|
||||
errorCode = 7
|
||||
emptiedComparators = append(emptiedComparators, a.ProductCode+":"+a.LastChange)
|
||||
}
|
||||
|
||||
timeB, err := time.Parse(layout, b.LastChange)
|
||||
if err != nil {
|
||||
// fmt.Println("Parse error! not in layout format: ", b.LastChange)
|
||||
|
||||
errorCode = 8
|
||||
emptiedComparators = append(emptiedComparators, b.ProductCode+":"+b.LastChange)
|
||||
}
|
||||
|
||||
if a.LastChange == "" && b.LastChange != "" {
|
||||
errorCode = 0
|
||||
return 1
|
||||
} else if a.LastChange != "" && b.LastChange == "" {
|
||||
errorCode = 0
|
||||
if inta < intb {
|
||||
if !ascending {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
} else if a.LastChange == "" && b.LastChange == "" {
|
||||
errorCode = 0
|
||||
} else if inta > intb {
|
||||
if !ascending {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
|
||||
return timeA.Compare(timeB)
|
||||
})
|
||||
case "Last Updated":
|
||||
av := reflect.Indirect(reflect.ValueOf(a)).FieldByName("LastChange")
|
||||
bv := reflect.Indirect(reflect.ValueOf(b)).FieldByName("LastChange")
|
||||
|
||||
layout := "02-Jan-2006 15:04:05"
|
||||
|
||||
timeA, _ := time.Parse(layout, av.String())
|
||||
timeB, _ := time.Parse(layout, bv.String())
|
||||
|
||||
compare_result := 0
|
||||
|
||||
if !ascending {
|
||||
compare_result = timeB.Compare(timeA)
|
||||
} else {
|
||||
compare_result = timeA.Compare(timeB)
|
||||
}
|
||||
|
||||
if compare_result == 0 {
|
||||
// has same time, compare product code
|
||||
av2 := reflect.Indirect(reflect.ValueOf(a)).FieldByName("ProductCode")
|
||||
bv2 := reflect.Indirect(reflect.ValueOf(b)).FieldByName("ProductCode")
|
||||
prea := strings.ReplaceAll(av2.String(), "-", "")
|
||||
preb := strings.ReplaceAll(bv2.String(), "-", "")
|
||||
inta, _ := strconv.Atoi(prea)
|
||||
intb, _ := strconv.Atoi(preb)
|
||||
|
||||
if inta < intb {
|
||||
if !ascending {
|
||||
return 1
|
||||
}
|
||||
return -1
|
||||
} else if inta > intb {
|
||||
if !ascending {
|
||||
return -1
|
||||
}
|
||||
return 1
|
||||
} else {
|
||||
return 0
|
||||
}
|
||||
} else {
|
||||
|
||||
return compare_result
|
||||
}
|
||||
}
|
||||
|
||||
// no switching order
|
||||
return 0
|
||||
})
|
||||
|
||||
// Clean or re-sort
|
||||
switch sort_by {
|
||||
case "Product Code":
|
||||
// ByRecipe01Field(SortRecipe01ByFieldName("ProductCode", ascending)).Sort(recipe.Recipe01)
|
||||
// recipe.Recipe01 = quickSortRecipe01(recipe.Recipe01, "ProductCode", 0, len(recipe.Recipe01)-1)
|
||||
|
||||
// collect product code and print
|
||||
// for _, r := range recipe.Recipe01 {
|
||||
// fmt.Println(r.ProductCode)
|
||||
// }
|
||||
|
||||
// test sample batch size 10
|
||||
result := quickSortRecipe01(recipe.Recipe01, "ProductCode", 0, 10)
|
||||
// for index, r := range recipe.Recipe01 {
|
||||
// fmt.Println(index, " ", r.ProductCode)
|
||||
// }
|
||||
|
||||
// reassign
|
||||
if len(result) == len(recipe.Recipe01) {
|
||||
copy(recipe.Recipe01, result)
|
||||
}
|
||||
|
||||
case "Name":
|
||||
// ByRecipe01Field(SortRecipe01ByFieldName("Name", ascending)).Sort(recipe.Recipe01)
|
||||
// recipe.Recipe01 = quickSortRecipe01(recipe.Recipe01, "Name", 0, len(recipe.Recipe01)-1)
|
||||
case "Other Name":
|
||||
// ByRecipe01Field(SortRecipe01ByFieldName("OtherName", ascending)).Sort(recipe.Recipe01)
|
||||
// recipe.Recipe01 = quickSortRecipe01(recipe.Recipe01, "OtherName", 0, len(recipe.Recipe01)-1)
|
||||
case "Description":
|
||||
// ByRecipe01Field(SortRecipe01ByFieldName("Description", ascending)).Sort(recipe.Recipe01)
|
||||
// recipe.Recipe01 = quickSortRecipe01(recipe.Recipe01, "Description", 0, len(recipe.Recipe01)-1)
|
||||
case "Other Description":
|
||||
// ByRecipe01Field(SortRecipe01ByFieldName("OtherDecsription", ascending)).Sort(recipe.Recipe01)
|
||||
// recipe.Recipe01 = quickSortRecipe01(recipe.Recipe01, "OtherDecsription", 0, len(recipe.Recipe01)-1)
|
||||
case "Last Updated":
|
||||
// cleaning up empty last change
|
||||
|
||||
// for index, v := range recipe.Recipe01 {
|
||||
// // save value temp
|
||||
// fmt.Println("checking lastchange ", v.ProductCode, " ='", v.LastChange, "'")
|
||||
// if v.LastChange == "" || len(v.LastChange) == 0 {
|
||||
// fmt.Println("Shift @ ", index, " = ", v.ProductCode)
|
||||
// temp := recipe.Recipe01[index]
|
||||
// // copy(recipe.Recipe01[index:], recipe.Recipe01[index+1:])
|
||||
// recipe.Recipe01 = append(recipe.Recipe01[:index], recipe.Recipe01[index+1:]...)
|
||||
// recipe.Recipe01 = append(recipe.Recipe01, temp)
|
||||
// }
|
||||
// }
|
||||
|
||||
no_zero_idx := 0
|
||||
for i := 0; i < len(recipe.Recipe01); i++ {
|
||||
if recipe.Recipe01[i].LastChange != "" {
|
||||
recipe.Recipe01[no_zero_idx], recipe.Recipe01[i] = recipe.Recipe01[i], recipe.Recipe01[no_zero_idx]
|
||||
no_zero_idx++
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errorCode != 0 {
|
||||
errStatus := fmt.Errorf("ERR[%v]", errorCode)
|
||||
fmt.Println(errStatus)
|
||||
return errStatus, emptiedComparators
|
||||
// set to cache after sorted
|
||||
// case redis active
|
||||
if d.redisClient.HealthCheck() == nil {
|
||||
fmt.Println("set to redis", filename)
|
||||
d.redisClient.SetToKey(filename, recipe)
|
||||
}
|
||||
fmt.Println("set to server mem")
|
||||
|
||||
d.CurrentRecipe[countryID] = recipe
|
||||
|
||||
return nil, emptiedComparators
|
||||
}
|
||||
|
||||
// merge
|
||||
|
|
|
|||
|
|
@ -842,22 +842,20 @@ func (rr *RecipeRouter) sortRecipe(w http.ResponseWriter, r *http.Request) {
|
|||
}
|
||||
|
||||
sortKey := sortConfig["sortKey"].(string)
|
||||
ascending := sortConfig["ascending"].(bool)
|
||||
|
||||
// store cache
|
||||
rr.cache_db.SetKeyTimeout(country+"_"+filename+"_latestSortKey", sortKey, 3600)
|
||||
rr.cache_db.SetKeyTimeout(country+"_"+filename+"_"+sortKey, ascending, 3600)
|
||||
|
||||
// sort recipe
|
||||
err, emptiedComparators := rr.data.SortRecipe(country, filename, sortKey)
|
||||
|
||||
if err != nil {
|
||||
|
||||
rr.taoLogger.Log.Error("RecipeRouter.sortRecipe", zap.Error(err), zap.Any("emptiedComparators", emptiedComparators))
|
||||
|
||||
http.Error(w, err.Error(), http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
rr.data.SortRecipe(country, filename, sortKey, ascending)
|
||||
|
||||
w.Header().Add("Content-Type", "application/json")
|
||||
json.NewEncoder(w).Encode(map[string]interface{}{
|
||||
"status": "OK",
|
||||
"key": sortKey,
|
||||
"result": rr.data.GetRecipe(country, filename).Recipe01,
|
||||
})
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@ import (
|
|||
"recipe-manager/models"
|
||||
"recipe-manager/services/logger"
|
||||
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"go.uber.org/zap"
|
||||
|
|
@ -302,7 +301,6 @@ func (rs *recipeService) GetRecipeOverview(request *contracts.RecipeOverviewRequ
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
result.TotalCount = len(result.Result)
|
||||
|
|
@ -310,9 +308,13 @@ func (rs *recipeService) GetRecipeOverview(request *contracts.RecipeOverviewRequ
|
|||
result.HasMore = result.TotalCount >= request.Take+request.Skip
|
||||
if result.HasMore {
|
||||
result.Result = result.Result[request.Skip : request.Take+request.Skip]
|
||||
sort.Slice(result.Result, func(i, j int) bool {
|
||||
return result.Result[i].ID < result.Result[j].ID
|
||||
})
|
||||
// sort.Slice(result.Result, func(i, j int) bool {
|
||||
// return result.Result[i].ID < result.Result[j].ID
|
||||
// })
|
||||
|
||||
// do sorting
|
||||
|
||||
|
||||
} else if result.TotalCount > request.Skip {
|
||||
result.Result = result.Result[request.Skip:]
|
||||
} else {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue