tags:

views:

104

answers:

3

Hi guys, i have an array structured like:

$something = array(
    0 => array(
        'label' => 'Foo',
        'items' => array(
            '123' => 4,
            '124' => 0,
        )
    ),
    1 => array(
        'label' => 'Bar',
        'items' => array(
            '125' => 5,
            '126' => 1,
        )
    ),
    2 => array(
        'label' => 'Baz',
        'items' => array(
            '127' => 0,
            '128' => 0,
        )
    )
);

And i need to remove all the 'items' key with value zero, and if an items have no childs, remove the entire block.

So, after filtering that array, i should have:

array(2){
    [0]=>
    array(2) {
        ["label"]=> "Foo"
        ["items"]=>
            array(1) {
                [123]=> 4
            }
    }
    [1]=>
    array(2) {
    ["label"]=> "Bar"
    ["items"]=>
        array(2) {
            [125]=> 5
            [126]=> 1
        }
    }
}

I've tryed using array_filter, array_walk and array_walk_recursive (this one works good - but - doesnt allow me to remove the keys in the callback function..) without success..

Have i to deconstruct and rebuild in a new one array, or am I missing the right use of array_* functions?

+1  A: 

I can't see a way to do it with array_walk_recursive, so would just go with something like this:

/**
 * Removes values from an array if the callback function is true.
 * Removes empty child arrays
 */
function array_remove_recursive(array $haystack, $f){
    if ( empty($haystack) ){
        return $haystack;
    }
    foreach ( $haystack as $key => $val ){
        if ( is_array($val){
            $haystack[$key] = array_remove_recursive($val);
            if ( empty($haystack[$key]){
                unset($haystack[$key]);
            }
        }elseif ( $f($val) === true ){
            unset($haystack[$key]);
        }
    }
    return $haystack;
}

Based on the "Each function does one thing and one thing only" principle, It is probably preferable to split it into two functions, one to remove an element if the function returns true and the other to remove empty children. This has the downside of having to walk over the array twice.

It shouldn't be too hard to convert into a function that uses references if you were passing a lot of data around.

Yacoby
+2  A: 
$something = array( .. ); // as defined above

for ( $i = 0, $iMax = count( $something ); $i < $iMax; $i++ )
{
    foreach ( $something[$i]['items'] as $key => $value )
    {
        if ( !$value )
            unset( $something[$i]['items'][$key] );
    }

    if ( count( $something[$i]['items'] ) == 0 )
        unset( $something[$i] );
}
$something = array_values( $something ); // reset indices
poke
+2  A: 

Ok, this is custom tailored for your array now. Don't expect it to work with arbitrary array structures:

class ItemFilterIterator extends RecursiveFilterIterator
{
    public function accept()
    {
        if(is_numeric($this->key()) && is_array($this->current())) {
            if(array_key_exists('items', $this->current())) {
                $items = $this->current();
                return array_sum($items['items']) > 0;
            }
        } elseif(is_numeric($this->key()) && $this->current() === 0 ) {
            return false;
        }
        return true;
    }
}

When iterating over the array, all elements are passed to the accept() method of ItemFilterIterator, which will check if the key of the current element is numeric. This applies only to top level elements and elements within items. If the current element is an array, check if there is an element with key items and if the children's values sum is greater than zero. If not, skip the element in the iteration. If it is not an array, but numeric and the value is zero, assume we are within items and skip this elements too.

You use it like this:

$iterator = new RecursiveIteratorIterator(
                new ItemFilterIterator(
                    new RecursiveArrayIterator($something)));

foreach($iterator as $key => $value) {
    echo $key, '--', $value, PHP_EOL; // or whatever else you want to do here
}

This was a fun exercise :)

More on SplIterators:

Gordon
+1 It works perfectly, thanks! But it looks like a bit overkill for my actual need.. i'll go with poke's solution
DaNieL