array_map,array_walk和array_filter之间的区别


373

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

他们执行相同的事情吗?
它们可以互换使用吗?

如果它们完全不同,请提供示例说明,我们将不胜感激。


对于通过array_reduce()进行命名数组处理来说,这是一个很酷的技巧。如果要研究array_map,array_walk和array_filter,则值得一读。stackoverflow.com/questions/11563119/...
兰斯克利夫兰

Answers:


564
  • 更改值:
  • 阵列键访问:
  • 返回值:
    • array_map返回一个新数组,array_walk仅返回true。因此,如果您不想由于遍历一个数组而创建一个数组,则应使用array_walk
  • 迭代多个数组:
    • array_map它还可以接收任意数量的数组,并且可以并行地遍历它们,而array_walk只能在一个数组上操作。
  • 将任意数据传递给回调:
    • array_walk可以接收一个额外的任意参数以传递给回调。自PHP 5.3(引入匿名函数时)以来,这几乎无关紧要。
  • 返回数组的长度:
    • 得到的数组的array_map长度与最大输入数组的长度相同;array_walk不返回数组,但同时不能更改原始数组的元素数;array_filter根据过滤功能仅选择数组元素的子集。它确实保留了密钥。

例:

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

3
PHP手册说:“ array_walk():仅可能更改数组的值;”
Feeela 2012年

10
“ array_map无法使用数组键操作”这是不正确的:array_map(callback($key, $value), array_keys($array), $array)
Jarek Jakubowski

12
它仍然没有访问任何数组的键,而是访问从键创建的数组中输入的值。这是一种解决方法,它不会否定该语句。
inarilo

虽然array_map不会隐式更改值,但通过将结果分配给同一数组,它基本上会更改它,而在同一数组上运行的“反常” array_walk不会直接更改其值,除非通过引用传递值(数组walk可以通过传递原始数组的匿名函数use子句间接将索引/元素作为array_filter删除,但这是一种解决方法。综上所述,更改值,或者返回值或通过引用传递值实际上都没有
太大的

而且看起来无论数组遍历以第一个数组参数作为参考,当要更改它时,他还必须传递回调项值作为参考
FentomX1 '19

91

将函数映射到数据数组的想法来自函数编程。您不应该将其array_map视为foreach在数组的每个元素上调用函数的循环(即使这是实现它的方式)。应该考虑将函数独立地应用于数组中的每个元素。

从理论上讲,诸如函数映射之类的事情可以并行完成,因为应用于数据的函数应该只影响数据,而不影响全局状态。这是因为array_map可以选择将功能应用到项目的顺序(即使在PHP中不是这样)。

array_walk另一方面,它是处理数据数组的完全相反的方法。它使用状态(&$userdata)而不是分别处理每个项目,并且可以就地编辑该项目(很像foreach循环)。由于每次将项目$funcname应用到它,它可能会更改程序的全局状态,因此需要一种正确的方式来处理项目。

早在PHP的土地,array_map以及array_walk几乎相同,除了array_walk让你更好地控制数据的重复,并且通常用来“改变”数据就地和回访新的“改变”阵列。

array_filter确实是array_walk(或array_reduce)的应用,或多或少只是为了方便而提供的。


5
对于第二段见解+1,因为“理论上,可以并行完成诸如函数映射之类的事情,因为应用于数据的函数应该只影响数据,而不影响全局状态。” 对于我们并行程序员来说,这是要记住的有用的事情。
etherice

您能解释一下如何array_filter()使用来实现array_walk()吗?
pfrenssen

40

从文档中

bool array_walk(array&$ array,callback $ funcname [,mixed $ userdata])<-返回布尔值

array_walk接受一个数组和一个函数F,并通过用替换每个元素x对其进行修改F(x)

array array_map(callback $ callback,array $ arr1 [,array $ ...])<-返回数组

array_map会做完全相同的事情,所不同的是,它会返回带有转换后元素的新数组,而不是就地修改。

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

具有function的array_filterF,而不是转换元素,将删除所有F(x)不正确的元素


无法弄清楚为什么我的数组值消失了。查看文档时,我认为array_walk返回的数组类似,array_map并认为问题出在我的函数中。直到我看到这才知道返回类型为布尔值。
Dylan Valade

22

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

array_reduce函数采用一个数组,一个两个参数的函数和一个“累加器”,如下所示:

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

如您所见,两个函数都使用$ accumulator,然后再次返回它。这些功能有两个区别:

  • $ MAP将始终追加到$ accumulator,但是$ 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大,则效率可能非常低,因为第一个调用(映射或过滤器)将遍历$ 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不会将“空”用作其默认$ function。同样,它们都不会保留密钥。

使它们像PHP一样行为并不难,但我认为这些复杂性将使核心思想更难发现。


1

以下修订旨在更清楚地描述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中过滤掉,而$​​ filtered仅带有水果类型。

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()调用时,它们也会自动获取元素的值及其键,并且该顺序也是如此。(在此处查看更多信息)。


1
请注意,函数$lambda$callback只是现有函数的eta扩展,因此是完全多余的。您可以通过传递底层函数(的名称)来获得相同的结果:$filtered = array_filter($array, 'ctype_alpha');$nu = array_map('strtoupper', $filtered);
Warbo,2015年
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.