diff options
author | Alexander Kiryukhin <a.kiryukhin@mail.ru> | 2022-05-01 21:50:12 +0300 |
---|---|---|
committer | Alexander Kiryukhin <a.kiryukhin@mail.ru> | 2022-05-01 21:50:12 +0300 |
commit | 9fcf8e29214210612d545bed50d7f889800ac639 (patch) | |
tree | 3a99d2cd37fb8158d49abc1de6298758d205c9dd /levenshtein/generic.go |
Initial
Diffstat (limited to 'levenshtein/generic.go')
-rw-r--r-- | levenshtein/generic.go | 99 |
1 files changed, 99 insertions, 0 deletions
diff --git a/levenshtein/generic.go b/levenshtein/generic.go new file mode 100644 index 0000000..4aa3ecf --- /dev/null +++ b/levenshtein/generic.go @@ -0,0 +1,99 @@ +package levenshtein + +import ( + "go.neonxp.dev/extra/math" +) + +// Levenshtein возвращает последовательность правок для превращения последовательности элементов s1 в s2 +// с учетом стоимостей операций возвращаемых функцией cost +// TODO в алгоритме не предусмотрена экономия памяти ¯\_(ツ)_/¯ +func Levenshtein[T comparable](s1, s2 []T, cost EditCost[T]) []Edit { + len1 := len(s1) + len2 := len(s2) + if cost == nil { + cost = EditCost[T](func(t EditType, s1 *T, s2 *T) int { + return 1 + }) + } + + dp := make([][]int, len1+1) + for i := range dp { + dp[i] = make([]int, len2+1) + } + dp[0][0] = 0 + for i := 0; i < len1; i++ { + for j := 0; j < len2; j++ { + if i == 0 && j == 0 { + continue + } + switch { + case i == 0: + dp[0][j] = dp[0][j-1] + cost(Insert, nil, &s2[j]) + case j == 0: + dp[i][0] = dp[i-1][0] + cost(Delete, &s1[i], nil) + default: + m1 := dp[i-1][j] + cost(Delete, &s1[i], nil) + m2 := dp[i][j-1] + cost(Insert, nil, &s2[j]) + m3 := dp[i-1][j-1] + cost(Replace, &s1[i], &s2[j]) + dp[i][j] = math.MinScalar(m1, m2, m3) + } + } + } + i, j := len1-1, len2-1 + edits := []Edit{} + for i > 0 && j > 0 { + m1 := dp[i-1][j] + cost(Delete, &s1[i], nil) + m2 := dp[i][j-1] + cost(Insert, nil, &s2[j]) + m3 := dp[i-1][j-1] + cost(Replace, &s1[i], &s2[j]) + min := math.MinScalar(m1, m2, m3) + if min == m1 { + // добавляем удаление символа S1[i] + edits = append(edits, Edit{ + Type: Delete, + Idx1: i, + }) + i-- + } + if min == m2 { + // добавляем вставку символа S2[j] после S1[i] + edits = append(edits, Edit{ + Type: Insert, + Idx1: i, + Idx2: j, + }) + j-- + } + if min == m3 { + // добавляем замену S1[i] на S2[j] + i-- + j-- + if s1[i] != s2[j] { + edits = append(edits, Edit{ + Type: Replace, + Idx1: i, + Idx2: j, + }) + } + } + } + return edits +} + +// EditCost функция возвращающая стоимость действия t над элементами from, to +type EditCost[T comparable] func(t EditType, from *T, to *T) int + +// Edit редакторская правка описывающее одно действие над исходной последовательностью +type Edit struct { + Type EditType // Тип правки: Вставка/Замена/Удаление + Idx1 int // Индекс элемента из первой последовательности + Idx2 int // Индекс элемента из второй последовательности +} + +// EditType тип правки +type EditType int + +const ( + Insert EditType = iota // Вставка + Replace // Замена + Delete // Удаление +) |