array_map、array_walk、array_filterの違い


373

正確にどのようなことの間の差であるarray_maparray_walkarray_filter。ドキュメントからわかるのは、コールバック関数を渡して、提供された配列に対してアクションを実行できることです。しかし、私はそれらの間の特定の違いを見つけるようには見えません。

彼らは同じことをしますか?
それらは交換可能に使用できますか?

実例が少しでも違う場合は、参考にしていただければ幸いです。


これは、array_reduce()による名前付き配列処理のクールなトリックです。array_map、array_walk、array_filterを調査している場合は、参照する価値があります。stackoverflow.com/questions/11563119/…–
ランスクリーブランド、

回答:


564
  • 値の変更:
    • array_map入力配列内の値を変更することはできませんarray_walk。特に、array_mapその引数を変更することはありません。
  • 配列キーへのアクセス:
  • 戻り値:
    • array_map新しい配列をarray_walk返し、のみを返しますtrue。したがって、1つの配列をトラバースした結果として配列を作成したくない場合は、を使用する必要がありますarray_walk
  • 複数の配列の反復:
    • array_mapまた、任意の数の配列を受け取ることができarray_walk、1つだけで動作しながら、それらを並列に反復できます。
  • コールバックに任意のデータを渡す:
    • 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

10
:これは真実ではない「array_mapをは配列のキーを操作することはできません」array_map(callback($key, $value), array_keys($array), $array)
Jarek Jakubowski

12
それはまだどの配列のキーにもアクセスしておらず、キーから作成した配列に入れた値にアクセスしています。これは回避策であり、ステートメントを無効にしません。
inarilo 2017

array_mapは暗黙的に値を変更しませんが、結果を同じ配列に割り当てることで基本的に変更します。同じ配列自体を操作する「逆説的に」array_walkは、参照によって渡された値(配列walkは、元の配列を渡す無名関数のuse句を介して間接的にarray_filterとしてインデックス/要素を削除する可能性がありますが、これは回避策です)。このように結論付けると、値を変更しても、値が参照によって返されたり渡されたりしても、効果はそれほど大きくはありませんが、配列ウォークはインデックスと複数の配列を持つ配列マップで機能します
FantomX1

さらに、配列ウォークが最初の配列パラメーターを参照として使用する場合でも、それを変更したい場合は、コールバック項目の値も参照として渡す必要があります
FantomX1

91

関数をデータの配列にマッピングするアイデアは、関数型プログラミングに由来します。あなたが考えるべきではないarray_mapとしてforeach、配列の各要素に関数を呼び出すループ(それはだにもかかわらず、それが実装されていますか)。これは、関数を配列内の各要素に個別に適用することと考える必要があります。

理論的には、データに適用される関数はデータにのみ影響し、グローバル状態には影響しないため、関数マッピングなどの処理は並行して行うことができます。これは、array_mapが関数を適用する順序を選択できるためです(PHPではそうではありませんが)。

array_walk一方、それはデータの配列を処理するための正反対のアプローチです。各アイテムを個別に処理する代わりに、状態(&$userdata)を使用し、(foreachループのように)アイテムを適切に編集できます。アイテムが$funcname適用されるたびに、プログラムのグローバル状態が変更される可能性があるため、アイテムを処理するための単一の正しい方法が必要です。

戻るPHPの土地で、array_mapかつarray_walk除いてほとんど同じですarray_walkあなたのデータの反復をより詳細に制御できますし、通常は新しい「変更」配列を返すVSインプレース「チェンジ」のデータをするために使用されます。

array_filterは本当にarray_walk(またはarray_reduce)のアプリケーションであり、多かれ少なかれ便宜上提供されています。


5
「理論上、データに適用される関数はデータにのみ影響し、グローバルな状態には影響しないので、関数マッピングなどのことは並行して行うことができる」という2番目の段落の洞察に+1 私たちの並列プログラマーにとって、これは覚えておくと便利なことです。
etherice 2014年

array_filter()を使用してどのように実装できるか説明できますarray_walk()か?
pfrenssen 2017

40

ドキュメントから、

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

array_walkは、配列と関数Fを取り、すべての要素xをで置き換えることにより、それを変更しF(x)ます。

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

array_mapは、インプレースで変更する代わりに、変換された要素を含む新しい配列を返すことを除いて、まったく同じこと行います。

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

F要素を変換する代わりに、functionを指定したarray_filterF(x)は、trueでない要素を削除します


配列の値が消えた理由を理解できませんでした。ドキュメントを見ると、次のarray_walkような配列が返されると想定しarray_map、問題は自分の関数にあると考えました。これを見て初めて、戻り値の型がブール型であることを理解しました。
Dylan Valade、2016年

22

他の回答は、array_walk(インプレース変更)とarray_map(変更されたコピーを返す)の違いをよく示しています。ただし、array_reduceについては実際には触れていません。これは、array_mapとarray_filterを理解するためのわかりやすい方法です。

array_reduce関数は、次のように、配列、2つの引数を持つ関数、および「アキュムレータ」を受け取ります。

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

配列の要素は、指定された関数を使用して、一度に1つずつアキュムレータと結合されます。上記の呼び出しの結果は、これを実行した場合と同じです。

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

このループバージョンでは、3番目の引数を「アキュムレータ」と呼んだ理由が明らかになります。これを使用して、各反復を通じて結果を累積できます。

それで、これは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のもう1つの癖です。重要な点は、$ MAPと$ FILTERを呼び出した関数を除いて、右側は同じであることです。それで、彼らはどのように見えますか?

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

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

ご覧のとおり、どちらの関数も$ accumulatorを受け取り、それを再び返します。これらの関数には2つの違いがあります。

  • $ MAPは常に$ accumulatorに追加しますが、$ FILTERは$ function($ element)がTRUEの場合にのみ追加します。
  • $ FILTERは元の要素を追加しますが、$ MAPは$ function($ element)を追加します。

これは役に立たない雑学にはほど遠いことに注意してください。それを使用して、アルゴリズムをより効率的にすることができます!

次の2つの例のようなコードがよく見られます。

// 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が大きい場合は非常に非効率的です。この中間配列は、2番目の呼び出しに直接渡されます。2番目の呼び出しは、もう一度全体をトラバースします。次に、中間配列をガベージコレクションする必要があります。

array_mapとarray_filterがどちらもarray_reduceの例であることを利用して、この中間配列を取り除くことができます。それらを組み合わせると、各例で$ inputsを1回トラバースするだけで済みます。

// 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が一度に1つの配列しか処理できず、array_filterがデフォルトの$関数として「空」を使用しないためです。また、どちらもキーを保持しません。

それらを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()によって呼び出された場合にも、要素の値とそのキーをこの順序で自動的に取得する2つのパラメーターを取ります。(詳細はこちら)。


1
関数$lambda$callbackは既存の関数のeta-expansionであり、完全に冗長であることに注意してください。基礎となる関数(の名前)を渡すことで同じ結果を得ることができます:$filtered = array_filter($array, 'ctype_alpha');および$nu = array_map('strtoupper', $filtered);
Warbo
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.