检查一个单词是否是某些给定符号的组合

时间:2014-08-10 00:10:21

标签: php graph-algorithm directed-acyclic-graphs

我有一个符号数组(不仅是字符,还有音节,例如'p','pa'等),我正在努力想出一个很好的算法来识别可以创建的单词通过连接这些符号。

e.g。给定符号数组('p', 'pa', 'aw'),字符串'paw'将是正匹配。

这是我目前的实施(太慢):

function isValidWord($word,&$symbols){
    $nodes = array($word);
    while (count($nodes)>0){
        $node = array_shift($nodes);
        $nodeExpansions = array();
        $nodeLength = strlen($node);
        if (in_array($node,$symbols)) { return true; }
        for ($len=$nodeLength-1;$len>0;$len--){
            if (in_array(substr($node, 0, $len), $symbols)){
                $nodeExpansions[] = substr($node, $len-$nodeLength);
            }
        }
        $nodes = array_merge($nodeExpansions,$nodes);
    }
    return false;
}

这似乎不是一个难题,它只是一个非循环的深度优先搜索实现?树,但我正在努力想出一个内存和CPU效率的实现。我在哪里可以找到资源来了解这类问题?

此外,这里还有一个指向脚本的链接,用于测试它并将其与下面评论中提出的解决方案进行比较:http://ideone.com/zQ9Cie

这里an album显示了非常奇怪的结果:当我在我的开发服务器上运行它时,我当前的迭代方法如何比递归方法(由@Waleed Khan提出的)快12倍,但是当我在生产服务器上运行它们,考虑到两台服务器的配置几乎相同? (一个是EC2微实例,另一个是VirtualBox容器,但它们都具有相同的操作系统,配置,更新,PHP版本和配置,内核数量和可用RAM)

2 个答案:

答案 0 :(得分:0)

不确定它是否非常有效但我想我会创建一个带有内部循环的循环,该循环遍历包含符号的给定数组。

<?php
$aSymbols = array('p', 'pa', 'aw');
$aDatabase = array('paw');
$aMatches = array();

for ($iCounter = 0; $iCounter < count($aSymbols); $iCounter++)
{
    for ($yCounter = 0; $yCounter < count($aSymbols); $yCounter++)
    {
        $sString = $aSymbols[$iCounter].$aSymbols[$yCounter];
        if (in_array($sString, $aDatabase))
        {
            $aMatches[] = $sString;
        }
    }
}
?>

if查询也可以用正则表达式查询替换。

答案 1 :(得分:0)

正如@Waleed Khan建议的那样,我尝试使用字典的Trie结构而不是普通数组来改进我的算法,以加快搜索匹配。

function generateTrie(&$dictionary){

    if (is_string($dictionary)){
        $dictionary = array($dictionary);
    }
    if (!is_array($dictionary)){
        throw new Exception(
            "Invalid input argument for \$dictionary (must be array)",
            500
        );
    }

    $trie = array();

    $dictionaryCount = count($dictionary);
    $f = false;
    for ($i=0;$i<$dictionaryCount;$i++){
        $word = $dictionary[$i];
        if ($f&&!inTrie('in',$trie)){
            var_export($trie);
            exit;
        }
        if (!is_string($word)){
            throw new Exception(
                "Invalid input argument for \$word (must be string)",
                500
            );
        }
        $wordLength = strlen($word);
        $subTrie = &$trie;
        for ($j=1;$j<$wordLength;$j++){
            if (array_key_exists($subWord = substr($word,0,$j),$subTrie)){
                $subTrie = &$subTrie[$subWord];
            }
        }
        if (array_key_exists($word,$subTrie)){
            continue;
        } 
        $keys = array_keys($subTrie);
        if (!array_key_exists($word,$subTrie)) {
            $subTrie[$word] = array();
        }
        foreach ($keys as $testWordForPrefix){
            if (substr($testWordForPrefix,0,$wordLength) === $word){
                $subTrie[$word][$testWordForPrefix] = &$subTrie[$testWordForPrefix];
                unset($subTrie[$testWordForPrefix]);
            }
        }
    }

    return $trie;

}

/**
 * Checks if word is on dictionary trie
 */
function inTrie($word, &$trie){

    $wordLen = strlen($word);
    $node = &$trie;
    $found = false;
    for ($i=1;$i<=$wordLen;$i++){
        $index = substr($word,0,$i);
        if (isset($node[$index])){
            $node = &$node[$index];
            $found = true;
        } else {
            $found = false;
        }
    }

    return $found;

}
/**
 * Checks if a $word is a concatenation of valid $symbols using inTrie()
 * 
 * E.g. `$word = 'paw'`, `$symbols = array('p', 'pa', 'aw')` would return
 * true, because `$word = 'p'.'aw'`
 *
 */
function isValidTrieWord($word,&$trie){
    $nodes = array($word);
    while (count($nodes)>0){
        $node = array_shift($nodes);
        if (inTrie($node,$trie)) { return true; }
        $nodeExpansions = array();
        $nodeLength = strlen($node);
        for ($len=$nodeLength-1;$len>0;$len--){
            if (inTrie(substr($node, 0, $len), $trie)){
                $nodeExpansions[] = substr($node, $len-$nodeLength);
            }
        }
        $nodes = array_merge($nodeExpansions,$nodes);
    }
    return false;
}

对于小字典大小(其中preg_match仍然是几个数量级的最快实现),它没有太大的区别,但对于中等大小的字典(~10000个符号),其中较长的符号通常是组合较短的(其中preg中断,其他两个实现每2-6个符号字可能需要接近25秒),Trie搜索仅需1秒左右。这足以满足我的需求(检查给定密码是否是来自给定字典的符号组合)。

(参见http://ideone.com/zQ9Cie上的完整脚本)

我本地开发虚拟机上的结果:

enter image description here

我的AWS EC2测试服务器上的结果:

enter image description here