12

Given the following array $mm

Array
(
    [147] => Array
        (
            [pts_m] => 
            [pts_mreg] => 1
            [pts_cg] => 1
        )    
    [158] => Array
        (
            [pts_m] => 
            [pts_mreg] => 
            [pts_cg] => 0
        )

    [159] => Array
        (
            [pts_m] => 
            [pts_mreg] => 1
            [pts_cg] => 1
        )

)

When I run count(array_filter($mm)) I get 3 as result since it is not recursive.

count(array_filter($mm), COUNT_RECURSIVE) also will not do because I actually need to run the array_filter recursively, and then count its result.

So my question is: how do I recursively run array_filter($mm) in this case? My expected result here would be 4.

Please note that I am not using any callback so I can exclude false, null and empty.

2
  • 1
    Are you simply trying to count the array elements that have a value? Commented Jul 22, 2011 at 20:21
  • @jrod that's correct, but not 0 Commented Jul 22, 2011 at 20:31

8 Answers 8

33

From the PHP array_filter documentation:

//This function filters an array and remove all null values recursively. 

<?php 
  function array_filter_recursive($input) 
  { 
    foreach ($input as &$value) 
    { 
      if (is_array($value)) 
      { 
        $value = array_filter_recursive($value); 
      } 
    } 

    return array_filter($input); 
  } 
?> 

//Or with callback parameter (not tested) : 

<?php 
  function array_filter_recursive($input, $callback = null) 
  { 
    foreach ($input as &$value) 
    { 
      if (is_array($value)) 
      { 
        $value = array_filter_recursive($value, $callback); 
      } 
    } 

    return array_filter($input, $callback); 
  } 
?>
Sign up to request clarification or add additional context in comments.

5 Comments

would this also remove empty and 0?
@torr - This would work the same way as array_filter normally does. If you look in the function, it calls array_filter unless it's the current value is an array at which point it calls the array_filter_recursive function.
@FrancoisDeschenes: array_filter_recursive with callback does not work as expected, it gives array to string conversion errors while recursion..
Does not work with callback provided. See my answer for a working function
If I do not pass a callback (callback is null), the result is also null :/ Can't figure out why
11

Should work

