Skip to content

Commit 193b09d

Browse files
committed
Create wait-groups for better performance;
Preference to raw query's for performance;
1 parent 80abd7d commit 193b09d

File tree

3 files changed

+178
-134
lines changed

3 files changed

+178
-134
lines changed

controllers/controller.go

Lines changed: 132 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,13 @@
11
package controllers
22

33
import (
4+
"errors"
45
"github.com/Darklabel91/API_Names/database"
6+
"github.com/Darklabel91/API_Names/metaphone"
57
"github.com/Darklabel91/API_Names/models"
68
"github.com/gin-gonic/gin"
79
"net/http"
10+
"sort"
811
"strings"
912
)
1013

@@ -22,8 +25,8 @@ func CreateName(c *gin.Context) {
2225
c.JSON(http.StatusOK, name)
2326
}
2427

25-
//SearchNameByID read name by id
26-
func SearchNameByID(c *gin.Context) {
28+
//GetID read name by id
29+
func GetID(c *gin.Context) {
2730
var name models.NameType
2831

2932
id := c.Params.ByName("id")
@@ -65,31 +68,34 @@ func UpdateName(c *gin.Context) {
6568

6669
database.Db.Model(&name).UpdateColumns(name)
6770
c.JSON(http.StatusOK, name)
68-
6971
}
7072

7173
//GetName read name by name
7274
func GetName(c *gin.Context) {
7375
var name models.NameType
7476

7577
n := c.Params.ByName("name")
76-
database.Db.Where("name = ?", strings.ToUpper(n)).Find(&name)
78+
database.Db.Raw("select * from name_types where name = ?", strings.ToUpper(n)).Find(&name)
7779

7880
if name.ID == 0 {
7981
c.JSON(http.StatusNotFound, gin.H{"Not found": "name not found"})
8082
return
8183
}
8284

8385
c.JSON(http.StatusOK, name)
86+
return
8487
}
8588

8689
//SearchSimilarNames search for all similar names by metaphone and Levenshtein method
8790
func SearchSimilarNames(c *gin.Context) {
88-
var names []models.NameType
89-
9091
//Name to be searched
9192
name := c.Params.ByName("name")
92-
database.Db.Find(&names)
93+
94+
var names []models.NameType
95+
database.Db.Raw("select * from name_types").Find(&names)
96+
97+
var canonicalEntity models.NameType
98+
database.Db.Raw("select * from name_types where name = ?", strings.ToUpper(name)).Find(&canonicalEntity)
9399

94100
similarNames, mtf := findSimilarNames(names, name, levenshtein)
95101

@@ -109,8 +115,17 @@ func SearchSimilarNames(c *gin.Context) {
109115
//order all similar names from high to low Levenshtein
110116
nameV := orderByLevenshtein(similarNames)
111117

112-
//build canonical return
113-
canonicalEntity := findCanonical(name, nameV)
118+
//build canonical
119+
if canonicalEntity.ID == 0 {
120+
ce, err := findCanonical(nameV)
121+
if err != nil {
122+
c.JSON(http.StatusNotFound, gin.H{"Not found": err.Error(), "metaphone": mtf})
123+
return
124+
}
125+
canonicalEntity = ce
126+
}
127+
128+
//return
114129
r := models.MetaphoneR{
115130
ID: canonicalEntity.ID,
116131
CreatedAt: canonicalEntity.CreatedAt,
@@ -121,7 +136,113 @@ func SearchSimilarNames(c *gin.Context) {
121136
Metaphone: canonicalEntity.Metaphone,
122137
NameVariations: nameV,
123138
}
124-
125-
//return
126139
c.JSON(200, r)
127140
}
141+
142+
/*-------ALL BELLOW USED ONLY ON searchSimilarNames-------*/
143+
144+
//findCanonical search for every similar name on the database returning the first matched name
145+
func findCanonical(similarNames []string) (models.NameType, error) {
146+
var canonicalEntity models.NameType
147+
148+
for _, similarName := range similarNames {
149+
database.Db.Raw("select * from name_types where name = ?", strings.ToUpper(similarName)).Find(&canonicalEntity)
150+
if canonicalEntity.ID != 0 {
151+
return canonicalEntity, nil
152+
}
153+
}
154+
155+
return models.NameType{}, errors.New("couldn't find canonical name")
156+
}
157+
158+
//findSimilarNames returns []models.NameVar and if necessary reduces' threshold to a minimum of 0.5
159+
func findSimilarNames(names []models.NameType, name string, threshold float32) ([]models.NameVar, string) {
160+
similarNames, mtf := findNames(names, name, threshold)
161+
162+
//in case of empty return the levenshtein constant is downgraded to the minimum of 0.5
163+
if len(similarNames) == 0 {
164+
similarNames, _ = findNames(names, name, threshold-0.1)
165+
if len(similarNames) == 0 {
166+
similarNames, _ = findNames(names, name, threshold-0.2)
167+
}
168+
if len(similarNames) == 0 {
169+
similarNames, _ = findNames(names, name, threshold-0.3)
170+
}
171+
}
172+
173+
return similarNames, mtf
174+
}
175+
176+
//findNames return []models.NameVar with all similar names and the metaphone code of searched string, called on findSimilarNames
177+
func findNames(names []models.NameType, name string, threshold float32) ([]models.NameVar, string) {
178+
var similarNames []models.NameVar
179+
180+
mtf := metaphone.Pack(name)
181+
for _, n := range names {
182+
if metaphone.IsMetaphoneSimilar(mtf, n.Metaphone) {
183+
similarity := metaphone.SimilarityBetweenWords(strings.ToLower(name), strings.ToLower(n.Name))
184+
if similarity >= threshold {
185+
similarNames = append(similarNames, models.NameVar{Name: n.Name, Levenshtein: similarity})
186+
varWords := strings.Split(n.NameVariations, "|")
187+
for _, vw := range varWords {
188+
if vw != "" {
189+
similarNames = append(similarNames, models.NameVar{Name: vw, Levenshtein: similarity})
190+
}
191+
}
192+
}
193+
194+
}
195+
}
196+
197+
return similarNames, mtf
198+
199+
}
200+
201+
//orderByLevenshtein used to sort an array by Levenshtein and len of the name
202+
func orderByLevenshtein(arr []models.NameVar) []string {
203+
// creates copy of original array
204+
sortedArr := make([]models.NameVar, len(arr))
205+
copy(sortedArr, arr)
206+
207+
// order by func
208+
sort.Slice(sortedArr, func(i, j int) bool {
209+
if sortedArr[i].Levenshtein != sortedArr[j].Levenshtein {
210+
return sortedArr[i].Levenshtein > sortedArr[j].Levenshtein
211+
} else {
212+
return len(sortedArr[i].Name) < len(sortedArr[j].Name)
213+
}
214+
})
215+
216+
//return array
217+
var retArr []string
218+
for _, lv := range sortedArr {
219+
retArr = append(retArr, lv.Name)
220+
}
221+
222+
//return without duplicates
223+
return removeDuplicates(retArr)
224+
}
225+
226+
//removeDuplicates remove duplicates of []string, called on orderByLevenshtein
227+
func removeDuplicates(arr []string) []string {
228+
var cleanArr []string
229+
230+
for _, a := range arr {
231+
if !contains(cleanArr, a) {
232+
cleanArr = append(cleanArr, a)
233+
}
234+
}
235+
236+
return cleanArr
237+
}
238+
239+
//contains verifies if []string already has a specific string, called on removeDuplicates
240+
func contains(s []string, str string) bool {
241+
for _, v := range s {
242+
if v == str {
243+
return true
244+
}
245+
}
246+
247+
return false
248+
}

controllers/searchSimilarnames_func.go

Lines changed: 0 additions & 120 deletions
This file was deleted.

routes/routes.go

Lines changed: 46 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package routes
33
import (
44
"github.com/Darklabel91/API_Names/controllers"
55
"github.com/gin-gonic/gin"
6+
"sync"
67
)
78

89
const door = ":8080"
@@ -12,12 +13,54 @@ func HandleRequests() {
1213
r.POST("/name", controllers.CreateName)
1314
r.DELETE("/:id", controllers.DeleteName)
1415
r.PATCH("/:id", controllers.UpdateName)
15-
r.GET("/:id", controllers.SearchNameByID)
16-
r.GET("/name/:name", controllers.GetName)
17-
r.GET("/metaphone/:name", controllers.SearchSimilarNames)
16+
r.GET("/:id", waitGroupID)
17+
r.GET("/name/:name", waitGroupName)
18+
r.GET("/metaphone/:name", waitGroupMetaphone)
1819

1920
err := r.Run(door)
2021
if err != nil {
2122
panic(err)
2223
}
2324
}
25+
26+
//waitGroupMetaphone crates a waiting group for handling requests using controllers.SearchSimilarNames
27+
func waitGroupMetaphone(c *gin.Context) {
28+
var wg sync.WaitGroup
29+
wg.Add(1)
30+
31+
// Handle the request in a separate goroutine
32+
go func() {
33+
defer wg.Done()
34+
controllers.SearchSimilarNames(c)
35+
}()
36+
37+
wg.Wait()
38+
}
39+
40+
//waitGroupMetaphone crates a waiting group for handling requests using controllers.GetName
41+
func waitGroupName(c *gin.Context) {
42+
var wg sync.WaitGroup
43+
wg.Add(1)
44+
45+
// Handle the request in a separate goroutine
46+
go func() {
47+
defer wg.Done()
48+
controllers.GetName(c)
49+
}()
50+
51+
wg.Wait()
52+
}
53+
54+
//waitGroupMetaphone crates a waiting group for handling requests using controllers.GetID
55+
func waitGroupID(c *gin.Context) {
56+
var wg sync.WaitGroup
57+
wg.Add(1)
58+
59+
// Handle the request in a separate goroutine
60+
go func() {
61+
defer wg.Done()
62+
controllers.GetID(c)
63+
}()
64+
65+
wg.Wait()
66+
}

0 commit comments

Comments
 (0)