关于php:array_map,array_walk和array_filter之间的区别

Difference between array_map, array_walk and array_filter

array_maparray_walkarray_filter的区别是什么?从文档中我可以看到,您可以传递回调函数来对所提供的数组执行操作。但我似乎没有发现它们之间有什么特别的区别。

他们做同样的事吗?它们可以互换使用吗?

如果有不同的例子,我将非常感谢您的帮助。


  • 更改值:
    • array_map不能更改输入数组中的值,而array_walk可以更改;特别是array_map从不更改其参数。
  • 数组键访问:
    • array_map不能用数组键操作,array_walk可以。
  • 返回值:
    • array_map返回一个新数组,array_walk只返回true/false。因此,如果不希望通过遍历一个数组而创建数组,则应使用array_walk
  • 迭代多个数组:
    • array_map也可以接收任意数量的数组,它可以并行迭代这些数组,而array_walk只对一个数组进行操作。
  • 将任意数据传递给回调:
    • array_walk可以接收一个额外的任意参数来传递给回调。这几乎与php 5.3(引入匿名函数时)无关。
  • 返回数组的长度:
    • array_map的结果数组的长度与最大输入数组的长度相同;array_walk不返回数组,但同时不能改变原始数组的元素数;array_filter根据过滤函数只选取数组元素的一个子集。它确实保留了钥匙。

例子:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
[cc lang="php"]
<?php

$origarray1 = array(2.4, 2.6, 3.5);
$origarray2 = array(2.4, 2.6, 3.5);

print_r(array_map('floor', $origarray1)); // $origarray1 stays the same

// changes $origarray2
array_walk($origarray2, function (&$v, $k) { $v = floor($v); });
print_r($origarray2);

// this is a more proper use of array_walk
array_walk($origarray1, function ($v, $k) { echo"$k => $v","
"
; });

// array_map accepts several arrays
print_r(
    array_map(function ($a, $b) { return $a * $b; }, $origarray1, $origarray2)
);

// select only elements that are > 2.5
print_r(
    array_filter($origarray1, function ($a) { return $a > 2.5; })
);

?>

< /代码>

结果:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Array
(
    [0] => 2
    [1] => 2
    [2] => 3
)
Array
(
    [0] => 2
    [1] => 2
    [2] => 3
)
0 => 2.4
1 => 2.6
2 => 3.5
Array
(
    [0] => 4.8
    [1] => 5.2
    [2] => 10.5
)
Array
(
    [1] => 2.6
    [2] => 3.5
)


将函数映射到数据数组的想法来自函数编程。你不应该把array_map看作是一个foreach循环,它在数组的每个元素上调用一个函数(即使这就是它的实现方式)。它应该被认为是将函数独立地应用于数组中的每个元素。

在理论上,函数映射等工作可以并行进行,因为应用于数据的函数只应影响数据,而不应影响全局状态。这是因为array_map可以选择将函数应用于中的项的任何顺序(即使在php中没有)。

另一方面,它是处理数据数组的完全相反的方法。它不单独处理每个项目,而是使用一个状态(&$userdata)并可以就地编辑项目(很像foreach循环)。因为每次一个项目应用$funcname时,它都会改变项目的全局状态,因此需要一种正确的处理项目的方法。

回到php-land,array_maparray_walk几乎是相同的,除了array_walk提供了对数据迭代的更多控制,通常用于"更改"就地数据,而不是返回新的"更改"数组。

array_filter实际上是array_walkarray_reduce的应用,它或多或少只是为了方便而提供的。


从文件中,

bool array_walk ( array &$array , callback $funcname [, mixed $userdata ] ) <-return bool

array-walk接受一个数组和一个函数F,并通过用F(x)替换每个元素x来修改它。

array array_map ( callback $callback ,
array $arr1 [, array $... ] )<-return array

数组映射执行完全相同的操作,只是它将返回带有转换元素的新数组,而不是就地修改。

array array_filter ( array $input [,
callback $callback ] )<-return array

带函数F的数组过滤器不会转换元素,而是删除F(x)不正确的元素。


其他答案很好地证明了array_walk(就地修改)和array_map(返回修改副本)之间的区别。然而,他们并没有真正提到数组减少,这是理解数组映射和数组过滤器的一种启发性方法。

array_reduce函数接受一个数组、一个双参数函数和一个"accumulator",如下所示:

1
2
3
array_reduce(array('a', 'b', 'c', 'd'),
             'my_function',
             $accumulator)

数组的元素使用给定的函数与累加器一次一个地组合在一起。上述调用的结果与执行此操作相同:

1
2
3
4
5
6
7
8
9
my_function(
  my_function(
    my_function(
      my_function(
        $accumulator,
        'a'),
      'b'),
    'c'),
  'd')