$count = array_sum(array_map(function ($item) {
  return ((int) !is_null($item['pts_m'])
       + ((int) !is_null($item['pts_mreg'])
       + ((int) !is_null($item['pts_cg']);
}, $array);

or maybe

$count = array_sum(array_map(function ($item) {
  return array_sum(array_map('is_int', $item));
}, $array);

There are definitely many more possible solutions. If you want to use array_filter() (without callback) remember, that it treats 0 as false too and therefore it will remove any 0-value from the array.

If you are using PHP in a pre-5.3 version, I would use a foreach-loop

$count = 0;
foreach ($array as $item) {
  $count += ((int) !is_null($item['pts_m'])
          + ((int) !is_null($item['pts_mreg'])
          + ((int) !is_null($item['pts_cg']);
}

Update

Regarding the comment below:

Thx @kc I actually want the method to remove false, 0, empty etc

When this is really only, what you want, the solution is very simple too. But now I don't know, how to interpret

My expected result here would be 5.

Anyway, its short now :)

$result = array_map('array_filter', $array);
$count = array_map('count', $result);
$countSum = array_sum($count);

The resulting array looks like

Array
(
[147] => Array
    (
        [pts_mreg] => 1
        [pts_cg] => 1
    )    
[158] => Array
    (
    )

[159] => Array
    (
        [pts_mreg] => 1
        [pts_cg] => 1
    )

)

3 Comments

Thx @kc I actually want the method to remove false, 0, empty etc
Your statement "My expected result here would be 5." is quite misleading then and after remove any false-evaluated value count($array) will still not return 0.
"... still not return 5". It counts the elements of the array and an array is an element and therefore will get counted as 1 (I just don't wanted this comment to be a silly "want to fix my typo"..)
8

A better alternative

One implementation that always worked for me is this one:

function filter_me(&$array) {
    foreach ( $array as $key => $item ) {
        is_array ( $item ) && $array [$key] = filter_me ( $item );
        if (empty ( $array [$key] ))
            unset ( $array [$key] );
    }
    return $array;
}

I notice that someone had created a similar function except that this one presents, in my opinion, few advantages:

  1. you pass an array as reference (not its copy) and thus the algorithm is memory-friendly
  2. no additional calls to array_filter which in reality involves:
    • the use of stack, ie. additional memory
    • some other operations, ie. CPU cycles

Benchmarks

  1. A 64MB array
    • filter_me function finished in 0.8s AND the PHP allocated memory before starting the function was 65MB, when function returned it was 39.35MB !!!
    • array_filter_recursive function recommended above by Francois Deschenes had no chance; after 1s PHP Fatal error: Allowed memory size of 134217728 bytes exhausted
  2. A 36MB array
    • filter_me function finished in 0.4s AND the PHP allocated memory before starting the function was 36.8MB, when function returned it was 15MB !!!
    • array_filter_recursive function succeeded this time in 0.6s and memory before/after was quite the same

I hope it helps.

Comments

1

This function effectively applies filter_recursive with a provided callback

class Arr {

    public static function filter_recursive($array, $callback = NULL)
    {
        foreach ($array as $index => $value)
        {
            if (is_array($value))
            {
                $array[$index] = Arr::filter_recursive($value, $callback);
            }
            else
            {
                $array[$index] = call_user_func($callback, $value);
            }

            if ( ! $array[$index])
            {
                unset($array[$index]);
            }
        }

        return $array;
    }

}

And you'd use it this way:

Arr::filter_recursive($my_array, $my_callback);

This might help someone

3 Comments

It may help so it's good to know. Anyway, the question was related to how to filter out those 'empty' records and my answer focused to provide a better alternative with respect to that problem. But I agree that your version of recursive filter has a broader usage.
Isn't array_filter supposed to preserve the original value if callback returns true? In this case you are slamming the original value to true
destroys the input array
0

I needed an array filter recursive function that would walk through all nodes (including arrays, so that we have the possibility to discard entire arrays), and so I came up with this:


    public static function filterRecursive(array $array, callable $callback): array
    {
        foreach ($array as $k => $v) {
            $res = call_user_func($callback, $v);
            if (false === $res) {
                unset($array[$k]);
            } else {
                if (is_array($v)) {
                    $array[$k] = self::filterRecursive($v, $callback);
                }
            }
        }

        return $array;
    }

See more examples here: https://github.com/lingtalfi/Bat/blob/master/ArrayTool.md#filterrecursive

1 Comment

this mutates the input array
0

This should work for callback and mode support along with an optional support for depth.

function array_filter_recursive(array $array, callable $callback = null, int $mode = 0, int $depth = -1): array
{
    foreach ($array as & $value) {
        if ($depth != 0 && is_array($value)) {
            $value = array_filter_recursive($value, $callback, $mode, $depth - 1);
        }
    }

    if ($callback) {
        return array_filter($array, $callback, $mode);
    }

    return array_filter($array);
}

Calling the function with $depth = 0 for nested arrays, will yield the same result as array_filter.

Comments

-1
<?php

$mm = array
(
    147 => array
        (
            "pts_m" => "",
            "pts_mreg" => 1,
            "pts_cg" => 1
        ) ,
    158 => array
        (
            "pts_m" => null ,
            "pts_mreg" => null,
            "pts_cg" => 0
        ),

    159 => array
        (
            "pts_m" => "",
            "pts_mreg" => 1,
            "pts_cg" => 1
        )

);

$count = 0;
foreach ($mm as $m) {
    foreach ($m as $value) {
        if($value !== false && $value !== "" && $value !== null) {
            $count++;
        }
    }
}
echo $count;
?>

1 Comment

Good idea - in that case array_filter could be used instead of the conditionals and $count++, no?
-1
  1. Recursion is not necessary because the array has a consistent depth of 2 levels.
  2. It is not necessary to generate an array of filtered elements so that you can traverse the filtered data to count it. Just traverse once and cast each value to a boolean and add that (which will be 0 or 1 when added) to the count total.

The following snippet calls no functions (only language constructs -- foreach()) and therefore will be highly efficient.

Code: (Demo)

$truthyCount = 0;
foreach ($array as $row) {
    foreach ($row as $v) {
        $truthyCount += (bool)$v;
    }
}
var_export($truthyCount);

For a functional-style approach, sum the mapped counts of all truthy values. Demo

var_export(
    array_sum(
        array_map(
            fn($row) => count(array_filter($row)),
            $array
        )
    )
);

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.