如何检测字符串列表中的公共子字符串

时间:2009-09-11 13:19:35

标签: algorithm pattern-matching

给定一组字符串,例如:

EFgreen
EFgrey
EntireS1
EntireS2
J27RedP1
J27GreenP1
J27RedP2
J27GreenP2
JournalP1Black
JournalP1Blue
JournalP1Green
JournalP1Red
JournalP2Black
JournalP2Blue
JournalP2Green

我希望能够检测到这些是三组文件:

  • EntireS [1,2]
  • J27 [红色,绿色] P [1,2]
  • JournalP [1,2] [红,绿,蓝]

是否有任何已知方法可以解决这个问题 - 我可以阅读哪些已发表的论文?

我正在考虑的方法是每个字符串查看所有其他字符串并查找常见字符和不同字符的位置,尝试查找最常见的字符串集,但我担心这不是很有效可能会给出误报。

请注意,这与'How do I detect groups of common strings in filenames'不同,因为它假定字符串后面总是会有一系列数字。

9 个答案:

答案 0 :(得分:10)

我会从这里开始:http://en.wikipedia.org/wiki/Longest_common_substring_problem

外部链接中有补充信息的链接,包括文章中解释的两种算法的Perl实现。

编辑添加:

根据讨论,我仍然认为最长公共子串可能是这个问题的核心。即使在您在评论中引用的Journal示例中,该集合的定义特征也是子字符串'Journal'。

我首先考虑将一个集定义为与其他集分开的内容。这为您提供了划分数据的分区,然后问题在于测量集合中存在多少共性。如果定义特征是一个公共子字符串,则Longest Common Substring将是一个逻辑起点。

要自动执行集检测过程,通常需要成对测量共性,可用于衡量所有可能对之间的“差异”。然后,您需要一种算法来计算导致总体差异最小的分区。如果差异度量不是最长公共子串,那很好,但是你需要确定它将是什么。显然,它需要是一个可以衡量的具体事物。

请记住,差异测量的属性将依赖于可用于制作分区的算法。例如,假设diff(X,Y)给出X和Y之间差异的度量。那么如果你的距离测量值是diff(A,C)<= diff(A,B)+那么它可能是有用的。 DIFF(B,C)。显然,diff(A,C)应该与diff(C,A)相同。