如果您更喜欢从循环的角度来考虑,这就像在执行以下操作(实际上,当数组减少不可用时,我将其用作回退):

1
2
3
4
5
6
function array_reduce($array, $function, $accumulator) {
  foreach ($array as $element) {
    $accumulator = $function($accumulator, $element);
  }
  return $accumulator;
}

这个循环版本清楚地说明了为什么我将第三个参数称为"accumulator":我们可以使用它在每次迭代中积累结果。

那么这与数组映射和数组过滤器有什么关系呢?结果发现它们都是一种特殊的数组减少。我们可以这样实现它们:

1
2
array_map($function, $array)    === array_reduce($array, $MAP,    array())
array_filter($array, $function) === array_reduce($array, $FILTER, array())

忽略这样一个事实:数组映射和数组过滤器以不同的顺序接受它们的参数;这只是PHP的另一个怪癖。重要的一点是,除了我称之为$map和$filter的函数外,右侧是相同的。那么,它们是什么样子的?

1
2
3
4
5
6
7
8
9
$MAP = function($accumulator, $element) {
  $accumulator[] = $function($element);
  return $accumulator;
};

$FILTER = function($accumulator, $element) {
  if ($function($element)) $accumulator[] = $element;
  return $accumulator;
};

如您所见,这两个函数都接受了$Accumulator并再次返回它。这些功能有两种不同:

  • $map将始终追加到$accumulator,但只有当$function($element)为真时,$filter才会这样做。
  • $filter追加原始元素,而$map追加$function($element)。

请注意,这远非无用的琐事;我们可以使用它来提高算法的效率!

我们经常可以看到类似以下两个示例的代码:

1
2
3
4
5
// Transform the valid inputs
array_map('transform', array_filter($inputs, 'valid'))

// Get all numeric IDs
array_filter(array_map('get_id', $inputs), 'is_numeric')

使用array_map和array_filter而不是循环,使这些示例看起来相当不错。但是,如果$inputs很大,则效率可能非常低,因为第一次调用(map或filter)将遍历$inputs并构建一个中间数组。这个中间数组直接传递到第二个调用中,第二个调用将再次遍历整个过程,然后需要对中间数组进行垃圾收集。

我们可以利用数组映射和数组过滤器都是数组减少的例子来摆脱中间数组。通过组合它们,我们在每个示例中只需要遍历$inputs一次:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// Transform valid inputs
array_reduce($inputs,
             function($accumulator, $element) {
               if (valid($element)) $accumulator[] = transform($element);
               return $accumulator;
             },
             array())

// Get all numeric IDs
array_reduce($inputs,
             function($accumulator, $element) {
               $id = get_id($element);
               if (is_numeric($id)) $accumulator[] = $id;
               return $accumulator;
             },
             array())

注意:上面的array_map和array_filter的实现不会像php那样运行,因为array_map一次只能处理一个数组,而array_filter不会使用"empty"作为其默认$函数。同样,也不会保留密钥。

让它们表现得像PHP并不难,但我觉得这些复杂的情况会使核心思想更难被发现。


下面的修订版试图更清楚地描述PHP的array_filer()、array_map()和array_walk(),所有这些都源于函数编程:

array_filter()过滤掉数据,从而生成一个新的数组,只保存前一个数组的所需项,如下所示:

1
2
3
4
5
6
<?php
$array = array(1,"apples",2,"oranges",3,"plums");

$filtered = array_filter( $array,"ctype_alpha");
var_dump($filtered);
?>

现场代码

所有数值都会从$array中过滤掉,只留下$filtered和水果类型。

array_map()还创建了一个新数组,但与array_filter()不同,结果数组包含输入$filtered的每个元素,但由于对每个元素应用了回调,因此使用了更改的值,如下所示:

1
2
3
4
5
<?php

$nu = array_map("strtoupper", $filtered);
var_dump($nu);
?>

现场代码

本例中的代码使用内置strtoupper()应用回调,但用户定义函数也是另一个可行的选项。回调应用于已筛选的每个$nu项,从而产生元素包含大写值的$nu。

在下一个代码段中,array walk()遍历$nu,并相对于引用运算符"&;"对每个元素进行更改。在不创建其他数组的情况下进行更改。每个元素的值都会就地更改为一个信息更丰富的字符串,指定其键、类别和值。

1
2
3
4
5
6
7
8
<?php

$f = function(&$item,$key,$prefix) {
    $item ="$key: $prefix: $item";
};
array_walk($nu, $f,"fruit");
var_dump($nu);    
?>

参见演示

注意:与array_walk()相关的回调函数接受两个参数,当array_walk()调用时,这些参数将自动获取元素的值及其键,并按此顺序获取。(更多信息请参见此处)。