Skip to content

Commit 83f80a5

Browse files
committed
better return JSON with canonical name returned
1 parent 2fb2cb4 commit 83f80a5

File tree

4 files changed

+192
-109
lines changed

4 files changed

+192
-109
lines changed

README.md

Lines changed: 40 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -56,22 +56,52 @@ Every method expect Status:200 and JSON content-type as show bellow:
5656
- GET - /metaphone/:name
5757
```json
5858
{
59+
"ID": 3,
60+
"CreatedAt": "0001-01-01T00:00:00Z",
61+
"UpdatedAt": "0001-01-01T00:00:00Z",
62+
"DeletedAt": null,
5963
"Name": "ARON",
60-
"metaphone": "ARM",
61-
"nameVariations": [
64+
"Classification": "M",
65+
"Metaphone": "ARM",
66+
"NameVariations": [
67+
"ARON",
6268
"AARON",
6369
"AHARON",
64-
"ARION",
65-
"ARION",
66-
"ARNOM",
67-
"ARNON",
68-
"ARNON",
6970
"AROM",
70-
"ARON",
71-
"ARON",
7271
"ARYON",
72+
"HARON",
7373
"HARNON",
74-
"HARON"
74+
"AIROM",
75+
"AIRON",
76+
"AIRYON",
77+
"AYRON",
78+
"HAIRON",
79+
"HAYRON",
80+
"IARON",
81+
"YARON",
82+
"ARLON",
83+
"ARILON",
84+
"ARLOM",
85+
"HARLON",
86+
"ARION",
87+
"ARNON",
88+
"ARNOM",
89+
"ARONE",
90+
"ARONI",
91+
"ARONY",
92+
"ARTON",
93+
"JARON",
94+
"JAROM",
95+
"KARON",
96+
"CARON",
97+
"MARON",
98+
"MAROM",
99+
"MARRON",
100+
"MARYON",
101+
"NARON",
102+
"RARON",
103+
"SARON",
104+
"SAROM"
75105
]
76106
}
77107
```

controllers/controller.go

Lines changed: 17 additions & 98 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,9 @@ package controllers
22

33
import (
44
"github.com/Darklabel91/API_Names/database"
5-
"github.com/Darklabel91/API_Names/metaphone"
65
"github.com/Darklabel91/API_Names/models"
76
"github.com/gin-gonic/gin"
87
"net/http"
9-
"sort"
108
"strings"
119
)
1210

@@ -85,7 +83,7 @@ func GetName(c *gin.Context) {
8583
c.JSON(http.StatusOK, name)
8684
}
8785

