【问题标题】:Finding groups of similar strings in a large set of strings在大量字符串中查找相似字符串组
【发布时间】:2011-03-20 17:57:03
【问题描述】:

我有一组相当大的字符串(比如 100 个),其中包含许多以相似性为特征的子组。我正在尝试找到/设计一种算法,可以合理有效地找到这些组。

例如,假设输入列表在左下方,输出组在右侧。

Input                           Output
-----------------               -----------------
Jane Doe                        Mr Philip Roberts
Mr Philip Roberts               Phil Roberts     
Foo McBar                       Philip Roberts   
David Jones                     
Phil Roberts                    Foo McBar        
Davey Jones            =>         
John Smith                      David Jones      
Philip Roberts                  Dave Jones       
Dave Jones                      Davey Jones      
Jonny Smith                     
                                Jane Doe         

                                John Smith       
                                Jonny Smith 

有没有人知道任何合理有效地解决这个问题的方法?

查找相似字符串的标准方法似乎是 Levenshtein 距离,但我看不出如何在这里充分利用它,而不必将每个字符串与列表中的每个其他字符串进行比较,然后以某种方式决定关于判断两个字符串是否在同一组中的差异阈值。

另一种方法是将字符串散列为整数的算法,其中相似的字符串散列为在数轴上靠得很近的整数。我不知道那会是什么算法,如果存在的话

有人有什么想法/指点吗?


更新: @Will A:也许名字不像我最初想象的那么好。作为一个起点,我想我可以假设在我将使用的数据中,字符串中的一个小变化不会使它从一个组跳到另一个组。

【问题讨论】:

  • 您希望您的算法如何处理一系列字符串,其中每个字符串都与前一个非常细微的不同,但第一个与最后一个有很大不同?
  • 好问题。作为一个起点,我不太担心这一点,因为我希望字符串组在我期望的数据中是相当不同的。我还应该补充一点,对于列表中的每个实体,我将至少有一个其他维度(已经是数字)以帮助进行分组,因此字符串比较部分不必是 100% 完美的。

标签: algorithm string design-patterns


【解决方案1】:

另一种流行的方法是通过它们的 Jaccard 索引来关联字符串。以http://en.wikipedia.org/wiki/Jaccard_index 开头。

这是一篇关于使用 Jaccard-index(和其他几种方法)来解决像您这样的问题的文章:

http://matpalm.com/resemblance/

【讨论】:

  • 仅当您将字符串视为一组单词时(即单词的顺序和它们的基数都不重要)。如果顺序很重要,Levenshtein 距离(如 Roman 所述)要准确得多(尽管计算速度要慢得多)。
【解决方案2】:

您要解决的问题是典型的clusterization 问题。

从简单的K-Means 算法开始,使用 Levenshtein 距离作为计算元素和聚类中心之间距离的函数。

顺便说一句,Levenshtein 距离计算的算法在 Apache Commons StringUtils 中实现 - StringUtils.getLevenshteinDistance

K-Means 的主要问题是您应该指定集群的数量(在您的术语中为子组)。因此,您将有 2 个选择:使用一些 euristic 改进 K-Means 或使用另一种不需要指定簇数的聚类算法(但该算法可能会表现出更差的性能,并且如果您决定实施它可能会非常困难自己)。

【讨论】:

  • 我知道“分组”这个词不太适合我想要做的事情。这些链接看起来也很有用。谢谢!
【解决方案3】:

如果我们谈论的是实际的发音词,比较它们的metaphone 的(开始)可能会有所帮助:

MRFLPRBRTS: Mr Philip Roberts
FLRBRTS: Phil Roberts   
FLPRBRTS: Philip Roberts 
FMKBR: Foo McBar      
TFTJNS: David Jones    
TFJNS: Dave Jones     
TFJNS: Davey Jones    
JNT: Jane Doe       
JNSM0: John Smith     
JNSM0: Jonny Smith

