2012-01-23 10 views
4

Aho–Corasickの実装はPHPで動作していますか? Wikipediaの記事に言及した1 Aho-Corasick string matching in PHPがあります:速いAho-Corasick PHPの実装

<?php 

/* 

    This class performs a multiple pattern matching by using the Aho-Corasick algorythm, which scans text and matches all patterns "at once". 

    This class can: 
    - find if any of the patterns occours inside the text 
    - find all occourrences of the patterns inside the text 
    - substitute all occourrences of the patterns with a specified string (empty as well) 

    Example of usage: 

    $words = array{ "ananas", "antani", "assassin" }; 
    $pm = new PatternsMatcher();  
    $pm->patterns_array = $words; 
    if ($pm->multipattern_match("banananassata")) { 
     echo "pattern found!!";   
    } 


    This class is definitively open-source under no particular license. 
    If you use it, let me know what you think about it and how to improve it: 

     Marco Nobile (Università degli studi di Milano-Bicocca) - [email protected] 

    The code wasn't deeply tested, use as your own risk.  

    P.S.: in order to use it as a bad words black-lister, I suggest you to wrap the words with two empty spaces (eg.: "ananas"-->" ananas ") 
    in order to avoid sub-patterns detection. Moreover, better delete the word by substituting it with an empty space instead of the empty string. 

*/ 


class PatternsMatcher { 

    var $patterns_array; 
    var $text; 
    var $finals; 
    var $delta; 
    var $phi; 
    var $container; 
    var $M; 
    var $ready; 


    /* costruttore */ 
    function PatternsMatcher() { 
     $this->finals = array(); 
     $this->phi = array(); 
     $this->container = array(); 
     $this->delta = array(); 
     $this->patterns_array = array(); 
     $this->ready = false; 
    } 


    /* import new pattern */ 
    function import_pattern($p) { 
     $this->patterns_array[]=$p; 
    } 


    /* shortcuts */ 
    function deltafun($indice, $carattere) { 
     return $this->delta[$indice][$carattere]; 
    }  
    function phifun($indice) { 
     return $this->phi[$indice+1]; 
    } 


    /* chiamata (ricorsiva) che controlla l'esistenza di prefissi uguali a questa stringa. 
     il parametro limita il numero di stati oltre il quale non verificare */ 
    function check_border($string , $state) { 


     /* se la stringa è lunga 0 non ho trovato prefissi buoni */ 
     if (strlen($string)==0) 
      return 0; 

     /* se la stringa è più lunga, controlliamo non sia contenuta in un prefisso 
      ovvero in una delle stringhe identificate dagli stati precedenti (<state) */ 
     for ($j=0; $j<$state; $j++) { 

      /* se questo suffisso è uguale ad un pattern, ritorna lo stato corrispondente */ 
      if ($string == $this->container[ $j ]) 
       return $j+1; 
     } 

     // trovato nulla, riprovo con la sottostringa 
     return $this->check_border(substr($string, 1) , $state);     
    } 


    /* costruisce la tabella phi (failure) */ 
    function build_phi() { 

     /* valore di partenza */ 
     $this->phi[0]=0; 

     /* foreach stato */ 
     foreach ($this->container as $index => $string) { 

      /* controlla se il prefisso di questo pattern ha un suffisso che è... 
       prefisso di un pattern tra quelli identificati dagli stati 0..index */ 
      $this->phi[ $index ] = $this->check_border($string , $index); 

     } 

     return $this->phi; 

    } 


    /* costruisce la tabella delta (next) */ 
    function build_delta() { 

     /* somma delle lunghezze dei patterns */ 
     $this->M = 0; 

     /* ultimo stato */ 
     $last_state = 0; 

     /* contiamo i caratteri dei patterns */ 
     foreach($this->patterns_array as $pattern) { 
      $lunghezza = strlen($pattern); 
      $this->M += $lunghezza; 
     } 

     /* for each pattern... */ 
     foreach($this->patterns_array as $key => $pattern) { 

      /* convertiamo le stringhe in array di caratteri */   
      $string = $pattern; 
      $lun = strlen($pattern); 

      /* stati iniziali */ 
      $asf_state = 0; 
      $in_pattern_index = 0; 

      /* tengo traccia dei prefissi, mi rende la vita più semplice dopo */ 
      $temp = ""; 

      /* finché il pattern non è esaurito e la delta è diversa da NIL... */ 
      while(($in_pattern_index < $lun) & (!is_null($this->deltafun($asf_state , $string[$in_pattern_index])))) { 

       // segui un percorso pre-esistente 
       $asf_state = $this->deltafun($asf_state , $string[ $in_pattern_index ]); 

       // aggiorna il prefisso fin quì 
       $temp.=$string[ $in_pattern_index ]; 

       // cambia carattere del pattern 
       $in_pattern_index++; 

      } 


      /* crea gli eventuali nuovi stati */ 
      while($in_pattern_index<$lun) { 

       // salva i prefissi aggiuntivi 
       $temp.=$string[ $in_pattern_index ];     
       $this->container[] = $temp; 

       // nuovo stato 
       $last_state++; 

       // salva in delta 
       $this->delta[ $asf_state ][ $string[ $in_pattern_index ] ] = $last_state; 

       // prossimo carattere (se c'è) 
       $in_pattern_index++; 
       $asf_state = $last_state; 

      } 

      /* è uno stato finale! */ 
      $this->finals[ $asf_state ] = true; 

     } 

     return $this->delta; 

    } 


