Skip to content

Commit

Permalink
Merge pull request #18 from noho-digital/bugfix/concurrent-find-norma…
Browse files Browse the repository at this point in the history
…lized-fold-panic

Fix for crash when concurrent calls are made to find normalized fold
  • Loading branch information
lithammer authored Oct 4, 2020
2 parents 6da2c93 + 5ef9091 commit 4762799
Show file tree
Hide file tree
Showing 2 changed files with 54 additions and 20 deletions.
52 changes: 32 additions & 20 deletions fuzzy/fuzzy.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,33 +12,45 @@ import (
"golang.org/x/text/unicode/norm"
)

var foldTransformer = unicodeFoldTransformer{}
var noopTransformer = transform.Nop
var normalizeTransformer = transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
var normalizeFoldTransformer = transform.Chain(normalizeTransformer, foldTransformer)
func noopTransformer() transform.Transformer {
return transform.Nop
}

func foldTransformer() transform.Transformer {
return unicodeFoldTransformer{}
}

func normalizeTransformer() transform.Transformer {
return transform.Chain(norm.NFD, runes.Remove(runes.In(unicode.Mn)), norm.NFC)
}

func normalizedFoldTransformer() transform.Transformer {
return transform.Chain(normalizeTransformer(), foldTransformer())
}


// Match returns true if source matches target using a fuzzy-searching
// algorithm. Note that it doesn't implement Levenshtein distance (see
// RankMatch instead), but rather a simplified version where there's no
// approximation. The method will return true only if each character in the
// source can be found in the target and occurs after the preceding matches.
func Match(source, target string) bool {
return match(source, target, noopTransformer)
return match(source, target, noopTransformer())
}

// MatchFold is a case-insensitive version of Match.
func MatchFold(source, target string) bool {
return match(source, target, foldTransformer)
return match(source, target, foldTransformer())
}

// MatchNormalized is a unicode-normalized version of Match.
func MatchNormalized(source, target string) bool {
return match(source, target, normalizeTransformer)
return match(source, target, normalizeTransformer())
}

// MatchNormalizedFold is a unicode-normalized and case-insensitive version of Match.
func MatchNormalizedFold(source, target string) bool {
return match(source, target, normalizeFoldTransformer)
return match(source, target, normalizedFoldTransformer())
}

func match(source, target string, transformer transform.Transformer) bool {
Expand Down Expand Up @@ -71,22 +83,22 @@ Outer:

// Find will return a list of strings in targets that fuzzy matches source.
func Find(source string, targets []string) []string {
return find(source, targets, noopTransformer)
return find(source, targets, noopTransformer())
}

// FindFold is a case-insensitive version of Find.
func FindFold(source string, targets []string) []string {
return find(source, targets, foldTransformer)
return find(source, targets, foldTransformer())
}

// FindNormalized is a unicode-normalized version of Find.
func FindNormalized(source string, targets []string) []string {
return find(source, targets, normalizeTransformer)
return find(source, targets, normalizeTransformer())
}

// FindNormalizedFold is a unicode-normalized and case-insensitive version of Find.
func FindNormalizedFold(source string, targets []string) []string {
return find(source, targets, normalizeFoldTransformer)
return find(source, targets, normalizedFoldTransformer())
}

func find(source string, targets []string, transformer transform.Transformer) []string {
Expand All @@ -108,22 +120,22 @@ func find(source string, targets []string, transformer transform.Transformer) []
// the Levenshtein calculation, only deletions need be considered, required
// additions and substitutions would fail the match test.
func RankMatch(source, target string) int {
return rank(source, target, noopTransformer)
return rank(source, target, noopTransformer())
}

// RankMatchFold is a case-insensitive version of RankMatch.
func RankMatchFold(source, target string) int {
return rank(source, target, foldTransformer)
return rank(source, target, foldTransformer())
}

// RankMatchNormalized is a unicode-normalized version of RankMatch.
func RankMatchNormalized(source, target string) int {
return rank(source, target, normalizeTransformer)
return rank(source, target, normalizeTransformer())
}

// RankMatchNormalizedFold is a unicode-normalized and case-insensitive version of RankMatch.
func RankMatchNormalizedFold(source, target string) int {
return rank(source, target, normalizeFoldTransformer)
return rank(source, target, normalizedFoldTransformer())
}

func rank(source, target string, transformer transform.Transformer) int {
Expand Down Expand Up @@ -164,22 +176,22 @@ Outer:
// RankFind is similar to Find, except it will also rank all matches using
// Levenshtein distance.
func RankFind(source string, targets []string) Ranks {
return rankFind(source, targets, noopTransformer)
return rankFind(source, targets, noopTransformer())
}

// RankFindFold is a case-insensitive version of RankFind.
func RankFindFold(source string, targets []string) Ranks {
return rankFind(source, targets, foldTransformer)
return rankFind(source, targets, foldTransformer())
}

// RankFindNormalized is a unicode-normalized version of RankFind.
func RankFindNormalized(source string, targets []string) Ranks {
return rankFind(source, targets, normalizeTransformer)
return rankFind(source, targets, normalizeTransformer())
}

// RankFindNormalizedFold is a unicode-normalized and case-insensitive version of RankFind.
func RankFindNormalizedFold(source string, targets []string) Ranks {
return rankFind(source, targets, normalizeFoldTransformer)
return rankFind(source, targets, normalizedFoldTransformer())
}

func rankFind(source string, targets []string, transformer transform.Transformer) Ranks {
Expand Down
22 changes: 22 additions & 0 deletions fuzzy/fuzzy_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,28 @@ func TestRankMatchNormalizedFold(t *testing.T) {
}
}

func TestRankMatchNormalizedFoldConcurrent(t *testing.T) {
target := strings.Split("Lorem ipsum dolor sit amet, consectetur adipiscing elit", " ")
source := "ips"
procs := 10
iter := 10
type empty struct{}
done := make(chan empty)
for i := 0; i <= procs; i++ {
go func() {
for n := 0; n < iter; n++ {
_ = RankFindNormalizedFold(source, target)
}
done <- empty{}
}()
}
cnt := 0;
for i := 0; i < procs; i++ {
<- done
cnt++
}
}

func TestRankFind(t *testing.T) {
target := []string{"cartwheel", "foobar", "wheel", "baz"}
wanted := []Rank{
Expand Down

0 comments on commit 4762799

Please sign in to comment.