使用嵌套的 foreach 循环取消设置数组中的对象未按预期工作?
Using nested foreach loops to unset objects in array not working as expected?
我有一组具有 id
和 parentId
属性的对象。 id
值是唯一的,但多个对象可能具有相同的 parentId
值。
如果多个对象具有相同的 parentId
值,我想删除除一个之外的所有对象(即删除 "siblings")。
我以为我可以用嵌套的 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
循环后保留,但正如您所看到的,数组中的所有对象都被删除了。
我在这里错过了什么?
你的 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>';
我在你的内部循环中添加了这一行:
echo 'Matched id #'.$objOuter->id.' parent #'.$objOuter->parentId.' with id #'.$objInner->id.' parent #'.$objInner->parentId."\r\n";
产生了:
Matched id #0 parent #1 with id #1 parent #1
Matched id #1 parent #1 with id #0 parent #1
Matched id #2 parent #2 with id #3 parent #2
Matched id #3 parent #2 with id #2 parent #2
再演示(!!
表示没有match/deletion,==
表示父匹配有删除):
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
?您的外循环存在相同的 flaw/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;
}
}
快速解决方案:
$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>';
我有一组具有 id
和 parentId
属性的对象。 id
值是唯一的,但多个对象可能具有相同的 parentId
值。
如果多个对象具有相同的 parentId
值,我想删除除一个之外的所有对象(即删除 "siblings")。
我以为我可以用嵌套的 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
循环后保留,但正如您所看到的,数组中的所有对象都被删除了。
我在这里错过了什么?
你的 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>';
我在你的内部循环中添加了这一行:
echo 'Matched id #'.$objOuter->id.' parent #'.$objOuter->parentId.' with id #'.$objInner->id.' parent #'.$objInner->parentId."\r\n";
产生了:
Matched id #0 parent #1 with id #1 parent #1
Matched id #1 parent #1 with id #0 parent #1
Matched id #2 parent #2 with id #3 parent #2
Matched id #3 parent #2 with id #2 parent #2
再演示(!!
表示没有match/deletion,==
表示父匹配有删除):
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
?您的外循环存在相同的 flaw/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;
}
}
快速解决方案:
$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>';