【问题标题】:Unsetting elements from array within foreach从 foreach 中的数组中取消设置元素
【发布时间】:2013-04-11 06:04:30
【问题描述】:

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

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

代码 sn-p 应该可以解决问题,我在这里遗漏了什么吗?据我所知,在 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。

提前致谢

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

编辑2: 我没有完成原始代码,但是在阅读了 cmets 之后,我目前对上面发布的代码 sn-p 提出了以下解决方案:

$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 的想法。

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

【问题讨论】:

  • here。我建议改用for 循环。
  • $ids[$field_id] 在您的示例中不存在,因为它将解释为$ids["a"],而不是$ids[0]。另外,我很难回答你的问题,你介意让它更清楚一点,比如“我有两个看起来像这样 [数组代码] 的数组,我想删除 if...这样以后可以看起来像这样[数组代码]。”?
  • @Passerby 优秀的观察+1。但是该错误不会改变输出中的任何内容。如果您将$ids = array('a', 'b', 'c'); 更改为$ids = array('a'=>'a', 'b'=>'b', 'c'=>'c');,则不会发生任何变化。
  • @nl-x 正如我所说,我不太了解 OP 的问题/逻辑。我发现这个错误只是因为它有点明显。
  • @nl-x:这里真正的问题不是改变输出,而是取消设置数组元素。

标签: php foreach unset


【解决方案1】:

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

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

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

所以总输出确实是acc。

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

【讨论】:

  • 这个答案可能正是我的问题。实际代码比我的示例要复杂得多,但我一定会看看并报告。
【解决方案2】:

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

  • 对于$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

Live demo

【讨论】:

    【解决方案3】:

    我现在正在仔细检查。但我认为用 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
    
    ?>
    

    【讨论】:

    • 为什么使用 foreach 不安全?
    • @bestprogrammerintheworld 我应该感到困惑。查看我的编辑
    • ok :-) 只是出于好奇 - foreach 使用哪种语言不安全(使用这种功能)
    • @bestprogrammerintheworld 我真的不确定了。但我认为是 Actionscript 我遇到了这个问题。
    【解决方案4】:

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

    您应该简单地使用右键取消设置:

    if (in_array($field_id, $ids))
        unset($ids[array_search($field_id, $ids)]);
    

    【讨论】:

    • 这不会改变输出,因为我也回复过路人
    • 这已经过测试,并且有效...这不会改变您的输出,因为您只是回显$field_id...您应该在脚本末尾尝试print_r($ids);。跨度>
    • 感谢您的回答,我更新了我的帖子,我的示例代码中有一个错误,这不是问题,但您当然得到了支持 :)
    【解决方案5】:

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

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

    【讨论】:

      【解决方案6】:

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

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

      这将从数组中删除元素。

      【讨论】:

      • 不幸的是,OP 使用原始:unset($ids[$field_id]);。而且它不是副本
      • 我只是想澄清一下。
      猜你喜欢
      • 2011-02-20
      • 2011-03-04
      • 2013-10-23
      • 2018-07-16
      • 1970-01-01
      • 2011-11-02
      • 2019-04-13
      • 1970-01-01
      • 2021-05-29
      相关资源
      最近更新 更多