88-
//SearchSimilarNames search for all similar names by metaphone and levenshtein method
86+
//SearchSimilarNames search for all similar names by metaphone and Levenshtein method
8987
func SearchSimilarNames(c *gin.Context) {
9088
var names []models.NameType
9189

@@ -95,7 +93,8 @@ func SearchSimilarNames(c *gin.Context) {
9593

9694
similarNames, mtf := findSimilarNames(names, name, levenshtein)
9795

98-
if len(names) == 0 {
96+
//in case of failure in find a metaphone conde we return status not found
97+
if len(names) == 0 || len(similarNames) == 0 {
9998
c.JSON(http.StatusNotFound, gin.H{"Not found": "metaphone not found", "metaphone": mtf})
10099
return
101100
}
@@ -107,102 +106,22 @@ func SearchSimilarNames(c *gin.Context) {
107106
}
108107
}
109108

109+
//order all similar names from high to low Levenshtein
110110
nameV := orderByLevenshtein(similarNames)
111111

112-
c.JSON(200, gin.H{
113-
"Name": strings.ToUpper(name),
114-
"metaphone": mtf,
115-
"nameVariations": nameV,
116-
})
117-
118-
}
119-
120-
//findSimilarNames returns []models.NameVar and if necessary reduces' threshold to a minimum of 0.5
121-
func findSimilarNames(names []models.NameType, name string, threshold float32) ([]models.NameVar, string) {
122-
similarNames, mtf := findNames(names, name, threshold)
123-
124-
//in case of empty return the levenshtein constant is downgraded to the minimum of 0.5
125-
if len(similarNames) == 0 {
126-
similarNames, _ = findNames(names, name, threshold-0.1)
127-
if len(similarNames) == 0 {
128-
similarNames, _ = findNames(names, name, threshold-0.2)
129-
}
130-
if len(similarNames) == 0 {
131-
similarNames, _ = findNames(names, name, threshold-0.3)
132-
}
133-
}
134-
135-
return similarNames, mtf
136-
}
137-
138-
//findNames return []models.NameVar with all similar names and the metaphone code of searched string, called on findSimilarNames
139-
func findNames(names []models.NameType, name string, threshold float32) ([]models.NameVar, string) {
140-
var similarNames []models.NameVar
141-
142-
mtf := metaphone.Pack(name)
143-
for _, n := range names {
144-
if metaphone.IsMetaphoneSimilar(mtf, n.Metaphone) {
145-
similarity := metaphone.SimilarityBetweenWords(strings.ToLower(name), strings.ToLower(n.Name))
146-
if similarity >= threshold {
147-
similarNames = append(similarNames, models.NameVar{Name: n.Name, Levenshtein: similarity})
148-
varWords := strings.Split(n.NameVariations, "|")
149-
for _, vw := range varWords {
150-
if vw != "" {
151-
similarNames = append(similarNames, models.NameVar{Name: vw, Levenshtein: similarity})
152-
}
153-
}
154-
}
155-
156-
}
157-
}
158-
159-
return similarNames, mtf
160-
161-
}
162-
163-
//orderByLevenshtein used to sort an array by Levenshtein
164-
func orderByLevenshtein(arr []models.NameVar) []string {
165-
// creates copy of original array
166-
sortedArr := make([]models.NameVar, len(arr))
167-
copy(sortedArr, arr)
168-
169-
// compilation func
170-
cmp := func(i, j int) bool {
171-
return sortedArr[i].Levenshtein > sortedArr[j].Levenshtein
172-
}
173-
174-
// order by func
175-
sort.Slice(sortedArr, cmp)
176-
177-
var retArry []string
178-
for _, lv := range sortedArr {
179-
retArry = append(retArry, lv.Name)
180-
}
181-
182-
return removeDuplicates(retArry)
183-
184-
}
185-
186-
//removeDuplicates remove duplicates of []string, called on orderByLevenshtein
187-
func removeDuplicates(arr []string) []string {
188-
var cleanArr []string
189-
190-
for _, a := range arr {
191-
if !contains(cleanArr, a) {
192-
cleanArr = append(cleanArr, a)
193-
}
194-
}
195-
196-
return cleanArr
197-
}
198-
199-
//contains verifies if []string already has a specific string, called on removeDuplicates
200-
func contains(s []string, str string) bool {
201-
for _, v := range s {
202-
if v == str {
203-
return true
204-
}
112+
//build canonical return
113+
canonicalEntity := findCanonical(name, nameV)
114+
r := models.MetaphoneR{
115+
ID: canonicalEntity.ID,
116+
CreatedAt: canonicalEntity.CreatedAt,
117+
UpdatedAt: canonicalEntity.UpdatedAt,
118+
DeletedAt: canonicalEntity.DeletedAt,
119+
Name: canonicalEntity.Name,
120+
Classification: canonicalEntity.Classification,
121+
Metaphone: canonicalEntity.Metaphone,
122+
NameVariations: nameV,
205123
}
206124

207-
return false
125+
//return
126+
c.JSON(200, r)
208127
}
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
package controllers
2+
3+
import (
4+
"github.com/Darklabel91/API_Names/database"
5+
"github.com/Darklabel91/API_Names/metaphone"
6+
"github.com/Darklabel91/API_Names/models"
7+
"sort"
8+
"strings"
9+
)
10+
11+
//findCanonical search for every similar name on the database returning the first matched name
12+
func findCanonical(searchName string, similarNames []string) models.NameType {
13+
var name models.NameType
14+
15+
database.Db.Where("name = ?", strings.ToUpper(searchName)).Find(&name)
16+
if name.ID != 0 {
17+
return name
18+
}
19+
20+
for _, similarName := range similarNames {
21+
database.Db.Where("name = ?", strings.ToUpper(similarName)).Find(&name)
22+
if name.ID != 0 {
23+
return name
24+
}
25+
}
26+
27+
return name
28+
}
29+
30+
//findSimilarNames returns []models.NameVar and if necessary reduces' threshold to a minimum of 0.5
31+
func findSimilarNames(names []models.NameType, name string, threshold float32) ([]models.NameVar, string) {
32+
similarNames, mtf := findNames(names, name, threshold)
33+
34+
//in case of empty return the levenshtein constant is downgraded to the minimum of 0.5
35+
if len(similarNames) == 0 {
36+
similarNames, _ = findNames(names, name, threshold-0.1)
37+
if len(similarNames) == 0 {
38+
similarNames, _ = findNames(names, name, threshold-0.2)
39+
}
40+
if len(similarNames) == 0 {
41+
similarNames, _ = findNames(names, name, threshold-0.3)
42+
}
43+
}
44+
45+
return similarNames, mtf
46+
}
47+
48+
//findNames return []models.NameVar with all similar names and the metaphone code of searched string, called on findSimilarNames
49+
func findNames(names []models.NameType, name string, threshold float32) ([]models.NameVar, string) {
50+
var similarNames []models.NameVar
51+
52+
mtf := metaphone.Pack(name)
53+
for _, n := range names {
54+
if metaphone.IsMetaphoneSimilar(mtf, n.Metaphone) {
55+
similarity := metaphone.SimilarityBetweenWords(strings.ToLower(name), strings.ToLower(n.Name))
56+
if similarity >= threshold {
57+
similarNames = append(similarNames, models.NameVar{Name: n.Name, Levenshtein: similarity})
58+
varWords := strings.Split(n.NameVariations, "|")
59+
for _, vw := range varWords {
60+
if vw != "" {
61+
similarNames = append(similarNames, models.NameVar{Name: vw, Levenshtein: similarity})
62+
}
63+
}
64+
}
65+
66+
}
67+
}
68+
69+
return similarNames, mtf
70+
71+
}
72+
73+
//orderByLevenshtein used to sort an array by Levenshtein
74+
func orderByLevenshtein(arr []models.NameVar) []string {
75+
// creates copy of original array
76+
sortedArr := make([]models.NameVar, len(arr))
77+
copy(sortedArr, arr)
78+
79+
// compilation func
80+
cmp := func(i, j int) bool {
81+
return sortedArr[i].Levenshtein > sortedArr[j].Levenshtein
82+
}
83+
84+
// order by func
85+
sort.Slice(sortedArr, cmp)
86+
87+
var retArry []string
88+
for _, lv := range sortedArr {
89+
retArry = append(retArry, lv.Name)
90+
}
91+
92+
return removeDuplicates(retArry)
93+
94+
}
95+
96+
//removeDuplicates remove duplicates of []string, called on orderByLevenshtein
97+
func removeDuplicates(arr []string) []string {
98+
var cleanArr []string
99+
100+
for _, a := range arr {
101+
if !contains(cleanArr, a) {
102+
cleanArr = append(cleanArr, a)
103+
}
104+
}
105+
106+
return cleanArr
107+
}
108+
109+
//contains verifies if []string already has a specific string, called on removeDuplicates
110+
func contains(s []string, str string) bool {
111+
for _, v := range s {
112+
if v == str {
113+
return true
114+
}
115+
}
116+
117+
return false
118+
}

models/nameType.go

Lines changed: 17 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package models
22

3-
import "gorm.io/gorm"
3+
import (
4+
"gorm.io/gorm"
5+
"time"
6+
)
47

58
//NameType main struct
69
type NameType struct {
@@ -11,7 +14,20 @@ type NameType struct {
1114
NameVariations string `json:"NameVariations,omitempty"`
1215
}
1316

17+
//NameVar struct for organizing name variations by Levenshtein
1418
type NameVar struct {
1519
Name string
1620
Levenshtein float32
1721
}
22+
23+
//MetaphoneR only use for SearchSimilarNames return
24+
type MetaphoneR struct {
25+
ID uint `json:"ID,omitempty"`
26+
CreatedAt time.Time `json:"CreatedAt,omitempty"`
27+
UpdatedAt time.Time `json:"UpdatedAt,omitempty"`
28+
DeletedAt gorm.DeletedAt `json:"DeletedAt,omitempty"`
29+
Name string `json:"Name,omitempty"`
30+
Classification string `json:"Classification,omitempty"`
31+
Metaphone string `json:"Metaphone,omitempty"`
32+
NameVariations []string `json:"NameVariations,omitempty"`
33+
}

0 commit comments

Comments
 (0)