与 Python + Sqlite 的字符串相似度(Levenshtein 距离/编辑距离)

Posted

技术标签:

【中文标题】与 Python + Sqlite 的字符串相似度(Levenshtein 距离/编辑距离)【英文标题】:String similarity with Python + Sqlite (Levenshtein distance / edit distance) 【发布时间】:2018-09-21 14:05:41 【问题描述】:

在 Python+Sqlite 中是否有可用的字符串相似度度量,例如 sqlite3 模块?

用例示例:

import sqlite3
conn = sqlite3.connect(':memory:')
c = conn.cursor()
c.execute('CREATE TABLE mytable (id integer, description text)')
c.execute('INSERT INTO mytable VALUES (1, "hello world, guys")')
c.execute('INSERT INTO mytable VALUES (2, "hello there everybody")')

这个查询应该匹配 ID 为 1 的行,而不是 ID 为 2 的行:

c.execute('SELECT * FROM mytable WHERE dist(description, "He lo wrold gyus") < 6')

如何在 Sqlite+Python 中做到这一点?

关于我目前发现的内容的注释:

Levenshtein distance,即将一个单词更改为另一个单词所需的单字符编辑(插入、删除或替换)的最小数量可能有用,但我不确定是否有官方实现存在于 Sqlite 中(我见过一些自定义实现,比如this one)

Damerau-Levenshtein 是相同的,除了它还允许两个相邻字符之间的换位;它也被称为Edit distance

我知道自己可以define a function,但是实现这样的距离将是不平凡的(对数据库进行超级高效的自然语言处理比较确实不平凡),这就是为什么我想看看是否Python / Sqlite 已经具备这样的工具

Sqlite 具有 FTS(全文搜索)功能:FTS3、FTS4、FTS5

CREATE VIRTUAL TABLE enrondata1 USING fts3(content TEXT);     /* FTS3 table */
CREATE TABLE enrondata2(content TEXT);                        /* Ordinary table */
SELECT count(*) FROM enrondata1 WHERE content MATCH 'linux';  /* 0.03 seconds */
SELECT count(*) FROM enrondata2 WHERE content LIKE '%linux%'; /* 22.5 seconds */

但我没有发现关于字符串比较与这样的“相似距离”,FTS 的功能MATCHNEAR 似乎没有字母变化等的相似性度量。

此外,this answer 表明:

SQLite 的 FTS 引擎基于令牌 - 搜索引擎尝试匹配的关键字。 有多种标记器可用,但它们相对简单。 “simple”标记器只是将每个单词拆分并小写:例如,在字符串“The quick brown fox jumps over the lazy dog”中,单词“jumps”会匹配,但不会匹配“jump”。 “porter”分词器更高级一些,它去除了单词的共轭,因此“jumps”和“jumping”会匹配,但像“jmups”这样的错字不会。

遗憾的是,后者(“jmups”无法与“jumps”相似)使得它对我的用例不切实际。

【问题讨论】:

【参考方案1】:

这是一个即用型示例test.py

import sqlite3
db = sqlite3.connect(':memory:')
db.enable_load_extension(True)
db.load_extension('./spellfix')                 # for Linux
#db.load_extension('./spellfix.dll')            # <-- UNCOMMENT HERE FOR WINDOWS
db.enable_load_extension(False)
c = db.cursor()
c.execute('CREATE TABLE mytable (id integer, description text)')
c.execute('INSERT INTO mytable VALUES (1, "hello world, guys")')
c.execute('INSERT INTO mytable VALUES (2, "hello there everybody")')
c.execute('SELECT * FROM mytable WHERE editdist3(description, "hel o wrold guy") < 600')
print c.fetchall()
# Output: [(1, u'hello world, guys')]

重要提示:editdist3 的距离已标准化,因此

值100用于插入和删除,150用于替换


