使用嵌套的foreach循环取消设置数组中未按预期工作的对象


Using nested foreach loops to unset objects in array not working as expected?

我有一个具有idparentId属性的对象数组。id值是唯一的,但多个对象可能具有相同的parentId值。

如果不止一个对象具有相同的parentId值,我希望删除除一个以外的所有对象(即删除"同级对象")。

我原以为使用嵌套的foreach循环可以很容易地做到这一点,但它并没有像我预期的那样工作。

这里有一个例子:

$objArray = [];
for($i = 0; $i < 2; $i++) {
    $obj = new stdClass();
    $obj->id = $i;
    $obj->parentId = 1;
    $objArray[] = $obj;
}
for($i = 2; $i < 4; $i++) {
    $obj = new stdClass();
    $obj->id = $i;
    $obj->parentId = 2;
    $objArray[] = $obj;
}
echo 'Before unsetting siblings:<pre>';
print_r($objArray);
echo '</pre>';
// loop over $objArray and remove elements with the same ->parentId (leaving one)
foreach ($objArray as $keyOuter => $objOuter) {
    foreach ($objArray as $keyInner => $objInner) {
        if ($objInner->id != $objOuter->id             // if the inner object is NOT the same as the outer object (i.e. it's a different object)
         && $objInner->parentId == $objOuter->parentId // and if the parent IDs are the same
        ) {
            unset($objArray[$keyInner]); // unset the inner object
        }
    }
}
echo 'After unsetting siblings:<pre>';
print_r($objArray);
echo '</pre>';

输出:

Before unsetting siblings:
Array
(
    [0] => stdClass Object
        (
            [id] => 0
            [parentId] => 1
        )
    [1] => stdClass Object
        (
            [id] => 1
            [parentId] => 1
        )
    [2] => stdClass Object
        (
            [id] => 2
            [parentId] => 2
        )
    [3] => stdClass Object
        (
            [id] => 3
            [parentId] => 2
        )
)
After unsetting siblings:
Array
(
)

我希望数组中的第一个和第三个对象在foreach循环后保留,但正如您所看到的,数组中的所有对象都被删除了。

我在这里错过了什么?

我在您的内部循环中添加了这一行:

echo 'Matched id #'.$objOuter->id.' parent #'.$objOuter->parentId.' with id #'.$objInner->id.' parent #'.$objInner->parentId."'r'n";

结果:

匹配id#0父#1和id#1父#1

匹配id#1父#1和id#0父#1

匹配id#2父#2和id#3父#2

匹配id#3父#2和id#2父#2

另一个演示(!!表示没有匹配/删除,==表示有删除的父匹配):

0/1!!0/1 0/1==1/1 0/1!!2/2 0/1!!3/2 
1/1==0/1 1/1!!2/2 1/1!!3/2 
2/2!!2/2 2/2==3/2 
3/2==2/2 

看到图案了吗?当您的内循环在拥有unset(已匹配的元素)后获得$objArray的最新版本时,外循环没有新版本,因为foreach实际上为$objOuter$keyOuter值保留了一个临时克隆数组。

这是一个概念验证:

$array = array(1,2,3,4);
foreach ($array as $a) {
    if (isset($array)) unset($array);
    echo $a;
}

输出:

1234

或者这个:

$array = array(1,2,3,4);
foreach ($array as $a) {
    $array[3] = 100;
    echo $a; // Output is still 1234
}

或者这个:

$array = array(1,2,3,4);
foreach ($array as $a) {
    if (isset($array[3])) unset($array[3]);
    echo $a; // Yet again 1234
}

如果$array已经不存在了,为什么循环仍在继续?按照同样的逻辑,为什么我的第二个例子的输出不是123100?外循环也存在相同的缺陷/bug。

我宁愿创建一个新的过滤数组,而不是试图用嵌套循环从原来的数组中删除:

$newArray = array();
foreach ($objArray as $obj) {
    if (!isset($newArray[$obj->parentId])) { // Use the index to test for existing parent IDs
        $newArray[$obj->parentId] = $obj;
    }
}
// Optional - use array_values to get rid of parentId in the indices
$newArray = array_values($newArray);
// or you can just do this if you want to replace $objArray
$objArray = array_values($newArray);
unset($newArray);

您还可以保留另一个现有父密钥数组,如果密钥已经存在,则从现有数组中删除:

$existingParents = array();
foreach ($objArray as $key => $obj) {
    if (isset($existingParents[$obj->parentId])) {
        unset($objArray[$key]);
    } else {
        $existingParents[$obj->parentId] = true;
    }
}

for循环的前两行是相同的,请使用var_dump确保:

foreach ($objArray as $keyOuter => $objOuter) {
    foreach ($objArray as $keyInner => $objInner) { 
        var_dump($objArray, $objInner); //the same object is returned
        ...

所以从技术上讲,在第二个foreach的第一次运行中,objOuter将始终等于objInner,因此删除了对象,最后您将删除所有对象。

对于这段代码,给您的变量一个不同的名称:

for($i = 2; $i < 4; $i++) {
    $obj = new stdClass();
    $obj->id = $i;
    $obj->parentId = 2;
    $objArray2[] = $obj; //instead of $objArray
}

这是最后的代码,它按照您希望的方式工作:

for($i = 0; $i < 2; $i++) {
    $obj = new stdClass();
    $obj->id = $i;
    $obj->parentId = 1;
    $objArray[] = $obj;
}
for($i = 2; $i < 4; $i++) {
    $obj = new stdClass();
    $obj->id = $i;
    $obj->parentId = 2;
    $objArray2[] = $obj;
}
echo 'Before unsetting siblings:<pre>';
print_r($objArray);
echo '</pre>';
foreach ($objArray as $keyOuter => $objOuter) {
    foreach ($objArray2 as $keyInner => $objInner) {
        if ($objInner->id != $objOuter->id 
            && $objInner->parentId == $objOuter->parentId) {
        unset($objArray[$keyInner]); // unset the inner object
        }
    }
}
echo 'After unsetting siblings:<pre>';
print_r($objArray);
echo '</pre>';

快速解决方案:

$objArray = [];
for($i = 0; $i < 2; $i++) {
    $obj = new stdClass();
    $obj->id = $i;
    $obj->parentId = 1;
    $objArray[] = $obj;
}
for($i = 2; $i < 4; $i++) {
    $obj = new stdClass();
    $obj->id = $i;
    $obj->parentId = 2;
    $objArray[] = $obj;
}
echo 'Before unsetting siblings:<pre>';
print_r($objArray);
echo '</pre>';
// loop over $objArray and remove elements with the same ->parentId (leaving one)
$max = count($objArray);
for ($i=0; $i<$max; $i++) {
    $objOuter = $objArray[$i];
    foreach ($objArray as $i2=>$objInner) {
        if ($objInner->id != $objOuter->id             // if the inner object is NOT the same as the outer object (i.e. it's a different object)
         && $objInner->parentId == $objOuter->parentId // and if the parent IDs are the same
        ) {
            unset($objArray[$i2]);
            $max=$max-1;// unset the inner object
        }
    }
}
echo 'After unsetting siblings:<pre>';
print_r($objArray);
echo '</pre>';