【讨论】:

    【解决方案4】:

    对于您给出的示例,我认为 Levenshtein 距离不合适,因为“Bonny Smith”与“Jonny Smith”“非常相似”,并且几乎可以肯定最终会被视为同一个班级。

    我认为您需要从具有同义词(例如“John”、“Jon”、“Jonny”、“Johnny”等)和匹配的某些名称的角度来处理这个问题(如果使用名称)基于这些。

    【讨论】:

    • William-->Bill... 如果这是一个现实世界的问题,那么它的解决方案一点也不简单,需要一些已经在语言学方面进行过研究并且可能已经填满数据库。
    【解决方案5】:

    我已经解决了一个这样的问题,首先我对文本进行了规范化,并从字符串中取出对整个字符串没有价值的单词,比如InC。美国...

    这个不值的词要你自己定义。

    标准化后,我使用 Jaro Winkler 距离按名称运行检查,并将结果分组到具有相似对象列表的对象中。

    真的很好。

    我用 30K 人名在 java 中运行了这个

    我希望这个想法对某人有用

    【讨论】:

      【解决方案6】:

      在用于模糊匹配的开源 Java 库中记录了这个确切问题的解决方案 https://github.com/intuit/fuzzy-matcher

      这里使用的想法是分解单词(令牌)中的名称并使用文本匹配算法来查找单词中的相似性(如 Soundex、Jaccard 或 Lavenshtiein)。

      然后使用从每个单词中找到的分数并平均每个名称的分数。

      这种匹配的性能是相当关键的,因为如果我们不断地将每个名称与其他名称进行匹配,这将成为指数级增长的复杂性。

      这个库依赖于匹配算法的等价性和传递性 如果 "David" 与 "Davey" 匹配,则隐含反向匹配,您不必运行这些匹配

      这个库还有一些技巧可以降低匹配的复杂性,我能够在大约 2 秒内对 4000 个名称进行匹配。

      【讨论】:

        【解决方案7】:

        这是 Levenshtein 函数的 SQL 代码:

        CREATE FUNCTION [Levenshtein](@str_1 nvarchar(4000), @str_2 nvarchar(4000))
        RETURNS int
        AS
        
        BEGIN
         DECLARE    @str_1_len int
                ,   @str_2_len int
                ,   @str_1_itr int
                ,   @str_2_itr int
                ,   @str_1_char nchar
                ,   @Levenshtein int
                ,   @LD_temp int
                ,   @cv0 varbinary(8000)
                ,   @cv1 varbinary(8000)
        
        SELECT  @str_1_len = LEN(@str_1)
            ,   @str_2_len = LEN(@str_2)
            ,   @cv1 = 0x0000
            ,   @str_2_itr = 1
            ,   @str_1_itr = 1
            ,   @Levenshtein = 0
        
        
        WHILE @str_2_itr <= @str_2_len
        
        SELECT  @cv1 = @cv1 + CAST(@str_2_itr AS binary(2))
            ,   @str_2_itr = @str_2_itr + 1
        
        WHILE @str_1_itr <= @str_1_len
        BEGIN
            SELECT  @str_1_char = SUBSTRING(@str_1, @str_1_itr, 1)
                ,   @Levenshtein = @str_1_itr
                ,   @cv0 = CAST(@str_1_itr AS binary(2))
                ,   @str_2_itr = 1
        
            WHILE @str_2_itr <= @str_2_len
            BEGIN
                SET @Levenshtein = @Levenshtein + 1
                SET @LD_temp = CAST(SUBSTRING(@cv1, @str_2_itr+@str_2_itr-1, 2) AS int) +
                CASE WHEN @str_1_char = SUBSTRING(@str_2, @str_2_itr, 1) THEN 0 ELSE 1 END
                IF @Levenshtein > @LD_temp SET @Levenshtein = @LD_temp
                SET @LD_temp = CAST(SUBSTRING(@cv1, @str_2_itr+@str_2_itr+1, 2) AS int)+1
                IF @Levenshtein > @LD_temp SET @Levenshtein = @LD_temp
                SELECT @cv0 = @cv0 + CAST(@Levenshtein AS binary(2)), @str_2_itr = @str_2_itr + 1
            END
        
        SELECT @cv1 = @cv0, @str_1_itr = @str_1_itr + 1
        END
        
        RETURN @Levenshtein
        END
        

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 2022-12-10
          • 2019-12-27
          • 2021-05-26
          • 2015-09-22
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多