我正在尝试在PHP中编写一个函数,它接受一个字符串数组(needle
)并对另一个字符串数组(haystack
)进行比较。此函数的目的是为AJAX搜索快速提供匹配的字符串,因此它需要尽可能快。
这里有一些示例代码来说明两个数组;
$needle = array('ba','hot','resta');
$haystack = array(
'Southern Hotel',
'Grange Restaurant & Hotel',
'Austral Hotel',
'Barsmith Hotel',
'Errestas'
);
虽然这本身很容易,但比较的目的是计算needle
中出现haystack
个字符串的数量。
但是,有三个限制因素;
needle
必须只匹配的字符
这个词的开头。例如,
“hote”将匹配“Hotel”,但“resta”
不符合“Errestas”。needles
的数量,而不是needle
出现次数。如果某个地方被命名为“酒店酒店酒店”,我们需要结果为1
而不是3
。使用上面的例子,我们期望得到以下关联数组:
$haystack = array(
'Southern Hotel' => 1,
'Grange Restaurant & Hotel' => 2,
'Austral Hotel' => 1,
'Barsmith Hotel' => 2,
'Erresta' => 0
);
我一直在尝试使用preg_match_all()
和看起来像/(\A|\s)(ba|hot|resta)/
的正则表达式来实现一个函数来执行此操作。虽然这可以确保我们只匹配单词的开头,但它不会考虑包含相同needle
两次的字符串。
我发帖是为了看其他人是否有解决方案?
答案 0 :(得分:7)
我发现你对问题的描述足够详细,我可以采用TDD方法来解决它。因此,因为我非常想成为一名TDD人,所以我编写了测试和函数以使测试通过。 Namings可能并不完美,但它们很容易改变。函数的算法也可能不是最好的,但是现在有了测试,重构应该非常容易和无痛。
class MultiMatcherTest extends PHPUnit_Framework_TestCase
{
public function testTheComparisonIsCaseInsensitive()
{
$needles = array('hot');
$haystack = array('Southern Hotel');
$result = match($needles, $haystack);
$this->assertEquals(array('Southern Hotel' => 1), $result);
}
public function testNeedleMatchesOnlyCharsAtBeginningOfWord()
{
$needles = array('resta');
$haystack = array('Errestas');
$result = match($needles, $haystack);
$this->assertEquals(array('Errestas' => 0), $result);
}
public function testMatcherCountsNeedlesNotOccurences()
{
$needles = array('hot');
$haystack = array('Southern Hotel', 'Grange Restaurant & Hotel');
$expected = array('Southern Hotel' => 1,
'Grange Restaurant & Hotel' => 1);
$result = match($needles, $haystack);
$this->assertEquals($expected, $result);
}
public function testAcceptance()
{
$needles = array('ba','hot','resta');
$haystack = array(
'Southern Hotel',
'Grange Restaurant & Hotel',
'Austral Hotel',
'Barsmith Hotel',
'Errestas',
);
$expected = array(
'Southern Hotel' => 1,
'Grange Restaurant & Hotel' => 2,
'Austral Hotel' => 1,
'Barsmith Hotel' => 2,
'Errestas' => 0,
);
$result = match($needles, $haystack);
$this->assertEquals($expected, $result);
}
}
function match($needles, $haystack)
{
// The default result will containg 0 (zero) occurences for all $haystacks
$result = array_combine($haystack, array_fill(0, count($haystack), 0));
foreach ($needles as $needle) {
foreach ($haystack as $subject) {
$words = str_word_count($subject, 1); // split into words
foreach ($words as $word) {
if (stripos($word, $needle) === 0) {
$result[$subject]++;
break;
}
}
}
}
return $result;
}
break
语句是否必要以下测试显示何时需要break
。在break
函数中使用和不使用match
语句运行此测试。
/**
* This test demonstrates the purpose of the BREAK statement in the
* implementation function. Without it, the needle will be matched twice.
* "hot" will be matched for each "Hotel" word.
*/
public function testMatcherCountsNeedlesNotOccurences2()
{
$needles = array('hot');
$haystack = array('Southern Hotel Hotel');
$expected = array('Southern Hotel Hotel' => 1);
$result = match($needles, $haystack);
$this->assertEquals($expected, $result);
}
答案 1 :(得分:2)
数组和字符串函数通常比regexp更快。使用array_filter和substr_count的组合来完成您想要的操作应该相当容易。
干杯,
答案 2 :(得分:1)
@Ionut G. Stan哇,真是个答案!
@Lachlan McDonald 如果您有速度问题(首先尝试,而不仅仅是假设:))您可以使用针应该匹配字符串的开头:在构建过程中用第一个字母分割haystack并仅迭代与第一个字母匹配的haystack数组针的焦点。
每针不到1/10比较。
答案 3 :(得分:0)
你可以尝试:
$results=Array();
foreach ($haystack as $stack) {
$counter=0;
$lcstack=strtolower($stack);
foreach ($needle as $need) {
if (substr($lcstack,0,strlen($need))==strtolower($need)) {
$counter++;
}
}
$results[$stack]=$counter;
}