首页
学习
活动
专区
圈层
工具
发布
社区首页 >问答首页 >更快的Aho-Corasick PHP实现

更快的Aho-Corasick PHP实现
EN

Stack Overflow用户
提问于 2012-01-24 02:12:52
回答 4查看 2.2K关注 0票数 6

在PHP中有没有Aho–Corasick的有效实现?在维基百科的文章中提到了一个Aho-Corasick string matching in PHP

代码语言:javascript
复制
<?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) - marco.nobile@unimib.it

    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呢?

EN

回答 4

Stack Overflow用户

发布于 2012-12-24 11:48:18

Aho-Corasick有一个很棒的C库,请查看项目页面http://sourceforge.net/projects/multifast/?source=dlp

我还需要在PHP中使用Aho Corasick,所以我实现了PHP扩展(.so)作为这个库的包装器。查看我的GitHub:https://github.com/ph4r05/php_aho_corasick

许可证: LGPL。

票数 7
EN

Stack Overflow用户

发布于 2012-01-30 14:21:22

我几乎想否决它,因为已经有了一个实现。您可以将问题命名为“更快的Aho-Corasick PHP实现”之类的标题。

无论如何,PHP真的很慢,这是众所周知的。最好将处理数字的工作留给编译语言和PHP扩展。您可以将此代码重写为扩展,但最好是采用现有的C库并将其包装到扩展中,例如:

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

如果您正在寻找一种更快的解决方案,那么可以使用shell来包装您推荐的perl / python实现之一。

票数 4
EN

Stack Overflow用户

发布于 2015-04-21 06:51:50

Aho corasick有两个阶段,一个是构建优化树,第二个阶段是使用该树来查找匹配。随着字典中条目数量的增加,第一阶段会变得越来越长。PHP有一个问题,每个请求都是一个无共享的执行,这意味着每个请求都必须重新构建整个二进制。

最简单的解决方案是将algorythm分成两部分,并有一个加载器来构建字典,并将其放入APC (APCU)、memcache或其他快速共享数据存储中。然后使用预加载的字典与另一半执行搜索。

或者,使用一种没有任何共享请求限制的语言创建一个小型REST服务器,这样就可以在全局范围内构建字典,并通过REST使用pwrform服务器。

票数 2
EN
页面原文内容由Stack Overflow提供。腾讯云小微IT领域专用引擎提供翻译支持
原文链接:

https://stackoverflow.com/questions/8976472

复制
相关文章

相似问题

领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档