11package controllers
22
33import (
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
7274func 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
8790func 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+ }
0 commit comments