    /* precalcola le tabelle phi e delta; se già calcolate, le ritorna direttamente */ 
    function generate() { 

     /* cache: se abbiamo già precalcolato le tabelle, ritornale direttamente */ 
     if ($this->ready) return; 

     /* ordina lessicograficamente */ 
     sort($this->patterns_array, SORT_STRING); 

     /* precalcula le tabelle */   
     $this->build_delta();  
     $this->build_phi(); 

     /* abbiamo precalcolato */ 
     $this->ready = true; 
    } 


    /* Aho-Corasick standard */ 
    function multipattern_match($text) { 

     // generate tables 
     $this->generate(); 

     // necessario per prendere anche le prime occorrenze della frase (es.: pattern = " ab " in "ab ac "). 
     $text = " ".$text; 

     $i=0;  
     $stato=0; 

     while ($i<strlen($text)) { 

      $n = $this->delta[ $stato ][ $text[$i] ]; 

      $stato = 
       is_null($n)? $this->phi[ $stato ] : $n; 

      if ($this->finals[ $stato]) { 
       return $i; 
      } 

      $i++; 
     }  
     return -1; 
    } 


    /* Aho-Corasick che trova tutte le occorrenze (ritorna un array di tuple {posizione,stringa}) */ 
    function multipattern_match_array($text) { 

     // generate tables 
     $this->generate(); 

     // necessario per prendere anche le prime occorrenze della frase (es.: pattern = " ab " in "ab ac "). 
     $text = " ".$text; 

     $i=0;  
     $stato=0; 
     $result = array(); 
     $temp = ""; 

     while ($i<strlen($text)) { 

      $n = $this->deltafun($stato , $text[$i]); 

      $stato = 
       is_null($n)? $this->phi[ $stato ] : $n; 

      $temp = 
       $stato == 0? 
        "" : $temp.$text[$i];   

      if ($this->finals[ $stato]) { 
       $result[] = array($temp,$i); 
       // echo $temp; 
      }   

      $i++; 
     }  

     return $result; 
    } 


    /* Aho-Corasick modificato per la cancellazione di pattern (blacklist). 
     Il parametro specifica con quale sequenza sostituire lo spazio vuoto */ 
    function remove_substrings($text , $with = "") { 

     // genera le tabelle 
     $this->generate(); 

     // necessario per prendere anche le prime occorrenze della frase (es.: pattern = " ab " in "ab ac "). 
     $text = " ".$text; 

     // contatore sul T 
     $i=0; 

     // contatore sul T' (output) 
     $j=0; 

     // contatore su P 
     $k=0; 

     // stato sull'ASF 
     $stato=0; 

     // non ricalcolare la dimensione del testo tutte le volte! 
     $luntext = strlen($text); 

     // preallochiamo il testo in uscita T' (necessario per le idiosincrasie di PHP) 
     $nuovo = str_repeat(" ", $luntext) ; 

     /* finché ci sono caratteri nel testo... */ 
     while ($i<$luntext) { 

      // prox stato su ASF 
      $n = $this->deltafun($stato , $text[$i]); 

      // è null? usiamo phi 
      $stato = 
       is_null($n)? $this->phifun($stato) : $n; 

      // aggiorniamo la posizione nella sottostringa (utile per fare backtrack dopo la sostituzione) 
      $k = 
       $stato == 0? 
        0 : $k+1; 

      // piazza il nuovo carattere 
      $nuovo[$j] = $text[$i]; 

      /* stato di accettazione! cancella all'indietro e riparti */    
      if ($this->finals[ $stato]) { 

       // backtracking (equivale a cancellare i caratteri) 
       $j -= $k; 
       $k=0; 

       // abbiamo cancellato della roba. dobbiamo riposizionarci sull'ASF! 
       $n = $this->deltafun($stato , substr($with,-1)); 

       $stato = 
        is_null($n)? $this->phifun($stato) : $n; 

       // ci posizioniamo sull'ultimo carattere della stringa con cui abbiamo sostituito il pattern 
       $i--; 
      } 

      // muoviamo i puntatori 
      $j++; $i++;   
     } 

     // non ritorniamo l'intera stringa ma solo quella lunga quanto il risultato 
     return substr($nuovo , 0, $j); 
    } 

} 

