0

我目前正在研究一个通用表单创建类,昨天遇到了一个问题。我做了一个片段来重现这个问题。

本质上,我想在绘制整个组之后删除从原始元素数组中分组的元素,并且我在循环遍历元素数组时这样做。

代码片段应该涵盖问题,我在这里遗漏了什么吗?据我所知,在 foreach 中删除元素是完全安全和合法的,因为 foreach 在内部只使用了可以在循环期间修改的副本。

$ids = array('a' => array(), 'b' => array(), 'c' => array());
$groups['g1'] = array('a', 'c');
foreach($ids as $id => $element) {

    //var_dump($ids);
    $g_id = '';

    // search the id in all groups
    foreach($groups as $group_id => $group) {
        if(in_array($id, $group)) {
            $g_id = $group_id;
            break;
        }
    }

    // element is part of a group
    if($g_id !== '') {

        //echo $g_id;

        // element a and c gets unset within loop and should not be in $ids anymore
        foreach($groups[$g_id] as $field_id) {
            unset($ids[$field_id]);

            echo $field_id;
        }
        unset($groups[$g_id]);
    } else {
        if($id === 'a' || $id === 'c')
            echo $id;   
    }
}

元素“c”在 foreach(groups ..) 循环中未设置,但随后再次在 else 分支中输出。此外,当我在开始时使用 var_dump($fields) 时,我总是在里面得到“a”、“b”和“c”。我正在使用 PHP 5.4.7。

提前致谢

编辑:我在示例代码中犯了一个错误,现在更新了。所有关于使用错误索引(本来应该是 0,1 等)的评论当然都是正确的。使用 var_dump 时的值现在未设置,但我仍然使用 'c' 进入 else 一次。

EDIT2:我没有完成原始代码,但在阅读了评论后,我目前对上面发布的代码片段提出了以下解决方案:

$ids=array("a"=>array(),"b"=>array(),"c"=>array(),"d"=>array(),"e"=>array());
$groups=array(array("a"),array("c", "e"));
array_walk($groups,function($v,$i)use(&$ids){

    $in_both = array_intersect(array_keys($ids),$v);
    //var_dump($in_both);
    foreach($in_both as $b) {
        unset($ids[$b]);
    }
});
print_r($ids);

或者

$ids=array("a"=>array(),"b"=>array(),"c"=>array(),"d"=>array(),"e"=>array());
$groups=array(array("a"),array("c"));
array_walk($ids,function($v,$i)use(&$ids, $groups){
    $in_both = array();
    foreach($groups as $g) {
        if(in_array($i,$g)) {
            $in_both = array_intersect(array_keys($ids),$g);
        }
    }

    foreach($in_both as $b) {
        unset($ids[$b]);
    }
});
print_r($ids);

在这种情况下,使用 foreach 对我不起作用,因为我需要在循环遍历它时更改 $ids 数组。

在最基本的情况下,代码如下:

$ids = array('a', 'b');

while(count($ids)) {
    array_pop($ids);
    echo 'pop';
}

echo 'empty';

尽管 foreach 可以更改数组中的原始值,但它不会更改用于迭代的数组副本,如 nl-x 已经说明的那样。感谢 Passerby 提出使用 array_walk 的想法。

EDIT3:更新的代码再次被剪断。尽管第二个剪断的行为也未定义。在迭代数组时从数组中删除元素似乎是个坏主意。

4

6 回答 6

1

花了一些时间阅读你的代码,我你的程序是:

  • 对于 中的每个元素$ids,检查它是否存在于中的某个子数组中$groups
  • 如果存在,则删除$ids该子数组中也存在的所有内容。

按照上面的逻辑,我想出了这个:

$ids=array("a","b","c","d","e");
$groups=array(array("a","c"),array("c","e"));
array_walk($groups,function($v,$i)use(&$ids){
    $ids=array_diff($ids,$v);
});
print_r($ids);//debug

现场演示

于 2013-04-18T08:34:21.680 回答
1

克里斯,如果我理解正确,您不希望在 else 分支中输出“C”吗?

但是应该输出。你的逻辑是:

  • 你做 foreach ids 并从 id 'a' 开始。
  • 然后您从 ids 中清除 ids a 和 c并删除包含“a”的组 g1 。在此步骤中,将输出已删除的 id,即 a 和 c。(从 ids 中清除 a 和 c 不会影响foreach($ids as $id)foreach 将继续使用未触及的副本,即使在 ids 数组已被清除后也是如此。)
  • 然后你做 id 'b':在任何组中都找不到它。(其实反正现在已经没有群了)
  • 所以对于 'b' 你进入 else 分支。但是 else 分支中的 if() 阻止了输出
  • 然后你做 id 'c',在任何组中也找不到,因为你已经删除了组 g1!没有组了,记得吗?
  • 所以对于 'c' 你也进入 else 分支。这次 else 分支中的 if() 允许输出!输出只是 c

所以总输出确实是acc。

很高兴知道,即使在其元素被清除后,foreach() 仍会继续保留未触及的副本,这是特定的 PHP 事物。其他语言不一定做同样的事情。

于 2013-04-18T09:22:09.917 回答
1

$ids[$field_id]不存在,您使用的是值而不是键。

您应该使用正确的键简单地取消设置:

if (in_array($field_id, $ids))
    unset($ids[array_search($field_id, $ids)]);
于 2013-04-18T08:27:30.697 回答
1

我现在正在仔细检查。但我认为用 foreach 取消设置数组并不是很安全。

我通常会做的是进行一次 foreach,并从最高索引开始,并在此过程中降低索引。for($i = count($arr)-1; $i >= 0; $i--) { unset($array[$i]); }

我会在几分钟内编辑这篇文章。

编辑:我很困惑。$i++ 的 for 确实是罪魁祸首。foreach 是安全的(在 php 中!不是在所有语言中)

<?php

$arr = Array(1,2,3,4,5,6,7,8,9,10);
foreach ($arr as $key=>$val)
    unset($arr[$key]);
echo implode(',',$arr); // returns nothing


$arr = Array(1,2,3,4,5,6,7,8,9,10);
for ($i=0; $i<count($arr); $i++)
    unset($arr[$i]);
echo implode(',',$arr); // returns 6,7,8,9,10


$arr = Array(1,2,3,4,5,6,7,8,9,10);
for ($i=count($arr)-1; $i>=0; $i--)
    unset($arr[$i]);
echo implode(',',$arr); // returns nothing

?>
于 2013-04-18T08:28:18.823 回答
0

如果你想从数组中删除元素,你不应该用 '拼接'它array_splice吗?

来自 PHP 手册: http: //php.net/manual/en/function.array-splice.php

于 2013-04-18T08:11:58.570 回答
0

由于使用了数组的副本,因此您的 foreach 不会进行任何更改。您需要使用按引用传递才能使其正常工作。下面提到了一种方法

  while(list($key,$value) = each($array)){
   if(your reason to unset)
       unset($array[$key]);
}

这将从数组中删除元素。

于 2013-04-18T08:16:35.603 回答