【问题标题】:How to check whether one array is a correctly ordered subset of another如何检查一个数组是否是另一个数组的正确排序子集
【发布时间】:2020-03-10 14:01:59
【问题描述】:

我有一些字符串数组,例如:

$big = ['html', 'body', 'div', 'table', 'tbody', 'tr', 'td'];
$small = ['body', 'div', 'td'];
$wrong = ['td', 'body', 'div'];

我需要检查$small$wrong 是否可以在$big 中找到。但是我需要顺序相同。所以我的函数应该为$small返回true,为$wrong返回false。手动完成应该相当简单,但我需要代码快速。因此,理想情况下,如果有一个内置功能可以实现这一点,我宁愿使用它。

所以问题主要是这样的内置是否存在。这是我想出的代码,以防万一:

/**
 * Returns whether the substack is contained in the stack in the correct order.  
 * 
 * @param string[] $stack           The substack to check
 * @param string[] $subStack        The substack to check 
 * @return bool
 */
function stackInStack(array $stack, array $subStack)
{

    // First let's do a simple array diff to save time on an ordered diff;
    // TODO: Check if this actually improves average performance.
    if (count(array_diff($subStack, $stack)) !== 0) return false;

    $stackSize = count($stack);
    $subStackSize = count($subStack);

    $stackIndex = 0;
    for ($subIndex = 0; $subIndex < $subStackSize; $subIndex++) {

        while (
            $stackIndex < $stackSize &&
            $stack[$stackIndex] !== $subStack[$subIndex]
        ) {
            $stackIndex++;
        }

        if ($stackIndex == $stackSize) {
            if ($subIndex <= $subStackSize - 1) {
                return false;
            } elseif ($subIndex > $subStackSize - 1) {
                throw new Exception('Very Strange Exception: subIndex has outgrown subStacksize');
            }
        } elseif ($stackIndex > $stackSize) {
            throw new Exception('Very Strange Exception: index has outgrown stacksize');
            break;
        } 

    }
    return true;
}

如果内置不存在或速度很慢,任何提高上述代码效率的技巧(除了用 c 重写)也将不胜感激。

【问题讨论】:

  • 喂?你放弃了吗???

标签: php arrays optimization php-7


【解决方案1】:

这比你的版本短一点,它使用array_intersect() 计算出两个数组中的公共元素,然后将结果与子堆栈进行比较,看看它们是否相同......

$big = ['html', 'body', 'div', 'table', 'tbody', 'tr', 'td'];
$small = ['body', 'div', 'td'];
$wrong = ['td', 'body', 'div'];

function stackInStack(array $stack, array $subStack)
{
    return array_values(array_intersect($stack, $subStack)) == $subStack;
}

var_dump(stackInStack($big, $small));
var_dump(stackInStack($big, $wrong));

只是为了说明我的意思

print_r(array_intersect($big, $wrong));

给...

Array
(
    [1] => body
    [2] => div
    [6] => td
)

所以将此与$wrong 进行比较,它的顺序不同。

【讨论】:

  • 如果$big 有重复项怎么办? $big = ['html', 'body', 'div', 'body','table', 'tbody', 'tr', 'td'];
  • @vivek_23,你怎么知道你想与子数组匹配哪一个? $small = ['body', 'div']; 变得模棱两可。
  • 我假设 OP 只是希望序列与$small 相同,这意味着测试$small 是否是$big 的子序列。可以匹配第一个 body 而不是第二个 body
  • 这是一个非常类似于 css 或 xpath 选择器的东西,所以@vivek_23 是正确的。堆栈是所有 xml 节点,包括当前正在由 XMLReader 处理的节点,子堆栈是确定是否存储当前节点的参数。我认为上下文不会非常相关,并认为尽可能概括这个问题会更好。 array_intesect 方法非常聪明,也许对其他人有用,但对于我的用例,它会输出假阴性。出于好奇,我可能会在明天进行基准测试。
  • 我要指出的一点是,如果您正在阅读 HTML,XMLReader 可能无法处理您在现实世界中看到的许多内容。任何轻微损坏都可能导致问题,因为 XHTML 比 HTML 严格得多。还有一些用例无法简单匹配层次结构(我为 JSON 流解析器做了类似的事情)。
【解决方案2】:

假设您的数组不是太大,您可以改用字符串比较。像这样的:

<?php

    $big = ['html', 'body', 'div', 'table', 'tbody', 'tr', 'td'];
    //$small = ['body', 'div', 'td']; // This is the original
    $small = ['body', 'div', 'table']; // This is for testing
    $wrong = ['td', 'body', 'div'];

    $bigToken = implode($big, ''); // Output: htmlbodydivtabletbodytrtd
    $smallToken = implode($small, ''); // Output: bodydivtable
    $wrongToken = implode($wrong, ''); // Output: tdbodydiv

    if (stristr($bigToken, $smallToken) !== false) {
        echo("Small is in big!");
    }
    elseif (stristr($bigToken, $wrongToken) !== false) {
        echo("Wrong is in big!");   
    }
    else {
        echo("No match found :)");
    }

