-
-
Notifications
You must be signed in to change notification settings - Fork 0
/
anagrams.go
110 lines (96 loc) · 2.9 KB
/
anagrams.go
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
103
104
105
106
107
108
109
110
package kowalski
import (
"context"
"sort"
"strings"
)
// Anagram finds all single-word anagrams of the given word, expanding '?' as a single wildcard character
func Anagram(ctx context.Context, checker *SpellChecker, word string) ([]string, error) {
return anagram(ctx, checker, word, false, 0)
}
// MultiAnagram finds all single- and multi-word anagrams of the given word, expanding '?' as a single wildcard
// character. To avoid duplicates, words are sorted lexicographically (i.e., "a ball" will be returned and "ball a"
// will not).
func MultiAnagram(ctx context.Context, checker *SpellChecker, word string) ([]string, error) {
// TODO: Allow configuring of the min length
return anagram(ctx, checker, word, true, 2)
}
func anagram(ctx context.Context, checker *SpellChecker, word string, multiWord bool, minLength int) ([]string, error) {
var (
res []string
swapBefore = len(word)
)
sortedWord := func(w string) string {
s := strings.Split(strings.ToLower(w), "")
sort.Strings(s)
return strings.Join(s, "")
}(word)
for w := []byte(sortedWord); w != nil; w = permute(w, swapBefore+1) {
matches, count, err := findMatch(ctx, checker, string(w), multiWord, minLength)
if err != nil {
return nil, err
}
if len(matches) > 0 {
res = append(res, matches...)
swapBefore = len(word)
} else {
swapBefore = count
}
}
if multiWord {
res = onlyAscendingWords(res)
}
sort.Strings(res)
return unique(res), nil
}
// onlyAscendingWords returns a slice that contains all the entries from input that meet the following criteria:
// * the entry is a single word
// * the entry is multiple-space separated words in increasing lexicographical order
func onlyAscendingWords(input []string) []string {
var filtered []string
for i := range input {
parts := strings.Split(input[i], " ")
if len(parts) > 1 {
last := parts[0]
valid := true
for _, p := range parts[1:] {
if strings.Compare(last, p) == 1 {
valid = false
break
}
}
if valid {
filtered = append(filtered, input[i])
}
} else {
filtered = append(filtered, input[i])
}
}
return filtered
}
// permute returns the next permutation of the given input, in lexicographical order.
// swapBefore can be used to force a swap within a certain number characters (i.e., skip all permutations that
// affect characters after the one with index swapBefore).
func permute(input []byte, swapBefore int) []byte {
if swapBefore < len(input)-1 {
input = append(input[0:swapBefore], func(w []byte) []byte {
s := strings.Split(string(w), "")
sort.Strings(s)
return reverse([]byte(strings.Join(s, "")), 0)
}(input[swapBefore:])...)
}
k, l := -1, -1
for i := range input {
if i+1 < len(input) && input[i] < input[i+1] {
k = i
l = -1
} else if k >= 0 && input[k] < input[i] {
l = i
}
}
if k == -1 {
return nil
}
input[k], input[l] = input[l], input[k]
return reverse(input, k+1)
}