【问题标题】:strpos() with multiple needles?strpos() 多针?
【发布时间】:2011-10-17 07:36:42
【问题描述】:

我正在寻找一个类似 strpos() 的函数,它有两个显着差异:

  1. 能够接受多针。我的意思是数以千计的针。
  2. 在大海捞针中搜索所有出现的针并返回起始位置数组。

当然,它必须是一种有效的解决方案,而不仅仅是通过每根针的循环。我搜索了这个论坛,也有类似的问题,比如:

但它们都不是我要找的。我使用 strpos 只是为了更好地说明我的问题,可能为此目的必须使用完全不同的东西。

我知道Zend_Search_Lucene,我很感兴趣它是否可以用来实现这一点以及如何(只是一般的想法)?

非常感谢您的帮助和时间!

【问题讨论】:

  • 你在处理什么样的数据?什么是可能的针值?你是不是例如查找整个单词或子序列?
  • 总体目标是什么?
  • 这是我的博士论文。我必须在文本中找到所有Named Entities。例如,假设我有一本包含所有国家和许多城市中的另一个的字典。我想根据这些字典搜索给定的文本。
  • 你真的想用 PHP 做这个吗? Python 具有更强大的字符串处理功能。也许你可以使用自然语言工具包:nltk.org
  • 我认为 php 足够强大,可以做到这一点。我知道像 Perl 和 Python 这样的语言能够做到这一点,但为什么 php 必须在后台。

标签: php full-text-search full-text-indexing strpos


【解决方案1】:

您可以使用正则表达式,它们支持 OR 操作。然而,与 strpos 相比,这会使其相当慢。

【讨论】:

  • 是的,使用正则表达式会使其非常慢,尤其是对于大量针头。
  • 如果只是多次调用 strpos 会怎样?
  • 你的意思是几千次?以及如何找到所有事件而不仅仅是第一个?
【解决方案2】:

这是我的策略的一些示例代码:

function strpos_array($haystack, $needles, $offset=0) {
    $matches = array();

    //Avoid the obvious: when haystack or needles are empty, return no matches
    if(empty($needles) || empty($haystack)) {
        return $matches;
    }

    $haystack = (string)$haystack; //Pre-cast non-string haystacks
    $haylen = strlen($haystack);

    //Allow negative (from end of haystack) offsets
    if($offset < 0) {
        $offset += $heylen;
    }

    //Use strpos if there is no array or only one needle
    if(!is_array($needles)) {
        $needles = array($needles);
    }

    $needles = array_unique($needles); //Not necessary if you are sure all needles are unique

    //Precalculate needle lengths to save time
    foreach($needles as &$origNeedle) {
        $origNeedle = array((string)$origNeedle, strlen($origNeedle));
    }

    //Find matches
    for(; $offset < $haylen; $offset++) {
        foreach($needles as $needle) {
            list($needle, $length) = $needle;
            if($needle == substr($haystack, $offset, $length)) {
                $matches[] = $offset;
                break;
            }
        }
    }

    return($matches);
}

我在上面实现了一个简单的蛮力方法,它适用于针和干草堆的任何组合(不仅仅是文字)。对于可能更快的算法,请查看:


其他解决方案

function strpos_array($haystack, $needles, $theOffset=0) {
    $matches = array();

    if(empty($haystack) || empty($needles)) {
        return $matches;
    }

    $haylen = strlen($haystack);

    if($theOffset < 0) {  // Support negative offsets
        $theOffest += $haylen;
    }

    foreach($needles as $needle) {
        $needlelen = strlen($needle);
        $offset = $theOffset;

        while(($match = strpos($haystack, $needle, $offset)) !== false) {
            $matches[] = $match;
            $offset = $match + $needlelen;
            if($offset >= $haylen) {
                break;
            }
        }
    }

    return $matches;
}

【讨论】:

  • 好主意!但我担心算法的同谋。对于大海捞针中的 10k 针和 10k 字符,它会调用 substr() 函数 100M。无论如何,我会检查一下并让您知道结果。谢谢!
  • @Nikola Obreshkov substr() 是内部的,所以它相当快。我的函数做了它可以做的所有优化(预先计算针的长度和将针预先铸造到弦上)。我考虑的另一个选择比这效率低得多,但我会继续思考。如果您想节省一些时间并且知道所有针头都是独一无二的,您可以删除顶部附近的array_unique()
  • 感谢您在这个问题上的努力。我认为` //如果没有数组或只有一根针,则使用 strpos if(!is_array($needles) || (count($needles) == 1 && ($needles = $needle[0] || true) )) { return strpos($haystack, $needles, $offset); } ` 必须被省略,因为它只会返回第一次出现。
  • @Nikola Obreshkov 省略。我不认为那个位只会返回第一次出现。不错的收获!基准测试进展如何?
  • 我发现了问题。通过重用$needle,我遇到了参考问题。我已经编辑了我的代码来解决这个问题。