这是在 Windows 上首先要做的事情:

    下载https://sqlite.org/2016/sqlite-src-3110100.zip、https://sqlite.org/2016/sqlite-amalgamation-3110100.zip并解压

    C:\Python27\DLLs\sqlite3.dll 替换为来自here 的新sqlite3.dll。如果跳过此步骤,您稍后会收到 sqlite3.OperationalError: The specified procedure could not be found

    运行:

    call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat"  
    

    call "C:\Program Files (x86)\Microsoft Visual Studio 12.0\VC\vcvarsall.bat" x64
    cl /I sqlite-amalgamation-3110100/ sqlite-src-3110100/ext/misc/spellfix.c /link /DLL /OUT:spellfix.dll
    python test.py
    

    (使用 MinGW,它将是:gcc -g -shared spellfix.c -I ~/sqlite-amalgation-3230100/ -o spellfix.dll

这是在 Linux Debian 上的操作方法:

(基于this answer)

apt-get -y install unzip build-essential libsqlite3-dev
wget https://sqlite.org/2016/sqlite-src-3110100.zip
unzip sqlite-src-3110100.zip
gcc -shared -fPIC -Wall -Isqlite-src-3110100 sqlite-src-3110100/ext/misc/spellfix.c -o spellfix.so
python test.py

下面是在 Linux Debian 上使用旧 Python 版本的方法:

如果您的发行版的 Python 有点旧,则需要另一种方法。由于sqlite3 模块是Python 内置的,所以not straightforward 似乎要升级它(pip install --upgrade pysqlite 只会升级pysqlite 模块,而不是底层的SQLite 库)。因此,this method 有效,例如,如果 import sqlite3; print sqlite3.sqlite_version 是 3.8.2:

wget https://www.sqlite.org/src/tarball/27392118/SQLite-27392118.tar.gz
tar xvfz SQLite-27392118.tar.gz
cd SQLite-27392118 ; sh configure ; make sqlite3.c ; cd ..
gcc -g -fPIC -shared SQLite-27392118/ext/misc/spellfix.c -I SQLite-27392118/src/ -o spellfix.so
python test.py   # [(1, u'hello world, guys')]

【讨论】:

感谢@JacquesGaudin,我修复了链接,并包含了您的 MinGW 版本。 一直很开心,顺便学了一点msvc命令行! 写得非常好:-)。请注意,只需稍加努力,您就可以将权重修改为 100 和 150 之间的差异。 哦,这很有趣@bodo。你试过其他重量吗? (最初我考虑使用权重 1(或 100)来处理通常定义的 Levenshtein 距离,但后来我想:为什么默认使用 100 和 150 可能是有原因的......)?我对此很好奇,因为我正在编写 “您确定您输入的文本尚未在我们的数据库中吗?” 功能 [有点类似于 SO 的“可能已经有您的问题回答“当您提出问题时],因此我需要将用户输入文本与数据库中已经存在的文本进行比较。 @SL5net 这是文件夹名称问题,可能与引号等一起使用。除此之外,我不知道它可能是什么【参考方案2】:

我实现了与距离相关的函数(Damerau-Levenshtein、Jaro-Winkler、最长公共子字符串和子序列)作为 SQLite 运行时可加载扩展。支持任何 UTF-8 字符串。

https://github.com/schiffma/distlib

【讨论】:

欢迎使用 ***,为这个漂亮的 Github 存储库加油。为了在这里成为一个有用的答案(不接受仅链接的答案),您是否可以添加更多上下文,并使用示例可重现代码(在几行 Python 中)显示如何实际使用它? 同意 Basj 的评论。这是一个很好的资源,而且效果很好!您应该在答案中添加一些 python 代码和解释,以展示如何使用和安装它。包含大量 .cpp 的 Github 可能会让一些 Python 程序员感到害怕……

以上是关于与 Python + Sqlite 的字符串相似度(Levenshtein 距离/编辑距离)的主要内容,如果未能解决你的问题,请参考以下文章

在 python 中使用余弦相似度返回与查询文档相比最相似的文档

Python:字符串的语义相似度得分

python 判断两个字符串的相似度的两个方法

Python 连接MongoDB并比较两个字符串相似度的简单示例

Python 比较两个字符串的相似度

Python单词短语相似度比较