在考虑这个问题时,我也开始怀疑我们是否可以将“差异”视为任意两个字符串之间的距离,并且通过严格定义距离,我们可以尝试某种{{3在输入字符串上。只是一个想法。

答案 1 :(得分:5)

好问题!解决方案的步骤是:

  1. tokenizing输入
  2. 使用令牌构建合适的data structureDAWG是理想的,但Trie更简单,也是一个不错的起点。
  3. 数据结构的可选后处理,以简化或将子树聚类为单独的输出
  4. serialization数据结构为regular expression或类似语法
  5. 我在regroup.py中实施了这种方法。这是一个例子:

    $ cat | ./regroup.py --cluster-prefix-len=2
    EFgreen
    EFgrey
    EntireS1
    EntireS2
    J27RedP1
    J27GreenP1
    J27RedP2
    J27GreenP2
    JournalP1Black
    JournalP1Blue
    JournalP1Green
    JournalP1Red
    JournalP2Black
    JournalP2Blue
    JournalP2Green
    ^D
    EFgre(en|y)
    EntireS[12]
    J27(Green|Red)P[12]
    JournalP[12](Bl(ack|ue)|(Green|Red))
    

答案 2 :(得分:2)

字符串相似性有很多种方法。我建议看看这个开源库,它实现了很多像Levenshtein距离这样的指标。

http://sourceforge.net/projects/simmetrics/

答案 3 :(得分:2)

这样的事可能有用。

  1. 构建代表所有字符串的trie。
  2. 在你给出的例子中,根将有两条边:“E”和“J”。然后“J”分支将分为“Jo”和“J2”。

    1. 分叉的单股,例如E-n-t-i-r-e-S-(分叉为1,2)表示选择,因此这将是EntireS [1,2]

    2. 如果绳子相对于叉子“太短”,例如B-A-(分叉到N-A-N-A和H-A-M-A-S),我们列出两个词(“香蕉,巴哈马”)而不是一个选择(“ba [nana,hamas]”)。 “太短”可能就像“如果叉子之后的部分比之前的部分长”,或者可能用具有给定前缀的单词数加权。

    3. 如果两个子树“足够相似”,那么它们可以合并,这样您就可以使用一般图形而不是树。例如,如果您有ABRed,ABBlue,ABGreen,CDRed,CDBlue,CDGreen,您可能会发现以“AB”为根的子树与以“CD”为根的子树相同,因此您将合并它们。在你的输出中,它将如下所示:[左分支,右分支] [子树],所以:[AB,CD] [红色,蓝色,绿色]。如何处理接近但不完全相同的子树?可能没有绝对的答案,但这里有人可能有个好主意。

    4. 我正在为这个答案社区维基做标记。请随意扩展它,以便我们可以对这个问题有一个合理的答案。

答案 4 :(得分:1)

您应该能够使用通用后缀树实现此目的:在后缀树中查找来自多个源字符串的长路径。

答案 5 :(得分:1)

尝试“frak”。它从字符串集创建正则表达式。也许对它的一些修改会对你有所帮助。 https://github.com/noprompt/frak

希望它有所帮助。

答案 6 :(得分:1)

提出了许多解决方案来解决查找公共子串的一般情况。但是,这里的问题更加专业。您正在寻找共同的前缀,而不仅仅是子串。这使它更简单一些。 找到最长公共前缀的一个很好的解释可以在 http://www.geeksforgeeks.org/longest-common-prefix-set-1-word-by-word-matching/

所以我建议的&#34; pythonese&#34;伪代码是这样的(请参阅find_lcp的实现链接:

def count_groups(items):
  sorted_list = sorted(items)

  prefix = sorted_list[0]
  ctr = 0
  groups = {}
  saved_common_prefix = ""
  for i in range(1, sorted_list):
    common_prefix = find_lcp(prefix, sorted_list[i])
    if len(common_prefix) > 0: #we are still in the same group of items
      ctr += 1
      saved_common_prefix = common_prefix
      prefix = common_prefix
    else: # we must have switched to a new group
      groups[saved_common_prefix] = ctr
      ctr = 0
      saved_common_prefix = ""
      prefix = sorted_list[i]
  return groups

答案 7 :(得分:0)

对于这个特殊的字符串示例,请考虑使用简单的字/数字分离。

非数字序列显然可以以大写字母(整数)开头。将所有字符串分成序列组后,如

[Entire][S][1]
[Entire][S][2]
[J][27][Red][P][1]
[J][27][Green][P][1]
[J][27][Red][P][2]
....
[Journal][P][1][Blue]
[Journal][P][1][Green]

然后按组开始分组,你很快就可以看到前缀“Entire”对于某些组是常见的,并且所有子组都有S作为headgroup,所以只有那些变量为1,2。 对于J27案例,您可以看到J27只是叶子,但它然后以红色和绿色分支。

所以List&lt; Pair&lt; list,string&gt;&gt; -structure(复合模式,如果我没记错的话)。

答案 8 :(得分:0)

import java.util.*;
class StringProblem
{
    public List<String> subString(String name)
    {
        List<String> list=new ArrayList<String>(); 
        for(int i=0;i<=name.length();i++)
        {
           for(int j=i+1;j<=name.length();j++)
           {
               String s=name.substring(i,j);
               list.add(s);
           }
        }
        return list;
    }
    public String commonString(List<String> list1,List<String> list2,List<String> list3)
    {
        list2.retainAll(list1);
        list3.retainAll(list2);

        Iterator it=list3.iterator();
        String s="";
        int length=0;
        System.out.println(list3);   // 1 1 2 3 1 2 1
        while(it.hasNext())    
        {
           if((s=it.next().toString()).length()>length)
           {
              length=s.length();
           }
        }
        return s;
    }
    public static void main(String args[])
    {
        Scanner sc=new Scanner(System.in);
        System.out.println("Enter the String1:");
        String name1=sc.nextLine();
        System.out.println("Enter the String2:");
        String name2=sc.nextLine();
        System.out.println("Enter the String3:");
        String name3=sc.nextLine();
      //  String name1="salman";
      //  String name2="manmohan";
      //  String name3="rahman";

        StringProblem  sp=new StringProblem();

        List<String> list1=new ArrayList<String>();
        list1=sp.subString(name1);

        List<String> list2=new ArrayList<String>();
        list2=sp.subString(name2);


        List<String> list3=new ArrayList<String>();
        list3=sp.subString(name3);

        sp.commonString(list1,list2,list3);
        System.out.println(" "+sp.commonString(list1,list2,list3));
    }
}