golang实现可定制编辑成本的Levenshtein距离与相似度计算插件库levenshtein的使用

Golang实现可定制编辑成本的Levenshtein距离与相似度计算插件库levenshtein的使用

概述

这是一个用于计算两个字符串之间Levenshtein距离的Go语言包。Levenshtein Distance表示将一个字符串转换为另一个字符串所需的最小编辑操作总成本。允许的编辑操作包括插入、删除和替换(在UTF-8字符级别)。每个操作默认成本为1,但可以自定义每个操作的成本(大于等于0的值)。

项目状态

v1.2.3 稳定版:保证未来v1.x版本不会有破坏性API变更。虽然基于"AS IS"提供,但可以安全用于生产环境。

功能特性

  • Distance:计算两个字符串的编辑距离,0表示完全相同
  • Similarity:将距离转换为0-1范围的相似度指标(1表示完全相同)
  • Match:提供带公共前缀奖励的相似度计算
  • 支持自定义编辑操作成本
  • 支持阈值提前终止计算优化

安装

go get github.com/agext/levenshtein

使用示例

基础用法

package main

import (
	"fmt"
	"github.com/agext/levenshtein"
)

func main() {
	// 计算两个字符串的编辑距离
	distance := levenshtein.Distance("kitten", "sitting", nil)
	fmt.Printf("Distance: %d\n", distance) // 输出: Distance: 3

	// 计算相似度(0-1范围)
	similarity := levenshtein.Similarity("kitten", "sitting", nil)
	fmt.Printf("Similarity: %.2f\n", similarity) // 输出: Similarity: 0.57
}

自定义编辑成本

package main

import (
	"fmt"
	"github.com/agext/levenshtein"
)

func main() {
	// 创建自定义配置:插入成本2,删除成本1,替换成本3
	cfg := levenshtein.NewParams()
	cfg.InsCost = 2
	cfg.DelCost = 1
	cfg.SubCost = 3

	// 使用自定义成本计算距离
	distance := levenshtein.Distance("kitten", "sitting", cfg)
	fmt.Printf("Custom cost distance: %d\n", distance) // 输出: Custom cost distance: 8
}

带阈值的相似度计算

package main

import (
	"fmt"
	"github.com/agext/levenshtein"
)

func main() {
	// 设置最小相似度阈值0.6
	cfg := levenshtein.NewParams()
	cfg.MinSimilarity = 0.6

	// 计算相似度(低于阈值返回0)
	similarity := levenshtein.Similarity("kitten", "sitting", cfg)
	fmt.Printf("Threshold similarity: %.2f\n", similarity) // 输出: Threshold similarity: 0.00
}

带前缀奖励的匹配

package main

import (
	"fmt"
	"github.com/agext/levenshtein"
)

func main() {
	// 计算带前缀奖励的相似度
	match := levenshtein.Match("martha", "marhta", nil)
	fmt.Printf("Match with prefix bonus: %.2f\n", match) // 输出: Match with prefix bonus: 0.94
}

高级用法

package main

import (
	"fmt"
	"github.com/agext/levenshtein"
)

func main() {
	// 使用Calculate函数进行底层计算
	cfg := levenshtein.NewParams()
	cfg.SubCost = 2 // 设置替换成本为2

	// 计算详细结果
	r := levenshtein.Calculate([]rune("kitten"), []rune("sitting"), cfg)
	fmt.Printf("Distance: %d\n", r.Distance)       // 编辑距离
	fmt.Printf("Similarity: %.2f\n", r.Similarity) // 相似度
	fmt.Printf("Match: %.2f\n", r.Match)           // 带奖励的匹配分数
}

性能提示

  • 对于长字符串比较,设置合理的阈值可以显著提高性能
  • 如果只关心是否超过某个距离阈值,可以使用Distance函数的阈值参数
  • 重用Params结构体以避免重复内存分配

许可证

该包基于Apache 2.0许可证发布。


更多关于golang实现可定制编辑成本的Levenshtein距离与相似度计算插件库levenshtein的使用的实战教程也可以访问 https://www.itying.com/category-94-b0.html

1 回复

更多关于golang实现可定制编辑成本的Levenshtein距离与相似度计算插件库levenshtein的使用的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


Golang实现可定制编辑成本的Levenshtein距离计算

Levenshtein距离(编辑距离)是衡量两个字符串相似度的常用方法,表示将一个字符串转换成另一个字符串所需的最少单字符编辑操作(插入、删除、替换)次数。下面我将展示如何用Golang实现一个可定制编辑成本的Levenshtein距离计算库。

基础实现

首先,我们实现一个基础版本的Levenshtein距离计算:

package levenshtein

// BasicLevenshtein 计算基础Levenshtein距离
func BasicLevenshtein(a, b string) int {
	// 创建一个二维切片存储距离
	d := make([][]int, len(a)+1)
	for i := range d {
		d[i] = make([]int, len(b)+1)
	}

	// 初始化边界条件
	for i := 0; i <= len(a); i++ {
		d[i][0] = i
	}
	for j := 0; j <= len(b); j++ {
		d[0][j] = j
	}

	// 填充距离矩阵
	for i := 1; i <= len(a); i++ {
		for j := 1; j <= len(b); j++ {
			cost := 0
			if a[i-1] != b[j-1] {
				cost = 1
			}
			d[i][j] = min(
				d[i-1][j]+1,   // 删除
				d[i][j-1]+1,   // 插入
				d[i-1][j-1]+cost, // 替换
			)
		}
	}

	return d[len(a)][len(b)]
}

