11

I'm trying to work with array using array_walk() function such way:

<?php

$array = array('n1' => 'b1', 'n2' => 'b2', 'n3' => 'b3');

array_walk($array, function(&$val, $key) use (&$array){
    echo $key."\n";
    if ($key == 'n1')
        $val = 'changed_b1';
    if ($key == 'n2' || $key == 'n3') {
        unset($array[$key]);
    }
});

print_r($array);

Get:

n1
n2
Array
(
    [n1] => changed_b1
    [n3] => b3
)

It seems, what after deletion of 2nd element -- 3rd element don't be sended to callback function.

4
  • 2
    there is an important note regarding changing the structure/unsetting thru array walk function in the manual, php.net/manual/en/function.array-walk.php Commented Oct 15, 2014 at 8:26
  • @Ghost, any suggestions how to solve the problem? Commented Oct 15, 2014 at 8:44
  • check out hd's answer below, or why not just use foreach Commented Oct 15, 2014 at 8:46
  • Actually this code is pretty convoluted, if you just want to end up with an array without the specific keys, use unset($array["n2"], $array["n3"]); Commented Oct 15, 2014 at 8:57

5 Answers 5

13

Use array_filter:

<?php
$filtered = array_filter($array, function($v,$k) {
    return $k !== "n2" && $k !== "n3";
}, ARRAY_FILTER_USE_BOTH);
?>

See http://php.net/array_filter

Sign up to request clarification or add additional context in comments.

2 Comments

Good to mention that it is for PHP >= 5.6.x
@m6w6, need to change some elements and unset some elements -- so array_filter() can;t be used.
7

What you can do is use a secondary array, which will give the effect that those nodes have been deleted, like;

$array = array('n1' => 'b1', 'n2' => 'b2', 'n3' => 'b3');

$arrFinal = array();
array_walk($array, function($val, $key) use (&$array, &$arrFinal){
    echo $key."\n";
    if ($key == 'n2' || $key == 'n3') {
        //Don't do anything
    } else {
       $arrFinal[$key] = $val;
    }
});

print_r($arrFinal);

1 Comment

It is seems to be a cludge, but if no other suggestions -- I will accept it.
0

From the documentation:

Only the values of the array may potentially be changed; its structure cannot be altered, i.e., the programmer cannot add, unset or reorder elements. If the callback does not respect this requirement, the behavior of this function is undefined, and unpredictable.

May be that's the reason why you don't get the desired output with your code. Hope it helps.

Possible alternatives:

  1. If you still want to use array_walk(), simply create a new array and copy the 'required' elements i.e. with indices you don't want to delete into the new array. This is a preferred alternative if number of elements to be deleted are very large.
  2. You could look into array_filter or array_map, both rely on applying a callback to every element of your array. You could simply put a condition there barring the indices you want to delete in this callback function. This would work if the number of elements you want to delete are very few.
  3. If however, the elements to delete are contiguous and form a 'portion' of an array (in your case you wanted to remove n2 and n3 which are adjacent). You can use the function array_splice

Sidenote - I am refraining from putting in any code snippets as I have linked the relevant documentations and getting started with them should be a good exercise in itself.

2 Comments

while you added an explanation (quoted from the manual) which is good, back it up with an alternative as well
@mochalygin, please see my updated answer for possible alternatives. Hope it gets you started in the right direction.
0

I realize this is several years old, but I found the thread while looking for the solution to a similar situation.

I wound up using preg_grep to return only the array that I wanted to walk, in case anyone finds it useful.

So, in my case, I wanted to ignore files in a scandir array with a "." prefix (system files), then apply a new prefix to the remaining files.

Here's what I wound up with:

$fl = array_map(function($i){
    return $new_prefix . "/" . $i;
}, preg_grep("/^[^\.]/", scandir($path)));

If you can build a regex to exclude the undesired array elements, this solution should work.

Comments

0

check the "array_walk" source code and u will see. array_walk use pos to fetch item, and in every loop, the pos move forward.

    do {
    /* Retrieve value */
    zv = zend_hash_get_current_data_ex(target_hash, &pos);
    

    /* Retrieve key */
    zend_hash_get_current_key_zval_ex(target_hash, &args[1], &pos);

    /* Move to next element already now -- this mirrors the approach used by foreach
     * and ensures proper behavior with regard to modifications. */
    zend_hash_move_forward_ex(target_hash, &pos);

    /* Back up hash position, as it may change */
    EG(ht_iterators)[ht_iter].pos = pos;

and reset pos to get value

    /* Reload array and position -- both may have changed */
    if (Z_TYPE_P(array) == IS_ARRAY) {
        pos = zend_hash_iterator_pos_ex(ht_iter, array);
        target_hash = Z_ARRVAL_P(array);
    } else if (Z_TYPE_P(array) == IS_OBJECT) {
        target_hash = Z_OBJPROP_P(array);
        pos = zend_hash_iterator_pos(ht_iter, target_hash);
    } else {
        php_error_docref(NULL, E_WARNING, "Iterated value is no longer an array or object");
        result = FAILURE;
        break;
    }  
  • if key= n1; then next pos 1
  • if key= n2; then next pos 2
  • if key= n3; then next pos 3

when run [$key == 'n2'] , the next pos is 2 ; after unset , pos 2 is unreachable ,so the loop end.

so in actually ,then $key=='n3' will not happen, and u will get the result.

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.