私はそれを使用して、ハードの時間を持っているが。赤ちゃんの例では動作しますが、何千ものキーワードを読み込もうとすると、スクリプトは読み込みに30秒の制限を超えます。

その他のスクリプト言語では、Perlの場合はhttp://metacpan.org/pod/Text::Scan、Pythonの場合はhttp://pypi.python.org/pypi/ahocorasick/0.9など素晴らしい実装があります。なぜPHPはありませんか?

+0

これまでに何を試みましたか?また、私はPHPにリンクしている**のように*あなたが既にPHPのために*なぜですか?あなたはたぶん計算上の限界にぶつかっているかもしれませんし、有益ではない方法でクラスを使用しているかもしれませんが、あなたはコードを投稿していません! – hakre

+0

あなたはより小さいテキストとそれほど頻繁でないパターンを使ってみましたか? – c69

+0

@hakre確かに、オートマトンを構築しようとすると、スクリプトは30秒の制限を超えます。自分のローカルサーバーでオートマトンを構築してホスティングにアップロードすることができれば、ドキュメントを見つけることができません。とにかく、推奨されるように拡張としてcライブラリを使う方が良いです。 –

答えて

2

すでに実装があるので、私はほとんどこれをダウン投票したいと思います。あなたは「高速Aho-Corasick PHP実装」という質問に名前を付けるかもしれません。

とにかくPHPは本当に気が狂っていて、それは知られています。コンパイルされた言語や、PHPの場合は、拡張子が残されている方が良いでしょう。あなたは拡張機能としてこのコードを書き直すことができますが、おそらく例えば、このライブラリーのような、何かを既存のCライブラリを取り、拡張子にそれをラップする方が良いでしょう:

http://sourceforge.net/projects/multifast/

あなたがしている場合より速いソリューションを探して、あなたが賞賛したperl/python実装の1つをシェルで包み込みます。

+0

あなたの発言は注目に値します! –

0

phpでPOSIX regexp関数を使用し、 "|"を使用すると、正規表現エンジンが正規表現を評価するためにおそらくDFAを生成するので、Aho-Corasickと基本的に同じ動作をします。

/stackoverflowの| serverfaultの| PHP |パイソン| C++ |ジャバスクリプト/

4

アホ - Corasickのための偉大なCライブラリがあり、プロジェクトページhttp://sourceforge.net/projects/multifast/?source=dlp

を見て私もそう、私はPHP拡張を(実装PHPでアホCorasickを必要と.so)をこのライブラリのラッパーとして使用します。 私のGitHubをチェックしてください:https://github.com/ph4r05/php_aho_corasick

ライセンス:LGPL。

+0

PHPエクステンションを試しましたが、ASCIIテキストのみをサポートしていますか?針と干し草をバイナリの内容から比較したい。たとえば、$ foreach(glob( "needle/*")as $ file){$ data = array( 'key' => $ file、 'value' => file_get_contents($ file)、 'ignoreCase' => false) ; $ needle [] = $ data; } $ c = ahocorasick_init($ needle); 'これはPHP標準関数' strpos() 'で動作します –

+1

これはおそらく遅すぎるかもしれませんが、私が指摘した問題を修正しました。それは今では – ph4r05

+0

素晴らしいです、私はそれをチェックアウトします。 –

1

Aho corasickには2つのフェーズがあり、1つは最適化されたツリーを構築し、2つ目はそのツリーを使用して一致を見つけることです。辞書のアイテム数を増やすと、最初のフェーズは長くなります。 PHPにはすべてのリクエストが共有されない実行であるという問題があります。つまり、すべてのリクエストが再びすべてのディクティナリを再構築する必要があります。

最も簡単な解決策は、algorythmを2つの部分に分割し、辞書を作成してAPC(APCU)、memcache、またはその他の高速共有データストアに配置するローダを持つことです。次に、プリロードされた辞書を使用して、残りの半分で検索を実行します。

また、シェアード・ナッシング・リクエストの制限がない言語を使用して小さなRESTサーバーを作成すると、辞書をグローバル・スコープで構築し、REST経由でSerachを作成することができます。

関連する問題