func min(nums ...int) int {
	m := nums[0]
	for _, n := range nums[1:] {
		if n < m {
			m = n
		}
	}
	return m
}

可定制成本的实现

接下来,我们扩展这个实现,允许自定义不同操作的代价:

package levenshtein

// CostParams 定义编辑操作的代价
type CostParams struct {
	InsertCost  int // 插入代价
	DeleteCost  int // 删除代价
	ReplaceCost int // 替换代价
}

// DefaultCost 返回默认代价(所有操作代价为1)
func DefaultCost() CostParams {
	return CostParams{1, 1, 1}
}

// CustomLevenshtein 计算可定制成本的Levenshtein距离
func CustomLevenshtein(a, b string, cost CostParams) int {
	d := make([][]int, len(a)+1)
	for i := range d {
		d[i] = make([]int, len(b)+1)
	}

	// 初始化边界条件
	for i := 0; i <= len(a); i++ {
		d[i][0] = i * cost.DeleteCost
	}
	for j := 0; j <= len(b); j++ {
		d[0][j] = j * cost.InsertCost
	}

	// 填充距离矩阵
	for i := 1; i <= len(a); i++ {
		for j := 1; j <= len(b); j++ {
			replaceCost := 0
			if a[i-1] != b[j-1] {
				replaceCost = cost.ReplaceCost
			}
			d[i][j] = min(
				d[i-1][j]+cost.DeleteCost,
				d[i][j-1]+cost.InsertCost,
				d[i-1][j-1]+replaceCost,
			)
		}
	}

	return d[len(a)][len(b)]
}

相似度计算

基于Levenshtein距离,我们可以计算字符串相似度:

// Similarity 计算字符串相似度(0-1之间)
func Similarity(a, b string, cost CostParams) float64 {
	maxLen := max(len(a), len(b))
	if maxLen == 0 {
		return 1.0
	}
	distance := CustomLevenshtein(a, b, cost)
	maxPossible := max(
		len(a)*cost.DeleteCost+len(b)*cost.InsertCost,
		len(a)*cost.InsertCost+len(b)*cost.DeleteCost,
		len(a)*cost.ReplaceCost+abs(len(a)-len(b))*max(cost.InsertCost, cost.DeleteCost),
	)
	return 1.0 - float64(distance)/float64(maxPossible)
}

func max(nums ...int) int {
	m := nums[0]
	for _, n := range nums[1:] {
		if n > m {
			m = n
		}
	}
	return m
}

func abs(x int) int {
	if x < 0 {
		return -x
	}
	return x
}

使用示例

package main

import (
	"fmt"
	"yourmodule/levenshtein"
)

func main() {
	str1 := "kitten"
	str2 := "sitting"

	// 使用默认成本
	basicDist := levenshtein.BasicLevenshtein(str1, str2)
	fmt.Printf("Basic Levenshtein distance: %d\n", basicDist)

	// 使用自定义成本
	customCost := levenshtein.CostParams{
		InsertCost:  1,
		DeleteCost:  1,
		ReplaceCost: 2, // 替换代价更高
	}
	customDist := levenshtein.CustomLevenshtein(str1, str2, customCost)
	fmt.Printf("Custom cost distance: %d\n", customDist)

	// 计算相似度
	similarity := levenshtein.Similarity(str1, str2, customCost)
	fmt.Printf("Similarity: %.2f\n", similarity)
}

性能优化

对于长字符串,我们可以优化空间复杂度,只使用两行而不是整个矩阵:

// OptimizedLevenshtein 空间优化的Levenshtein距离计算
func OptimizedLevenshtein(a, b string, cost CostParams) int {
	// 确保b是较短的字符串
	if len(a) < len(b) {
		a, b = b, a
	}

	prevRow := make([]int, len(b)+1)
	currentRow := make([]int, len(b)+1)

	// 初始化第一行
	for j := 0; j <= len(b); j++ {
		prevRow[j] = j * cost.InsertCost
	}

	for i := 1; i <= len(a); i++ {
		currentRow[0] = i * cost.DeleteCost
		for j := 1; j <= len(b); j++ {
			replaceCost := 0
			if a[i-1] != b[j-1] {
				replaceCost = cost.ReplaceCost
			}
			currentRow[j] = min(
				prevRow[j]+cost.DeleteCost,
				currentRow[j-1]+cost.InsertCost,
				prevRow[j-1]+replaceCost,
			)
		}
		// 交换行
		prevRow, currentRow = currentRow, prevRow
	}

	return prevRow[len(b)]
}

总结

这个实现提供了:

  1. 基础Levenshtein距离计算
  2. 可定制的编辑操作成本
  3. 字符串相似度计算
  4. 空间优化版本

你可以根据需要进一步扩展,比如添加:

  • 阈值限制,当距离超过阈值时提前终止计算
  • 支持Unicode字符
  • 并行计算
  • 缓存机制

这个库可以用于拼写检查、模糊搜索、DNA序列比对等多种场景。

回到顶部