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

它们的作用是一样的吗? 它们可以互换使用吗?

如果它们完全不同,我将非常感谢您提供举例说明。


当前回答

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

Array_filter()过滤掉数据,生成一个只包含前一个数组所需项的新数组,如下所示:

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

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

这里是实时代码

所有数值都从$array中过滤掉,只过滤出水果类型。

Array_map()也创建了一个新数组,但与array_filter()不同的是,生成的数组包含输入$filtered每个元素,但值改变了,这是由于对每个元素应用了回调,如下所示:

<?php

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

这里是实时代码

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

在下一个代码片段中,数组walk()遍历$nu,并根据引用操作符'&'对每个元素进行更改。更改发生时无需创建额外的数组。每个元素的值都相应地改变为一个更有信息的字符串,指定它的键、类别和值。

<?php

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

看到演示

注意:关于array_walk()的回调函数接受两个参数,当被array_walk()调用时,这两个参数将自动获取一个元素的值和它的键,并且也是按这个顺序。(详见此处)。

其他回答

将函数映射到数据数组的思想来自函数式编程。您不应该将array_map视为对数组的每个元素调用函数的foreach循环(尽管这就是它的实现方式)。它应该被认为是将函数独立地应用于数组中的每个元素。

理论上,像函数映射这样的事情可以并行完成,因为应用到数据上的函数应该只影响数据而不影响全局状态。这是因为array_map可以选择任意顺序将函数应用于中的项(尽管在PHP中不是这样)。

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

回到PHP领域,array_map和array_walk几乎是相同的,除了array_walk让您对数据的迭代有更多的控制,并且通常用于就地“更改”数据,而不是返回一个新的“更改”数组。

Array_filter实际上是array_walk(或array_reduce)的一个应用,它的提供或多或少只是为了方便。

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

Array_filter()过滤掉数据,生成一个只包含前一个数组所需项的新数组,如下所示:

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

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

这里是实时代码

所有数值都从$array中过滤掉,只过滤出水果类型。

Array_map()也创建了一个新数组,但与array_filter()不同的是,生成的数组包含输入$filtered每个元素,但值改变了,这是由于对每个元素应用了回调,如下所示:

<?php

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

这里是实时代码

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

在下一个代码片段中,数组walk()遍历$nu,并根据引用操作符'&'对每个元素进行更改。更改发生时无需创建额外的数组。每个元素的值都相应地改变为一个更有信息的字符串,指定它的键、类别和值。

<?php

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

看到演示

注意:关于array_walk()的回调函数接受两个参数,当被array_walk()调用时,这两个参数将自动获取一个元素的值和它的键,并且也是按这个顺序。(详见此处)。

从文档来看,

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

array_walk接受一个数组和一个函数F,并通过将每个元素x替换为F(x)来修改它。

数组array_map 数组$arr1[,数组$…])<-返回数组

Array_map做了完全相同的事情,只不过它不是原地修改,而是返回一个包含转换后的元素的新数组。

array_filter(数组$input [, Callback $ Callback])<-返回数组

使用函数F的array_filter,而不是转换元素,将删除任何F(x)不为真的元素

其他答案很好地演示了array_walk(原地修改)和array_map(返回修改后的副本)之间的区别。然而,他们并没有真正提到array_reduce,这是理解array_map和array_filter的一种有启发性的方式。

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

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

数组的元素使用给定的函数一次一个地与累加器组合。上述调用的结果与这样做的结果相同:

my_function(
  my_function(
    my_function(
      my_function(
        $accumulator,
        'a'),
      'b'),
    'c'),
  'd')

如果你更喜欢从循环的角度来考虑,它就像做以下事情(当array_reduce不可用时,我实际上已经使用了这个作为备用):

function array_reduce($array, $function, $accumulator) {
  foreach ($array as $element) {
    $accumulator = $function($accumulator, $element);
  }
  return $accumulator;
}

这个循环版本清楚地说明了为什么我把第三个参数称为“累加器”:我们可以用它来累积每次迭代的结果。

这和array_map和array_filter有什么关系呢?它们都是一种特殊的array_reduce。我们可以这样实现它们:

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

忽略array_map和array_filter以不同的顺序获取参数的事实;这只是PHP的另一个怪癖。重要的一点是,除了我称为$MAP和$FILTER的函数外,右边是相同的。那么,它们长什么样呢?

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

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

如您所见,两个函数都接受$累加器并再次返回它。这些功能有两个不同之处:

$MAP总是附加到$累加器,但$FILTER只在$function($element)为TRUE时才这样做。 $FILTER附加原始元素,而$MAP附加$function($element)。

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

我们经常可以看到这样的代码:

// 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并构建一个中间数组。这个中间数组被直接传递给第二个调用,它将再次遍历整个对象,然后需要对中间数组进行垃圾收集。

我们可以通过利用array_map和array_filter都是array_reduce的例子来摆脱这个中间数组。通过组合它们,我们只需要在每个例子中遍历$inputs一次:

// 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一样运行并不难,但我觉得这些复杂的操作会使核心思想更难被发现。

Changing Values: array_map cannot change the values inside input array(s) while array_walk can; in particular, array_map never changes its arguments. Array Keys Access: array_map cannot operate with the array keys, array_walk can. Return Value: array_map returns a new array, array_walk only returns true. Hence, if you don't want to create an array as a result of traversing one array, you should use array_walk. Iterating Multiple Arrays: array_map also can receive an arbitrary number of arrays and it can iterate over them in parallel, while array_walk operates only on one. Passing Arbitrary Data to Callback: array_walk can receive an extra arbitrary parameter to pass to the callback. This mostly irrelevant since PHP 5.3 (when anonymous functions were introduced). Length of Returned Array: The resulting array of array_map has the same length as that of the largest input array; array_walk does not return an array but at the same time it cannot alter the number of elements of original array; array_filter picks only a subset of the elements of the array according to a filtering function. It does preserve the keys.

例子:

<pre>
<?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", "\n"; });

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

?>
</pre>

结果:

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
)