在一个二维数组中计算“真值”(即非零或非空)的数量

12

给定以下数组 $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
        )

)
当我运行count(array_filter($mm))时,由于它不是递归的,所以得到的结果是3count(array_filter($mm), COUNT_RECURSIVE)也行不通,因为我实际上需要对array_filter递归地运行,然后统计其结果。
那么我的问题是:在这种情况下如何递归地运行 array_filter($mm) ?我期望的结果是4
请注意,我没有使用任何回调函数,因此可以排除 false、null 和空值。

1
你只是想计算数组中有值的元素吗? - Jared
@jrod,那是正确的,但不是0。 - pepe
8个回答

33

根据PHP array_filter文档:

//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); 
  } 
?>

这也会删除空值和0吗? - pepe
@torr - 这将与array_filter通常的工作方式相同。如果您查看函数,它会调用array_filter,除非当前值是数组,在这种情况下,它将调用array_filter_recursive函数。 - Francois Deschenes
@FrancoisDeschenes:带回调的array_filter_recursive无法按预期工作,递归时会出现数组转换为字符串的错误。 - Qarib Haider
不适用于提供的回调函数。请参见我的答案以获取可工作的函数。 - helpse
如果我没有传递回调函数(回调函数为null),那么结果也是null :/ 无法弄清楚为什么。 - Karmalakas

11

应该能够正常工作

$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);

或者可能

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

还有许多其他可能的解决方案。如果你想使用array_filter()(不带回调函数),请记住它也将0视为false,因此它将从数组中删除任何0值。

如果您在使用PHP的早期版本(低于5.3),我建议使用foreach循环。

$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']);
}

更新

关于下面的评论:

感谢 @kc,我实际上想要删除假、0、空等内容的方法。

如果这是你真正想要的,解决方案也非常简单。但现在我不知道如何解释:

我的预期结果应该是 5。

无论如何,它现在很简短了 :)

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

得到的数组看起来像是

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

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

)

谢谢@kc,我实际上希望这个方法可以去除false、0、空等。 - pepe
你的陈述“我的预期结果是5。”相当误导人,即使删除任何false值,count($array)仍然不会返回0 - KingCrunch
"...仍然没有返回5"。它计算数组的元素,而数组本身是一个元素,因此将被计算为1(我只是不想让这个注释成为一个愚蠢的“想要修复我的拼写错误”..) - KingCrunch

8

更好的选择

对我来说一直有效的一个实现是这个:

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;
}

我注意到有人创建了一个类似的函数,但是在我看来,这个函数几乎没有任何优势:
1. 你传递的是数组的引用(而不是它的副本),因此该算法更加友好,占用内存更少。
2. 不需要额外调用 array_filter,因为实际上这将涉及以下操作:
- 使用堆栈,即额外的内存。 - 其他一些操作,即 CPU 周期。
基准测试:
1. 一个 64MB 的数组:
- filter_me 函数在 0.8 秒内完成,开始函数之前 PHP 分配的内存为 65 MB,在函数返回时为 39.35 MB!!!
- Francois Deschenes 建议使用的 array_filter_recursive 函数无法胜任;1 秒后出现 PHP 致命错误:允许的内存大小为 134217728 字节已耗尽。
2. 一个 36MB 的数组:
- filter_me 函数在 0.4 秒内完成,开始函数之前 PHP 分配的内存为 36.8 MB,在函数返回时为 15 MB!!!
- array_filter_recursive 函数此次成功,在 0.6 秒内完成,之前/之后的内存基本相同。
希望这可以帮到你。

1
这个函数有效地应用了提供的回调函数来执行递归过滤。
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;
    }

}

"而且,您可以这样使用它:

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

这可能对某人有帮助


这可能会有所帮助,所以了解一下是好的。无论如何,问题与如何过滤掉那些“空”记录有关,我的答案侧重于提供一个更好的替代方案来解决这个问题。但我同意你的递归过滤版本具有更广泛的用途。 - Eugen Mihailescu
1
array_filter难道不应该在回调函数返回“true”时保留原始值吗?在这种情况下,您将原始值强制更改为“true”。 - Jonathan
破坏了输入数组。 - Mulan

0

这应该适用于回调和模式支持,以及对深度的可选支持。

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);
}

使用$depth = 0调用函数来处理嵌套数组,将产生与array_filter相同的结果。


0
这让我想到了一个XY问题
  1. 递归是不必要的,因为数组的深度始终保持在2级。
  2. 不需要生成一个过滤后的元素数组,以便遍历过滤后的数据来计数。只需遍历一次,并在遇到真值时将1添加到计数变量中。

以下代码片段不调用任何函数(仅使用语言结构--foreach()),因此效率非常高。

代码:(演示

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

0
我需要一个数组过滤递归函数,它可以遍历所有节点(包括数组,以便我们有可能丢弃整个数组),于是我想出了这个:

    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;
    }

在此处查看更多示例:https://github.com/lingtalfi/Bat/blob/master/ArrayTool.md#filterrecursive


这会改变输入数组。 - Mulan

-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;
?>

好主意 - 在这种情况下,可以使用array_filter代替条件和$count++,对吧? - pepe

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接