-
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
-
111
-
112
-
113
-
114
-
115
-
116
-
117
-
118
-
119
-
120
-
121
-
122
-
123
-
124
-
125
-
126
-
127
-
128
-
129
-
130
-
131
-
132
-
133
-
134
-
135
-
136
-
137
-
138
-
139
-
140
-
141
-
142
-
143
-
144
-
145
-
146
-
147
-
148
-
149
-
150
-
151
-
152
-
153
-
154
-
155
-
156
-
157
-
158
-
159
-
160
-
161
-
162
-
163
-
164
-
165
-
166
-
167
-
168
-
169
-
170
-
171
-
172
-
173
-
174
-
175
-
176
-
177
-
178
-
179
-
180
-
181
-
182
-
183
-
184
-
185
-
186
-
187
-
188
-
189
-
190
-
191
-
192
-
193
-
194
-
195
-
196
-
197
-
198
-
199
-
200
-
201
-
202
-
203
-
204
-
205
-
206
-
207
-
208
-
209
-
210
-
211
-
212
-
213
-
214
-
215
-
216
-
217
-
218
-
219
-
220
-
221
-
222
-
223
-
224
-
225
-
226
-
227
-
228
-
229
-
230
-
231
-
232
-
233
-
234
-
235
-
236
-
237
-
238
-
239
-
240
-
241
-
242
-
243
-
244
-
245
-
246
-
247
-
248
-
249
-
250
-
251
-
252
-
253
-
254
-
255
-
256
-
257
-
258
-
259
-
260
-
261
-
262
-
263
-
264
-
265
-
266
-
267
-
268
-
269
-
270
-
271
-
272
-
273
-
274
-
275
-
276
-
277
-
278
-
279
-
280
-
281
-
282
-
283
-
284
-
285
-
286
-
287
-
288
-
289
-
290
-
291
-
292
-
293
-
294
-
295
-
296
-
297
-
298
-
299
-
300
-
301
-
302
-
303
-
304
-
305
-
306
-
307
-
308
-
309
-
310
-
311
-
312
-
313
-
314
-
315
-
316
-
317
-
318
-
319
-
320
-
321
-
322
-
323
-
324
-
325
-
326
-
327
-
328
-
329
-
330
-
331
-
332
-
333
-
334
-
335
-
336
-
337
-
338
-
339
-
340
-
341
-
342
-
343
-
344
-
345
-
346
-
347
-
348
-
349
// Copyright (c) 2018 Couchbase, Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package levenshtein
import (
"crypto/md5"
"encoding/json"
"fmt"
"math"
)
type ParametricState struct {
shapeID uint32
offset uint32
}
func newParametricState() ParametricState {
return ParametricState{}
}
func (ps *ParametricState) isDeadEnd() bool {
return ps.shapeID == 0
}
type Transition struct {
destShapeID uint32
deltaOffset uint32
}
func (t *Transition) apply(state ParametricState) ParametricState {
ps := ParametricState{
shapeID: t.destShapeID}
// don't need any offset if we are in the dead state,
// this ensures we have only one dead state.
if t.destShapeID != 0 {
ps.offset = state.offset + t.deltaOffset
}
return ps
}
type ParametricStateIndex struct {
stateIndex []uint32
stateQueue []ParametricState
numOffsets uint32
}
func newParametricStateIndex(queryLen,
numParamState uint32) ParametricStateIndex {
numOffsets := queryLen + 1
if numParamState == 0 {
numParamState = numOffsets
}
maxNumStates := numParamState * numOffsets
psi := ParametricStateIndex{
stateIndex: make([]uint32, maxNumStates),
stateQueue: make([]ParametricState, 0, 150),
numOffsets: numOffsets,
}
for i := uint32(0); i < maxNumStates; i++ {
psi.stateIndex[i] = math.MaxUint32
}
return psi
}
func (psi *ParametricStateIndex) numStates() int {
return len(psi.stateQueue)
}
func (psi *ParametricStateIndex) maxNumStates() int {
return len(psi.stateIndex)
}
func (psi *ParametricStateIndex) get(stateID uint32) ParametricState {
return psi.stateQueue[stateID]
}
func (psi *ParametricStateIndex) getOrAllocate(ps ParametricState) uint32 {
bucket := ps.shapeID*psi.numOffsets + ps.offset
if bucket < uint32(len(psi.stateIndex)) &&
psi.stateIndex[bucket] != math.MaxUint32 {
return psi.stateIndex[bucket]
}
nState := uint32(len(psi.stateQueue))
psi.stateQueue = append(psi.stateQueue, ps)
psi.stateIndex[bucket] = nState
return nState
}
type ParametricDFA struct {
distance []uint8
transitions []Transition
maxDistance uint8
transitionStride uint32
diameter uint32
}
func (pdfa *ParametricDFA) initialState() ParametricState {
return ParametricState{shapeID: 1}
}
// Returns true iff whatever characters come afterward,
// we will never reach a shorter distance
func (pdfa *ParametricDFA) isPrefixSink(state ParametricState, queryLen uint32) bool {
if state.isDeadEnd() {
return true
}
remOffset := queryLen - state.offset
if remOffset < pdfa.diameter {
stateDistances := pdfa.distance[pdfa.diameter*state.shapeID:]
prefixDistance := stateDistances[remOffset]
if prefixDistance > pdfa.maxDistance {
return false
}
for _, d := range stateDistances {
if d < prefixDistance {
return false
}
}
return true
}
return false
}
func (pdfa *ParametricDFA) numStates() int {
return len(pdfa.transitions) / int(pdfa.transitionStride)
}
func min(x, y uint32) uint32 {
if x < y {
return x
}
return y
}
func (pdfa *ParametricDFA) transition(state ParametricState,
chi uint32) Transition {
return pdfa.transitions[pdfa.transitionStride*state.shapeID+chi]
}
func (pdfa *ParametricDFA) getDistance(state ParametricState,
qLen uint32) Distance {
remainingOffset := qLen - state.offset
if state.isDeadEnd() || remainingOffset >= pdfa.diameter {
return Atleast{d: pdfa.maxDistance + 1}
}
dist := pdfa.distance[int(pdfa.diameter*state.shapeID)+int(remainingOffset)]
if dist > pdfa.maxDistance {
return Atleast{d: dist}
}
return Exact{d: dist}
}
func (pdfa *ParametricDFA) computeDistance(left, right string) Distance {
state := pdfa.initialState()
leftChars := []rune(left)
for _, chr := range []rune(right) {
start := state.offset
stop := min(start+pdfa.diameter, uint32(len(leftChars)))
chi := characteristicVector(leftChars[start:stop], chr)
transition := pdfa.transition(state, uint32(chi))
state = transition.apply(state)
if state.isDeadEnd() {
return Atleast{d: pdfa.maxDistance + 1}
}
}
return pdfa.getDistance(state, uint32(len(left)))
}
func (pdfa *ParametricDFA) buildDfa(query string, distance uint8,
prefix bool) (*DFA, error) {
qLen := uint32(len([]rune(query)))
alphabet := queryChars(query)
psi := newParametricStateIndex(qLen, uint32(pdfa.numStates()))
maxNumStates := psi.maxNumStates()
deadEndStateID := psi.getOrAllocate(newParametricState())
if deadEndStateID != 0 {
return nil, fmt.Errorf("Invalid dead end state")
}
initialStateID := psi.getOrAllocate(pdfa.initialState())
dfaBuilder := withMaxStates(uint32(maxNumStates))
mask := uint32((1 << pdfa.diameter) - 1)
var stateID int
for stateID = 0; stateID < StateLimit; stateID++ {
if stateID == psi.numStates() {
break
}
state := psi.get(uint32(stateID))
if prefix && pdfa.isPrefixSink(state, qLen) {
distance := pdfa.getDistance(state, qLen)
dfaBuilder.addState(uint32(stateID), uint32(stateID), distance)
} else {
transition := pdfa.transition(state, 0)
defSuccessor := transition.apply(state)
defSuccessorID := psi.getOrAllocate(defSuccessor)
distance := pdfa.getDistance(state, qLen)
stateBuilder, err := dfaBuilder.addState(uint32(stateID), defSuccessorID, distance)
if err != nil {
return nil, fmt.Errorf("parametric_dfa: buildDfa, err: %v", err)
}
alphabet.resetNext()
chr, cv, err := alphabet.next()
for err == nil {
chi := cv.shiftAndMask(state.offset, mask)
transition := pdfa.transition(state, chi)
destState := transition.apply(state)
destStateID := psi.getOrAllocate(destState)
stateBuilder.addTransition(chr, destStateID)
chr, cv, err = alphabet.next()
}
}
}
if stateID == StateLimit {
return nil, ErrTooManyStates
}
dfaBuilder.setInitialState(initialStateID)
return dfaBuilder.build(distance), nil
}
func fromNfa(nfa *LevenshteinNFA) (*ParametricDFA, error) {
lookUp := newHash()
lookUp.getOrAllocate(*newMultiState())
initialState := nfa.initialStates()
lookUp.getOrAllocate(*initialState)
maxDistance := nfa.maxDistance()
msDiameter := nfa.msDiameter()
numChi := 1 << msDiameter
chiValues := make([]uint64, numChi)
for i := 0; i < numChi; i++ {
chiValues[i] = uint64(i)
}
transitions := make([]Transition, 0, numChi*int(msDiameter))
var stateID int
for stateID = 0; stateID < StateLimit; stateID++ {
if stateID == len(lookUp.items) {
break
}
for _, chi := range chiValues {
destMs := newMultiState()
ms := lookUp.getFromID(stateID)
nfa.transition(ms, destMs, chi)
translation := destMs.normalize()
destID := lookUp.getOrAllocate(*destMs)
transitions = append(transitions, Transition{
destShapeID: uint32(destID),
deltaOffset: translation,
})
}
}
if stateID == StateLimit {
return nil, ErrTooManyStates
}
ns := len(lookUp.items)
diameter := int(msDiameter)
distances := make([]uint8, 0, diameter*ns)
for stateID := 0; stateID < ns; stateID++ {
ms := lookUp.getFromID(stateID)
for offset := 0; offset < diameter; offset++ {
dist := nfa.multistateDistance(ms, uint32(offset))
distances = append(distances, dist.distance())
}
}
return &ParametricDFA{
diameter: uint32(msDiameter),
transitions: transitions,
maxDistance: maxDistance,
transitionStride: uint32(numChi),
distance: distances,
}, nil
}
type hash struct {
index map[[16]byte]int
items []MultiState
}
func newHash() *hash {
return &hash{
index: make(map[[16]byte]int, 100),
items: make([]MultiState, 0, 100),
}
}
func (h *hash) getOrAllocate(m MultiState) int {
size := len(h.items)
var exists bool
var pos int
md5 := getHash(&m)
if pos, exists = h.index[md5]; !exists {
h.index[md5] = size
pos = size
h.items = append(h.items, m)
}
return pos
}
func (h *hash) getFromID(id int) *MultiState {
return &h.items[id]
}
func getHash(ms *MultiState) [16]byte {
msBytes := []byte{}
for _, state := range ms.states {
jsonBytes, _ := json.Marshal(&state)
msBytes = append(msBytes, jsonBytes...)
}
return md5.Sum(msBytes)
}