-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Expand file tree
/
Copy pathlevenshtein.go
More file actions
102 lines (90 loc) · 2.46 KB
/
Copy pathlevenshtein.go
File metadata and controls
102 lines (90 loc) · 2.46 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
package levenshtein
import (
"golang.org/x/exp/constraints"
"golang.org/x/xerrors"
)
// Matches returns the closest matches to the needle from the haystack.
// The maxDistance parameter is the maximum Matches distance to consider.
// If no matches are found, an empty slice is returned.
func Matches(needle string, maxDistance int, haystack ...string) (matches []string) {
for _, hay := range haystack {
if d, err := Distance(needle, hay, maxDistance); err == nil && d <= maxDistance {
matches = append(matches, hay)
}
}
return matches
}
var ErrMaxDist = xerrors.New("levenshtein: maxDist exceeded")
// Distance returns the edit distance between a and b using the
// Wagner-Fischer algorithm.
// A and B must be less than 255 characters long.
// maxDist is the maximum distance to consider.
// A value of -1 for maxDist means no maximum.
func Distance(a, b string, maxDist int) (int, error) {
if len(a) > 255 {
return 0, xerrors.Errorf("levenshtein: a must be less than 255 characters long")
}
if len(b) > 255 {
return 0, xerrors.Errorf("levenshtein: b must be less than 255 characters long")
}
// #nosec G115 - Safe conversion since we've checked that len(a) < 255
m := uint8(len(a))
// #nosec G115 - Safe conversion since we've checked that len(b) < 255
n := uint8(len(b))
// Special cases for empty strings
if m == 0 {
return int(n), nil
}
if n == 0 {
return int(m), nil
}
// Allocate a matrix of size m+1 * n+1
d := make([][]uint8, 0)
var i, j uint8
for i = 0; i < m+1; i++ {
di := make([]uint8, n+1)
d = append(d, di)
}
// Source prefixes
for i = 1; i < m+1; i++ {
d[i][0] = i
}
// Target prefixes
for j = 1; j < n; j++ {
d[0][j] = j // nolint:gosec // this cannot overflow
}
// Compute the distance
for j = 0; j < n; j++ {
for i = 0; i < m; i++ {
var subCost uint8
// Equal
if a[i] != b[j] {
subCost = 1
}
// Don't forget: matrix is +1 size
d[i+1][j+1] = minOf(
d[i][j+1]+1, // deletion
d[i+1][j]+1, // insertion
d[i][j]+subCost, // substitution
)
// check maxDist on the diagonal
// #nosec G115 - Safe conversion as maxDist is expected to be small for edit distances
if maxDist > -1 && i == j && d[i+1][j+1] > uint8(maxDist) {
return int(d[i+1][j+1]), ErrMaxDist
}
}
}
return int(d[m][n]), nil
}
func minOf[T constraints.Ordered](ts ...T) T {
if len(ts) == 0 {
panic("minOf: no arguments")
}
m := ts[0]
for _, t := range ts[1:] {
if t < m {
m = t
}
}
return m
}