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

时间:2015-02-19 01:59:01

标签: php arrays foreach

我有一组具有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循环之后,但正如您所看到的那样,数组中的所有对象都将被删除。

我在这里缺少什么?

3 个答案:

答案 0 :(得分:1)

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

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

所以从技术上讲,objOuter总是在第二个foreach的第一次运行中等于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>';

答案 1 :(得分:1)

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

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

产生了:

  

匹配的id#0父#1,id#1 parent#1

     

匹配id#1 parent#1,id#0 parent#1

     

匹配id#2 parent#2,id#3 parent#2

     

匹配id#3父#2,id#2 parent#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 

看模式?在您拥有$objArray已匹配的元素后,您的内循环获取最新版本的unset,而您的外部循环没有新版本,因为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的输出?你的外环存在同样的缺陷/错误。

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

$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;
    }
}

答案 2 :(得分:1)

快速解决方案:

$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>';