【解决方案3】:

您似乎正在搜索整个单词。在这种情况下,这样的事情可能会有所帮助。由于它使用内置函数,它应该比自定义代码更快,但您必须对其进行分析:

$words = str_word_count($str, 2);

$word_position_map = array();

foreach($words as $position => $word) {
    if(!isset($word_position_map[$word])) {
        $word_position_map[$word] = array();
    }
    $word_position_map[$word][] = $position;
}

// assuming $needles is an array of words
$result = array_intersect_key($word_position_map, array_flip($needles));

以正确的格式存储信息(如针)将改善运行时间(例如,您不必致电 array_flip)。

str_word_count 文档中的注释:

为了这个函数的目的,'word'被定义为一个包含字母字符的依赖于语言环境的字符串,它也可以包含但不能以“'”和“-”字符开头。

所以请确保您设置正确的语言环境。

【讨论】:

  • 谢谢 Felix,我不知道 str_word_count()。我将为上述两个想法准备一个时间测试。我认为你的代码会更快。
  • 你知道一些方法可以让 str_word_count() 与 uft-8 一起工作吗?我可以做一个类似的功能来做到这一点,但不会有真正的比较。
  • @Nikola:如果您设置正确的语言环境,它应该可以工作......看看php.net/manual/en/function.setlocale.php
  • 根据 cmets str_word_count() 与 uft-8 不兼容,建议使用一些解决方法php.net/manual/en/function.str-word-count.php#85579 基于 preg_match_all()
【解决方案4】:

我知道这不能回答 OP 的问题,但想发表评论,因为此页面位于 Google 的顶部,用于多针 strpos。这是一个简单的解决方案(同样,这不是特定于 OP 的问题 - 抱歉):

    $img_formats = array('.jpg','.png');
    $missing = array();
    foreach ( $img_formats as $format )
        if ( stripos($post['timer_background_image'], $format) === false ) $missing[] = $format;
    if (count($missing) == 2)
        return array("save_data"=>$post,"error"=>array("message"=>"The background image must be in a .jpg or .png format.","field"=>"timer_background_image"));

如果将 2 个项目添加到 $missing 数组,则意味着输入不满足 $img_formats 数组中的任何图像格式。那时你知道你可以返回一个错误等。这可以很容易地变成一个小函数:

    function m_stripos( $haystack = null, $needles = array() ){
        //return early if missing arguments 
        if ( !$needles || !$haystack ) return false; 
        // create an array to evaluate at the end
        $missing = array(); 
        //Loop through needles array, and add to $missing array if not satisfied
        foreach ( $needles as $needle )
            if ( stripos($haystack, $needle) === false ) $missing[] = $needle;
        //If the count of $missing and $needles is equal, we know there were no matches, return false..
        if (count($missing) == count($needles)) return false; 
        //If we're here, be happy, return true...
        return true;
    }

回到我们使用 then 函数的第一个示例:

    $needles = array('.jpg','.png');
    if ( !m_strpos( $post['timer_background_image'], $needles ) )
        return array("save_data"=>$post,"error"=>array("message"=>"The background image must be in a .jpg or .png format.","field"=>"timer_background_image"));

当然,函数返回 true 或 false 后做什么,由你自己决定。

【讨论】:

    【解决方案5】:

    尝试多个预匹配

    if (preg_match('/word|word2/i', $str))
    

    Checking for multiple strpos values

    【讨论】:

      【解决方案6】:

      使用array_map() 的简单解决方案怎么样?

      $string = 'one two three four';
      $needles = array( 'five' , 'three' );
      
      $strpos_arr = array_map( function ( $check ) use ( $string ) {
          return strpos( $string, $check );
      }, $needles );
      

      作为返回,您将有一个数组,其中键是针的位置,值是起始位置(如果找到的话)。

      //print_r( $strpos_arr );
      Array
      (
          [0] => 
          [1] => 8
      )
      

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-04-29
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多