web-dev-qa-db-ja.com

文字列のリストで一般的な部分文字列を検出するにはどうすればよいですか

たとえば、一連の文字列が与えられたとします。

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

これらが3つのファイルセットであることを検出できるようにしたい:

  • EntireS [1,2]
  • J27 [赤、緑] P [1,2]
  • JournalP [1,2] [赤、緑、青]

この問題に対処する既知の方法はありますか-これについて読むことができる公開済みの論文はありますか?

私が検討しているアプローチは、各文字列が他のすべての文字列を見て、共通の文字と異なる文字がある場所を見つけ、最も共通する文字列のセットを見つけようとすることですが、これはあまり効率的ではなく、偽陽性。

これは 'ファイル名内の一般的な文字列のグループを検出する方法' と同じではないことに注意してください。これは、文字列の後に一連の数字が続くことを前提としているためです。

43
danio

ここから始めます: http://en.wikipedia.org/wiki/Longest_common_substring_problem

記事で説明されている2つのアルゴリズムのPerl実装を含む、外部リンクの補足情報へのリンクがあります。

追加するように編集:

議論に基づいて、私はまだ最も長い共通部分文字列がこの問題の中心にある可能性があると思います。コメントで参照するJournalの例でも、そのセットを定義する特性は部分文字列「Journal」です。

まず、セットを他のセットとは別のものとして定義するものを検討します。これにより、パーティションを使用してデータを分割できます。問題は、セット内に存在する共通性の程度を測定することです。定義する特性が共通部分文字列の場合、最も長い共通部分文字列が論理的な開始点になります。

セット検出のプロセスを自動化するには、一般に、可能なすべてのペア間の「差異」を測定するために使用できる共通性のペアごとの測定値が必要になります。次に、全体の差が全体的に最小になるパーティションを計算するアルゴリズムが必要です。差分メジャーが最長共通部分文字列でない場合は問題ありませんが、それがどうなるかを判断する必要があります。明らかに、測定できる具体的なものである必要があります。

また、差分測定のプロパティは、パーティションの作成に使用できるアルゴリズムに影響することにも注意してください。たとえば、diff(X、Y)がXとYの差の測定値を与えると仮定します。次に、距離の測定値がdiff(A、C)<= diff(A、B)+ diffである場合におそらく役立つでしょう。 (紀元前)。そして、明らかにdiff(A、C)はdiff(C、A)と同じでなければなりません。

これについて考えるとき、「違い」を2つの文字列間の距離として考えることができるかどうか、また、距離を厳密に定義して、何らかの種類の クラスター分析 入力文字列。ちょっとした考え。

12
Jeremy Bourque

すばらしい質問です。解決策の手順は次のとおりです。

  1. トークン化 入力
  2. トークンを使用して適切な データ構造 を構築します。 a [〜#〜] dawg [〜#〜] が理想的ですが、 Trie の方がシンプルで、まともな開始点です。
  3. サブツリーを単純化または個別の出力にクラスタリングするためのデータ構造のオプションの後処理
  4. serialization から 正規表現 または同様の構文へのデータ構造

このアプローチは 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))
9
Ryan Flynn

そのような何かがうまくいくかもしれません。

  1. すべての文字列を表すトライを作成します。

あなたが与えた例では、ルートから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]")ではなく2つの単語( "banana、bahamas")をリストします。 「短すぎる」は、「フォークの後の部分が前の部分よりも長い場合」のように単純な場合もあれば、特定のプレフィックスを持つ単語の数によって重み付けされている場合もあります。

  3. 2つのサブツリーが「十分に類似」している場合、それらをマージして、ツリーの代わりに一般的なグラフを作成できます。たとえば、ABRed、ABBlue、ABGreen、CDRed、CDBlue、CDGreenがある場合、「AB」をルートとするサブツリーが「CD」をルートとするサブツリーと同じであるため、それらをマージします。出力では、これは次のようになります:[左ブランチ、右ブランチ] [サブツリー]、つまり:[AB、CD] [赤、青、緑]。近くにあるが完全に同じではないサブツリーを処理する方法は?おそらく絶対的な答えはありませんが、ここの誰かが良い考えを持っているかもしれません。

この回答コミュニティwikiをマークしています。一緒に、私たちが質問に合理的に答えられるように、自由に延長してください。

3
redtuna

文字列の類似性には多くのアプローチがあります。レーベンシュタイン距離のような多くのメトリックを実装するこのオープンソースライブラリを見てみることをお勧めします。

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

"frak"を試してください。文字列のセットから正規表現を作成します。多分それのいくつかの修正はあなたを助けるでしょう。 https://github.com/noprompt/frak

それが役に立てば幸い。

1
Vikrant Sagar

一般的な部分文字列を見つける一般的なケースを解決するために提案された多くのソリューションがあります。ただし、ここでの問題はより専門的です。部分文字列だけでなく、一般的なプレフィックスを探しています。これにより、少し簡単になります。最も長い共通のプレフィックスを見つけるための素晴らしい説明は http://www.geeksforgeeks.org/longest-common-prefix-set-1-Word-by-Word-matching/ にあります

だから私が提案した「pythonese」疑似コードは次のようなものです(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
1

一般化されたサフィックスツリーでこれを実現できるはずです。複数のソース文字列から来るサフィックスツリー内の長いパスを探します。

1

この特定の文字列の例を非常に単純に保つには、単純な単語/数字の分離を使用することを検討してください。

数字以外のシーケンスは明らかに大文字(全体)で始めることができます。すべての文字列をシーケンスのグループに分割した後、次のようなもの

[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があるため、それらの変数のみが1,2であることがすぐにわかります。 J27の場合、J27は葉だけで、赤と緑で分岐していることがわかります。

したがって、List <Pair <list、string >> -structure(私が正しく思い出せば複合パターン)のようなものです。

0
Pasi Savolainen
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));
    }
}
0
Riyar Ajay