?>

它基本上将数组转换为字符串并检查其中是否包含另一个字符串。性能方面,这完全取决于您的实际数组有多大,但这确保了正确的顺序并且更易于维护。


正如下面的 cmets 中所指出的,最好使用某种标记来implode,以确保在可能发生冲突的情况下正确分离标签。

【讨论】:

  • 我认为 substr_count() 比 stristr() 更快
  • 不错的方法顺便说一句:)
  • 谢谢,是的,这可能是真的。我从未对其进行基准测试。
  • 对于那些想要优化这个stackoverflow.com/questions/20269745/…
  • 也许分隔符是个好主意,因为目前没有什么可以检查 tbodyt,body 之类的内容当前是否是同一个字符串。
【解决方案3】:

只需使用简单的指针来遍历$needle,这是一个较小的数组,可以作为较大数组中的子序列找到。一旦找到匹配,增加指针,否则继续移动。如果指针达到较小数组的长度,宾果游戏,它是一个子序列,否则它不是。

片段:

<?php

$big = ['html', 'body', 'div', 'body','table', 'tbody', 'tr', 'td'];
$small = ['body', 'div', 'td'];
$wrong = ['td', 'body', 'div'];


function isSubsequence($haystack,$needle){
    if(count($needle) > count($haystack)) return false;
    $keys = array_keys($needle);
    $ptr = 0;
    $len = count($needle);

    foreach($haystack as $element){
        if($ptr === $len) return true;
        if($needle[$keys[$ptr]] === $element) $ptr++;
    }

    return $ptr === $len;
}


var_dump(isSubsequence($big, $small));
var_dump(isSubsequence($big, $wrong));

演示: https://3v4l.org/vqBMj

【讨论】:

  • 您的解决方案比我的解决方案行数更少,更易于阅读和理解。我相信它还可以处理关联数组,我的可能会失败(也没有测试过)。我会接受你的回答。但是您应该修改它以考虑我在下一条评论中关于 count() 有多昂贵的发现。最后,我可能会选择使用序列而不是数组的解决方案的变体,前提是它的基准测试更好。
  • 所以我做了一些基准测试。最初,您的解决方案测试速度慢了 7%。当我删除所有早期条件时,他们进行了相同的测试。当我将初始条件添加到您的函数中时,您的执行速度比我的快 2-3%。事实证明,count 非常昂贵,而 array_diff 并不像人们想象的那么昂贵......
  • 最初我认为 array diff 实际上比 count 更昂贵,但是 haystack 小于 needle 的情况是非典型的(这就是为什么我一开始甚至没有测试它),这就是为什么你的运行速度较慢。但是当我将 10% 的此类情况添加到我的测试数据中时,您的函数的性能开始比我的差 20%。我认为这证明 array_diff 实际上比某种方式更快
  • @Kaan 有趣的指标。如果您在我的代码if(count($needle) &gt; count($haystack)) return false; 中删除此早期检查,会发生什么情况?此外,PHP 中的 count() 发生在 O(1) 时间内。见这里stackoverflow.com/questions/5835241/…
  • @Kaan 另外,数组在 PHP 中是按值传递的。因此,函数参数是传递数组的新副本。将函数定义从 function isSubsequence($haystack,$needle){ 更改为 function isSubsequence(&amp;$haystack,&amp;$needle){ 会提高性能吗?因为这里我们指示它使用相同的数组而不是制作新的副本。
【解决方案4】:

这几乎与Nigel Ren's 重复,但我是在另一个窗口中输入的。

计算将按照第一个数组的顺序的交集,重新索引两者并将交集与原始数组进行比较:

$result = array_values(array_intersect($big, $small)) === array_values($small);
// true

$result = array_values(array_intersect($big, $wrong)) === array_values($wrong);
// false

【讨论】:

  • 如果$big 有重复项怎么办? $big = ['html', 'body', 'div', 'body','table', 'tbody', 'tr', 'td'];
【解决方案5】:

这也适用于关联数组:

"array_search()" 将返回值对应的键, 所以你可以看看顺序是否正确......

function is_array_in_array($array, $inarray){
    $pos = -1; $match = 0;
    $array = array_values($array);
    $inarray = array_values($inarray);
    foreach($array as $v){
        $p = array_search($v, $inarray);
        if($p===false || $p<=$pos) return false;
        $pos = $p; $match++;
    }
    if(count($array)==$match) return true;
    return false;
}

它会返回:

$small_assoc = array('A'=>'body', 'B'=>'div', 'C'=>'td');

is_array_in_array($small, $big);  # =1
is_array_in_array($wrong, $big);  # =0
is_array_in_array($big, $big);    # =1
is_array_in_array($small_assoc, $big); # =1

【讨论】:

    猜你喜欢
    • 2010-09-24
    • 2016-12-13
    • 2012-05-20
    • 1970-01-01
    • 1970-01-01
    • 2013-06-01
    • 2021-07-10
    • 1970-01-01
    相关资源
    最近更新 更多