题目:比较14万单词的相似度
输入:14万单词表,table( ID,word)
输出:给出单词关系表( ID1,ID2,相似度),相似度要大于60%
例如:
aoc和aoe的相似度为(百分之):67
abc和abcd的相似度为75
实验用三万单词库下载:
http://www.dullwolf.cn/word/word.rar
14万单词库已经有了,加工后和3万的一起打包再给大家下载.
3万的适合做背单词,14万适合做字典.
包括真人发声,全部单词读音的mp3
我是先切词,所有词都拆成匹配原子表table(ID,原子),原子的长度要大于单词自身长度的60%才有意义.
然后用 原子表 join 自联原子表 on 原子=原子
比较所有 最大长度 原子相同 /两个单词中长的那个
就是相似程度.
3万词切成原子,大概有30万行.
spide
总长度是5
那么>=3的原子才有用
3个的:spi/pid/ide
4个的:spid/pide
5个的:spide
1夹2的:s_id/p_de/
2夹1的:sp_i/pi_e
1夹3的:s_ide
3夹1的:spi_e
2夹2的:sp_de
....
这样一来
假设这词id是900
那么全部词就形成这样的原子对照表
900 spi
900 pid
900 ide
900 spid
....
901 ...
999 ....
自联去比较,可能是一个很大的集合.
假设有一个靶有26个靶环,分别是 a,b,c,d,----z;我们规定如果击中了靶环,该环得分为1,反之为零,每个单词的字母表示该单词击中了对应的靶环的位置.
ok:照上面的规定
abc:就是1110000000000000000000000000有3个1
abd:就是1101000000000000000000000000有3个1
异或运算
结果:就是1100000000000000000000000000有2个1
好了结果就有了,相似度为2/3
我给一个oracle数据库的简单算法:
先创建一个函数,返回相关性。
Create Or Replace Function f_Correlation(f_Str Varchar2, f_Data Varchar2)
Return Number Is
l_Corr Number;
l_Pos Int;
Begin
l_Corr := 0;
For c_Str In (Select Substr(f_Data, Rownum, 1) s
From All_Objects
Where Rownum <= Length(f_Data)) Loop
Select Instr(f_Str, c_Str.s) Into l_Pos From Dual;
If l_Pos > 0 Then
l_Corr := l_Corr + 1;
End If;
End Loop;
Return l_Corr;
End f_Correlation;
然后调用函数:
--源数据
SQL> select * from ta;
A
------------------------------
这是
这是排名
相关
相关度排名
度数
--以“这是相关度测试排名'”作为匹配标准
SQL> select a,f_correlation(a,'这是相关度测试排名') "相关性" from ta order by 2 desc;
A 相关性
------------------------------ ----------
相关度排名 5
这是排名 4
这是 2
相关 2
度数 1
实际中,如何分词是搜索的关键。
比较单词相似度,必须首先切词,英文单词切词法,combine排列组合的“组合”数学算法是关键。
下面代码给出原子的一种思路:比如输入单词“spide”,输出所有可能用来和其他单词比较的原子,由于单词长度是5,匹配度小于60%的没意义,所以原子最小长度是3。
代码是从C语言算法改过来的,同样代码也可以修改成任何编程语言。
ide
pde
sde
pie
sie
spe
pid
sid
spd
spi
pide
side
spde
spie
spid
spide
这里用到了Combine 输出全部组合,Combine 5,3就是得到在5个里选3个的全部可能选法。
<SCRIPT LANGUAGE="vbScript">
str="spide"
''创建全局字典对象,用来存储所有得到的原子结果
Set dict=CreateObject("Scripting.Dictionary")
Dim a(100)
strLength=Len(str)
''原子
atomyLength=round(strLength*0.6)
For x=atomyLength To strLength
a(0)=x
''计算5选3,5选4,5选5组合
combine strLength,x
next
sub combine(m, k)
''计算组合在m里面选k个元素的全部组合情况,添加到字典对象里
i=0
j=0
For i=m To k Step -1
a(k)=i
if (k>1) then
combine i-1,k-1
else
tempStr=""
for j=1 To a(0)
tempStr=tempStr & Mid(str,a(j),1)
Next
''排除重复的,加到字典里
If Not dict. Exists(tempStr) then dict.add tempStr,Len(tempStr)
End if
next
End sub
Main()
Sub Main
''输出显示结果
For i=0 To dict.count-1
Document.write dict.keys()(i) & "<br/>"
next
End sub
</SCRIPT>
考虑到抠去某些字符的组合,结果就非常多,以combine为例有64种不重复切法,输入superdullwolf达到了1700多种!按照平均每个词200种原子算,14万词要产生3000万,在数据库里原子表自联后是个天文数字。
bine
mine
oine
cine
mbne
obne
cbne
omne
cmne
cone
mbie
obie
cbie
omie
cmie
coie
ombe
cmbe
cobe
come
mbin
obin
cbin
omin
cmin
coin
ombn
cmbn
cobn
comn
ombi
cmbi
cobi
comi
comb
mbine
obine
cbine
omine
cmine
coine
ombne
cmbne
cobne
comne
ombie
cmbie
cobie
comie
combe
ombin
cmbin
cobin
comin
combn
combi
ombine
cmbine
cobine
comine
combne
combie
combin
combine
按照在索引中对两个词的相似度的定义:两个单词的编辑距离ed(word1,word2)为word1经过编辑到达word2所需的最小的步骤,这里编辑所指为:
1.插入:如ab->acb
2.删除:如abc->ab
3.修改:如abc->abb
如ed(surgery,survey)=2
ed()越小证明两个单词就越相似,这个的原始定义可以看“A Guided Tour to Approximate String Matching”作者Gonzalo Navarro
确定两个单词的相似有很多方法,可以用动态规划,Q-gram,不确定自动机等。在Gonzalo Navarro的这篇文章中也都有介绍“A Guided Tour to Approximate String Matching”。
把这个给这里的大牛们,看看有人实现没。
Transact-SQL 参考
SOUNDEX
返回由四个字符组成的代码 (SOUNDEX) 以评估两个字符串的相似性。
语法
SOUNDEX ( character_expression )
参数
character_expression
是字符数据的字母数字表达式。character_expression 可以是常数、变量或列。
返回类型
char
注释
SOUNDEX 将 alpha 字符串转换成由四个字符组成的代码,以查找相似的词或名称。代码的第一个字符是 character_expression 的第一个字符,代码的第二个字符到第四个字符是数字。将忽略 character_expression 中的元音,除非它们是字符串的第一个字母。可以嵌套字符串函数。
示例
下例显示 SOUNDEX 函数及相关的 DIFFERENCE 函数。在第一个示例中,返回所有辅音字母的标准 SOUNDEX 值。为 Smith 和 Smythe 返回的 SOUNDEX 结果相同,因为不包括所有元音、字母 y、连写字母和字母 h。
-- Using SOUNDEX
SELECT SOUNDEX ('Smith'), SOUNDEX ('Smythe')
下面是结果集:
----- -----
S530 S530
(1 row(s) affected)
DIFFERENCE 函数比较 SOUNDEX 模式结果的差。第一个示例显示两个仅元音不同的字符串。返回的差是 4(可能的最小差)。
-- Using DIFFERENCE
SELECT DIFFERENCE('Smithers', 'Smythers')
GO
下面是结果集:
-----------
4
(1 row(s) affected)
在下例中,字符串的辅音不同,所以返回的差是 2(较高的差)。
SELECT DIFFERENCE('Anothers', 'Brothers')
GO
下面是结果集:
-----------
2
(1 row(s) affected)
组合算法 ,子陌红尘的存储过程:
create procedure sp_test(@n int,@r int)
as
begin
if isnull(@n,0)<isnull(@r,0)
return
set rowcount @n
select identity(int,1,1) as num into # from sysobjects a,syscolumns b
set rowcount 0
declare @sql varchar(8000),@ord varchar(8000),@i int
set @sql='select * from # [1]'
set @ord='[1].num'
set @i=1
while @i<@r
begin
set @i=@i+1
set @sql=@sql+' inner join # ['+rtrim(@i)+'] on ['+rtrim(@i)+'].num>['+rtrim(@i-1)+'].num'
set @ord=@ord+',['+rtrim(@i)+'].num'
end
set @sql=@sql+' order by '+@ord
print @sql
exec(@sql)
end
go
--exec sp_test 5,1
--exec sp_test 5,2
exec sp_test 15,10
go
drop procedure sp_test
go
单词表:word(id,word)
假设单词最长20个
select a.id, b.id,a.word, b.word,
(decode(substr(a.word,1,1),null,0,substr(b.word,1,1),1,0)+
decode(substr(a.word,2,1),null,0,substr(b.word,2,1),1,0)+
decode(substr(a.word,3,1),null,0,substr(b.word,3,1),1,0)+
decode(substr(a.word,4,1),null,0,substr(b.word,4,1),1,0)+
decode(substr(a.word,5,1),null,0,substr(b.word,5,1),1,0)+
decode(substr(a.word,6,1),null,0,substr(b.word,6,1),1,0)+
decode(substr(a.word,7,1),null,0,substr(b.word,7,1),1,0)+
decode(substr(a.word,8,1),null,0,substr(b.word,8,1),1,0)+
decode(substr(a.word,9,1),null,0,substr(b.word,9,1),1,0)+
decode(substr(a.word,10,1),null,0,substr(b.word,10,1),1,0)+
decode(substr(a.word,11,1),null,0,substr(b.word,11,1),1,0)+
decode(substr(a.word,12,1),null,0,substr(b.word,12,1),1,0)+
decode(substr(a.word,13,1),null,0,substr(b.word,13,1),1,0)+
decode(substr(a.word,14,1),null,0,substr(b.word,14,1),1,0))
decode(substr(a.word,15,1),null,0,substr(b.word,15,1),1,0))
decode(substr(a.word,16,1),null,0,substr(b.word,16,1),1,0))
decode(substr(a.word,17,1),null,0,substr(b.word,17,1),1,0))
decode(substr(a.word,18,1),null,0,substr(b.word,18,1),1,0))
decode(substr(a.word,19,1),null,0,substr(b.word,19,1),1,0))
decode(substr(a.word,20,1),null,0,substr(b.word,20,1),1,0)) --比较每个位置是否相同
/
decode(trunc((length(a.word)-length(b.word)+1000)/1000),1,length(a.word),length (b.word)) -- 取两个单词中最大的长度
from word a, word b
where a.id<>b.id
我现在是找到了原子.问题是数据量太大,回家实验下,原子需要用非SQL程序来找,计划用ASP, 因为懒得编译,做一个页面不停的刷,先把全部词的原子找出来.
然后实验:
一:
1种方案,海量数据的原子表包含(单词ID,原子)其中原子重复数据,自联接,找出最长公共子串.
二:
原子表单独建立,建立一个单词和原子的对应关系表,再连接.
三:
如果上面两种办法都导致机器冒烟,那么把每个单词的原子生成一个XML文件,用SQL 去load成单独的小表,这样一个一个表慢慢比较.(可能要很久),每次比较耗费的资源不多,就是比较的次数比较多,大概是单词数量的阶乘,就算每秒比较10次,可能这辈子都比较不完了.
如果按照Honey_boy(阿良)的余弦算法,采样点到原点的距离,x轴是字母a-z用1-26表示
y轴用单词出现的位置来表示.
那么,
对于单词abc,坐标分别是(1,1)(2,2)(3,3)(0,4) P1=sqrt(2);P2=sqrt(8);P3=sqrt(18);P4=4;
对于单词abcd,坐标分别是(1,1)(2,2)(3,3) Q1=sqrt(2);Q2=sqrt(8);Q3=sqrt(18);Q4=sqrt(32);
计算大概是1.68
越接近1越相似.
原子表包括了一些联想元素,符合人的思维习惯,比如Combine的原子可以是
mine
oine
cine
这些并不是单纯的字符片段,而是抠掉了部分字母.
建立原子字典表和单词关系两个表,速度上会有提升,hansonboy() 说的对,提前计算出原子长度和单词长度储存在合适的位置,会加速计算的速度.