我正在寻找一个宝石或项目,让我发现两个名字是同一个人。例如
J.R. Smith == John R. Smith == John Smith == John Roy Smith == Johnny Smith
我想你明白了。我知道什么都不会100%准确,但我想得到的东西至少可以处理大多数情况。我知道最后一个可能需要一个昵称数据库。
答案 0 :(得分:4)
我认为一种选择是使用Levenshtein distance
的ruby实现两个字符串之间的Levenshtein距离定义为将一个字符串转换为另一个字符串所需的最小编辑数,允许的编辑操作是插入,删除或替换单个字符。
然后你可以定义距离小于X的名字(X是你必须调整的数字)来自同一个人。
修改强> 通过一点点搜索,我能够找到另一种基于语音Metaphone
的算法还有很多漏洞,但我认为在这种情况下,每个人都可以做的最好的事情就是给你替代方案来测试并看看哪种方法效果最好
答案 1 :(得分:4)
这有点晚了(并且是一个无耻的启动插件),但是为了它的价值,我在GSoC项目期间编写了一个human name parser,您可以使用gem install namae
进行安装。它显然无法可靠地检测到您的重复项,但它可以帮助您完成此类任务。
例如,您可以解析示例中的名称并使用显示形式使用首字母来检测首字母相同的名称,依此类推:
names = Namae.parse('J.R. Smith and John R. Smith and John Smith and John Roy Smith and Johnny Smith ')
names.map { |n| [n.given, n.family] }
#=> => [["J.R.", "Smith"], ["John R.", "Smith"], ["John", "Smith"], ["John Roy", "Smith"], ["Johnny", "Smith"]]
names.map { |n| n.initials expand: true }
#=> ["J.R. Smith", "J.R. Smith", "J. Smith", "J.R. Smith", "J. Smith"]
答案 2 :(得分:3)
类似的东西:
1:将名称转换为数组:
irb> names.map!{|n|n.scan(/[^\s.]+\.?/)}
["J.", "R.", "Smith"]
["John", "R.", "Smith"]
["John", "Smith"]
["John", "Roy", "Smith"]
["Johnny", "Smith"]
2:身份的某些功能:
for a,b in names.combination(2)
p [(a&b).size,a,b]
end
[2, ["J.", "R.", "Smith"], ["John", "R.", "Smith"]]
[1, ["J.", "R.", "Smith"], ["John", "Smith"]]
[1, ["J.", "R.", "Smith"], ["John", "Roy", "Smith"]]
[1, ["J.", "R.", "Smith"], ["Johnny", "Smith"]]
[2, ["John", "R.", "Smith"], ["John", "Smith"]]
[2, ["John", "R.", "Smith"], ["John", "Roy", "Smith"]]
[1, ["John", "R.", "Smith"], ["Johnny", "Smith"]]
[2, ["John", "Smith"], ["John", "Roy", "Smith"]]
[1, ["John", "Smith"], ["Johnny", "Smith"]]
[1, ["John", "Roy", "Smith"], ["Johnny", "Smith"]]
或者代替&
,您可以使用.permutation
+ .zip
+ .max
来应用一些自定义函数,该函数确定的部分名称相同。
UPD:
aim = 'Rob Bobbie Johnson'
candidates = [
"Bob Robbie John",
"Bobbie J. Roberto",
"R.J.B.",
]
$synonyms = Hash[ [
["bob",["bobbie"]],
["rob",["robbie","roberto"]],
] ]
def prepare name
name.scan(/[^\s.]+\.?/).map &:downcase
end
def mf a,b # magick function
a.zip(b).map do |i,j|
next 1 if i == j
next 0.9 if $synonyms[i].to_a.include?(j) || $synonyms[j].to_a.include?(i)
next 0.5 if i[/\.$/] && j.start_with?(i.chomp '.')
next 0.5 if j[/\.$/] && i.start_with?(j.chomp '.')
-10 # if some part of name appears to be different -
# it's bad even if another two parts were good
end.inject :+
end
for c in candidates
results = prepare(c).permutation.map do |per|
[mf(prepare(aim),per),per]
end
p [results.transpose.first.max,c]
end
[-8.2, "Bob Robbie John"] # 0.9 + 0.9 - 10 # Johnson != John # I think ..)
[2.4, "Bobbie J. Roberto"] # 1 + 0.9 + 0.5 # Rob == Roberto, Bobbie == Bobbie, Johnson ~~ J.
[1.5, "R.J.B."] # 0.5 + 0.5 + 0.5
答案 3 :(得分:2)
对于必须尝试匹配来自不同数据源的人名的任何人,这都是一个非常棘手的问题。使用3个宝石的组合似乎效果很好。
我们有一个应用程序,在列表A中有一百万人,需要将他们与数十种不同的数据源进行匹配。 (尽管有些古怪的评论声称,但这不是处理“现实世界”混乱数据的本质的“设计缺陷”。)
到目前为止,我们发现唯一可以正常工作的方法是结合使用namae
gem(用于将名称解析为标准化的开头,中间,结尾和后缀表示形式)和text
gem来计算levenshtein,soundex,metaphone和porter分数,还有fuzzy-string-match
来计算JaroWinkler分数(通常是最好的)。
John "JJ" Doe
或Samuel (Sammy) Smith
时提取昵称通过对每种评分方法的评分阈值进行一些调整,我们获得了不错的结果。 YMMV。
BTW将姓氏放在首位非常重要,至少对于JaroWinkler而言,因为姓氏的差异通常较小(Smithe几乎始终是Smithe,但是名字在不同数据源中可能是Tom或Tommy或Thomas),并且在JaroWinkler中,字符串开头最“敏感”。对于“ ROB SMITHE / ROBIN SMITHE”,如果您首先使用名字,则JaroWinkler距离为0.91;如果您首先使用姓氏,则为0.99。
答案 4 :(得分:1)
您可能会发现最好的预编码,这就是名为“text”的宝石。
https://github.com/threedaymonk/text
它有许多匹配算法:Levenshtein Distance,Metaphone,Soundex等。
答案 5 :(得分:0)
我不认为这样的图书馆存在。
我不是故意冒犯,但这个问题似乎是由于设计不佳造成的。也许如果你发布关于你想要解决的一般问题的更多细节,人们可以建议一个更好的方法。
答案 6 :(得分:0)
Ruby有一个名为text
的非常漂亮的宝石,我发现Text::WhiteSimilarity
本身非常好,但它也实现了一堆其他测试
答案 7 :(得分:0)
Ruby中一个强大的人名匹配器/集群解决方案的初步尝试:https://github.com/adrianomitre/match_author_names