PHP5上級試験/準上級試験の上級合格に挑戦(9)　5章配列後編

#5章配列後編

## 各要素に対する関数（１）

コールバック関数登場。
他の関数に引数として渡される関数で、外側の関数で何らかの処理やアクションを実行するというもの。
上記をもとに（１）と（２）が指し示す関数とはなにか？

（１）配列の各要素に対して、指定した関数（＝これがコールバック関数）を実行できる。
　　　 また、（２）と違って、各要素に対して上書きでき、添字/キーをcallbackの引数に取れる。
（２）同様。ただし、（１）と違って、上書きや添字・キーを引数にすることができない。ただし、複数の配列を引数にすることができる。
（３）　下記の配列を使い、引数は　（値名）、（キー名）、（第３の引数））です。とコールバック関数の引数を出力させるコードを書け。なお、第３の引数は　引数第３C　（つまり文字列）とする。
（４）　１~５までの数が入っている配列\$numbersを作り、第１引数\$key・第２引数は\＄valとし、「キーは、\$valです」と出力させ、さらに配列に対して2０をかけ、次にその$numbersを出力せよ。
これを２つの方法で出力せよ。（ただし、一方はキー出力ができないのでそれはスキップしてOK)


$array = [
  "a" => 1,
  "b" => 2,
  "c" => 3,
] ;

解答

(１） array_walk()
(２） array_map()

```index.php
//(3)
$array = [
  "a" => 1,
  "b" => 2,
  "c" => 3,
] ;

function callback ( $a, $b, $c ) {
  echo "引数は、" . $a . "、" . $b . "、" . $c . "です。<br>" ;
}

$response = array_walk( $array, "callback", "引数第３C" ) ;

var_dump( $response ) ;

//(4)
$numbers = range(1,5);

array_walk($numbers, function(&$val, $index) {
    echo '添字/キーは '.$index;
    print"<br>";
    $val = $val * 20;
});

print_r($numbers);
print"<br>";
// 配列の中身をリセットする
$numbers = [1,2,3,4,5];

array_map(function(&$val) {
    $val = $val * 20;
}, $numbers);

print_r($numbers);
```

出力結果
引数は、1、a、引数第３Cです。
引数は、2、b、引数第３Cです。
引数は、3、c、引数第３Cです。
bool(true)

添字/キーは 0
添字/キーは 1
添字/キーは 2
添字/キーは 3
添字/キーは 4
Array ( [0] => 10 [1] => 20 [2] => 30 [3] => 40 [4] => 50 )
Array ( [0] => 1 [1] => 2 [2] => 3 [3] => 4 [4] => 5 )

（３）はコールバック関数の中身を出力することでarray_walkの仕組みを知ることが目的。
ちなみに最後のところで、array_walkがうまくいったらTrueが返るということもわかる。もちろんarray_mapもTrue/Falseが返る。
（４）array_walkとarray_mapの違いを知るところ。
array_walkはインデックス配列でもちゃんとキーが返る。
ちなみにarray_walkでも、引数を参照渡しにしていないと上書きにならないことに注意。
array_mapでは参照渡しにしても無駄。

### array_walkとarray_mapの違い
+ array_walk ( array &\$array , callable \$callback [, mixed \$userdata = NULL ] )：boolという形式
+ &$arrayは対象の配列
+ $callbackは各要素を引数にして実行される<b>コールバック関数「名」</b>。<b>コールバックの第1引数に要素の値、第2引数に要素のキー、第3引数にarray_walk()の第3引数</b>、が渡される。</b>　（第１引数がキーではなく、逆になるので要注意）
+ $userdataは指定した場合のみ、上記の第３引数として渡される。つまり必ず指定する必要はない。
+ array_mapの場合、array_walkでは使える引数のキーがないため、値のみしか使えず、添字/キーを扱う処理ができない
+ ただし、array_mapは、array_walkでは単独の配列を引数にしか取得できないところ、複数の配列を引数に取得することができる
+ array_map( callable \$callback,array \$arr1[, array \$arr2...]) で、arrで複数の配列を引数に取れるところ
+ array_walkとarray_mapの引数の順番を比べても、array_walkだと、最初に配列、次にコールバック関数というところ、mapだと逆になるのでここも注意するべきところ
+ array_mapの複数の配列は指定できるが、コールバック関数を等しく作用させるため、要素数は同じでないといけない([参考|PHPマニュアル](https://www.php.net/manual/ja/function.array-map.php))

## 各要素に対する関数（２）

（１）配列の各要素の和・積を出力する関数は？
（２）ユーザー定義関数を用いて配列内の要素を１つに集約する関数は？
（３） $a = range(1,5); を使って、（１）を実現せよ。
（４） (3)の配列\$aを使い、array_sum（）と同じことをせよ。
（５）　第３の引数を使い、（４）＋２になるようにせよ
　
　
　
　
　
　
　
解答
（１） array_sum()、array_product()
（２） array_reduce() ※ちなみに「減らす」という意味。要素を減らすってことだろう。

```index.php
//(3)
$a = range(1,4);
echo array_sum($a);
print '<br>';
echo array_product($a);
print '<br>';

//(4)
$a = range(1,4);
function My_func($x,$y){
  return $x + $y;
}
echo array_reduce($a,'My_func');
print '<br>';

//(5)
echo array_reduce($a,'My_func',2);


```
出力結果
10
24
10
12

（４）array_reduceでも同じことができる。
　 ただ、これをやるぐらいならもちろんarray_sumを使ったほうが簡単。
　 積や和を求めない場合に有効。
　 書式はこれ
　　array_reduce(array \$array, callable \$function_name[, mixed $initial = NULL]);
　　最初が配列で、２番めが配列の各要素に対して行うコールバック関数。
　　３番めは、１番目に計算するときにあらかじめ入っている数値。普段はなくてOK
　　(（５）のように、結果に＋１したいとか、そういう微調整に使う）

## ソート

（１）値を基準とした昇順ソートで、配列のキーも変更する関数は？
（２）値を基準とした降順でソートで、配列のキーも変更する関数は？
（３）値を基準とした昇順ソートで、配列のキーは変更しない関数は？
（４）値を基準とした降順でソートで、配列のキーも変更しない関数は？
（５）キーを基準とした昇順ソートをする関数は？
（６）キーを基準とした降順でソートをする関数は？
（７）値を基準とした昇順ソートだが、任意の基準も含み、配列のキー・値を変化させる関数は？
（８）値を基準とした昇順ソートで、任意の基準も含むが、配列のキー・値は変化しない関数は？
（９）キーを基準とした昇順ソートで、任意の基準も含むが、配列のキー・値は変化しない関数は？
（10）配列の要素を自然順で大文字小文字を区別する関数は？
（11）配列の要素を自然順で大文字小文字を区別しない関数は？
（12）（１）〜（６）の第２引数であるソート型のオプションにはどんなものがあるか？
（13）　\$money = [100, 50, 20, 4000];　この配列を昇順・降順に並べ替えて出力せよ
（14） 下記を、キーが壊れないように、値を昇順・降順に並べ替えて出力せよ
$programs = array(
  'PHP' => 60,
  'CSS' => 80,
  'HTML' => 100,
  'Javascript' => 30
);
（15）(14)の配列を、今度はキーを昇順・降順に並べ替えて出力せよ。値はそのままとする。
（16）下記の配列の値を辞書順に昇順、そして自然順の昇順に並べ替えて出力せよ。
　　なお、下記は配列名が違うだけで中身は同じである。

　\$array1 = array("img22.png", "img11.png", "img12.png", "img100.png");
　$array2 = array("img22.png", "img11.png", "img12.png", "img100.png");
　
　
　
解答

（１）sort() /そーと
（２）rsort()/ りそーと
（３）asort() /あそーと
（４）arsort() /ありそーと
（５）ksort()/きそーと
（６）krsort()/きりそーと
（７）usort()/ゆそーと
（８）uasort()/ゆあそーと
（９）uksort()/ゆきそーと
（10）natsort()/なちゅそーと
（11）natcasesort()/なちゅけーすそーと
（12）SORT_REGULAR/数値形式の文字列同士は数値として比較
　　　SORT_NUMERIC/値を数値として比較
　　　SORT_STRING/値を文字列として比較

```index.php

//(13)
$money = [100, 50, 20, 4000];

sort($money);
var_dump($money);
print '<br>';
rsort($money);
var_dump($money);
print '<br>';

//(14)
$programs = array(
  'PHP' => 60,
  'CSS' => 80,
  'HTML' => 100,
  'Javascript' => 30
);

asort($programs);
var_dump($programs);
print '<br>';
arsort($programs);
var_dump($programs);
print '<br>';

//(15)
ksort($programs);
var_dump($programs);
print '<br>';
krsort($programs);
var_dump($programs);
print '<br>';

//(16)
$array1 = $array2 = array("img22.png", "img11.png", "img12.png", "img100.png");

asort($array1);
var_dump($array1);
print '<br>';
natsort($array2);
var_dump($array2);


```
出力結果

array(4) { [0]=> int(20) [1]=> int(50) [2]=> int(100) [3]=> int(4000) }
array(4) { [0]=> int(4000) [1]=> int(100) [2]=> int(50) [3]=> int(20) }
array(4) { ["Javascript"]=> int(30) ["PHP"]=> int(60) ["CSS"]=> int(80) ["HTML"]=> int(100) }
array(4) { ["HTML"]=> int(100) ["CSS"]=> int(80) ["PHP"]=> int(60) ["Javascript"]=> int(30) }
array(4) { ["CSS"]=> int(80) ["HTML"]=> int(100) ["Javascript"]=> int(30) ["PHP"]=> int(60) }
array(4) { ["PHP"]=> int(60) ["Javascript"]=> int(30) ["HTML"]=> int(100) ["CSS"]=> int(80) }
array(4) { [3]=> string(10) "img100.png" [1]=> string(9) "img11.png" [2]=> string(9) "img12.png" [0]=> string(9) "img22.png" }
array(4) { [1]=> string(9) "img11.png" [2]=> string(9) "img12.png" [0]=> string(9) "img22.png" [3]=> string(10) "img100.png" }


(14)のようにしないで、同じように値をソートするsort()でもできるが、
この場合、キーが壊れて、番号が振られてしまうため、この場合はasort()になった。
sortはキーを振り直すが、asort,ksortはキーを振り直さない。この違いは注意したい。

（１６）は自然順と辞書順の違いを理解するための問題。
自然順は、数値に見えるものはすべて数値と考え、数値の大小で順番を決めるもの（一番最後の解答）
辞書順の場合、数値に見えるものでも、辞書順になる。（一番最後から２番めの解答）。
<b>自然順と辞書順の並べ方について理解しておく</b>
なお、<b>natsortとnatcasesortはキーが振り直される</b>ので注意。

## （７）〜（９）のユーザー定義型ソート
+ usortはキーが振り直されるが、uasort,uksortはキーはそのままになる。
+ ロジックを理解できないと、かなりハード。

次の配列を、上記のsort関数を使わず、ユーザー定義型ソートで昇降順機能を実現し、コールバック関数で並べ替えて出力せよ。
$a = array(4, 10, 8, 26, 11);

（１） 小さい順（昇順）に並べ替える
（２） 大きい順（降順）に並べ替える
（３） １０を一番左に固定してあとは昇順で並べ替え
（４） １０を一番左に固定してあとは降順で並べ替え
（５） １０を一番右に固定してあとは昇順で並べ替え
（６） １０を一番右に固定してあとは降順で並べ替え

```index.php
$a = array(4, 10, 8, 26, 11);


//各コールバック関数
//(1)
function cmp($a, $b)
{
    if ($a == $b) {
        return 0;
    }
    return ($a < $b) ? -1 : 1;
}

//(2)
function cmp2($a, $b)
{
    if ($a == $b) {
        return 0;
    }
    return ($a > $b) ? -1 : 1;
}


//(3)
function cmp3($a, $b)
{
    if ($a == 10 ) {
      return -1;
    }
    if ($b == 10 ){
      return 1;
    }
    if ($a <> 10 || $b <> 10){
      if ($a == $b) {
          return 0;
      }
      return ($a < $b) ? -1 : 1;
    }
}

//(4)
function cmp4($a, $b)
{
    if ($a == 10 ) {
      return 1;
    }
    if ($b == 10 ){
      return -1;
    }
    if ($a <> 10 || $b <> 10){
      if ($a == $b) {
          return 0;
      }
      return ($a < $b) ? -1 : 1;
    }
}


//(5)
function cmp5($a, $b)
{
    if ($a == 10 ) {
      return 1;
    }
    if ($b == 10 ){
      return -1;
    }
    if ($a <> 10 || $b <> 10){
      if ($a == $b) {
          return 0;
      }
      return ($a > $b) ? -1 : 1;
    }
}


//(6)
function cmp6($a, $b)
{
    if ($a == 10 ) {
      return -1;
    }
    if ($b == 10 ){
      return 1;
    }
    if ($a <> 10 || $b <> 10){
      if ($a == $b) {
          return 0;
      }
      return ($a > $b) ? -1 : 1;
    }
}


// ここからメイン

usort($a, "cmp");

foreach ($a as $key => $value) {
    echo "$key: $value";
    print "<br>";
}

print "<br>";

usort($a, "cmp2");

foreach ($a as $key => $value) {
    echo "$key: $value";
    print "<br>";
}
print "<br>";


usort($a, "cmp3");

foreach ($a as $key => $value) {
    echo "$key: $value";
    print "<br>";
}

print "<br>";


usort($a, "cmp4");

foreach ($a as $key => $value) {
    echo "$key: $value";
    print "<br>";
}
print "<br>";

usort($a, "cmp5");

foreach ($a as $key => $value) {
    echo "$key: $value";
    print "<br>";
}
print "<br>";

usort($a, "cmp6");

foreach ($a as $key => $value) {
    echo "$key: $value";
    print "<br>";
}


```
出力結果
0: 26
1: 11
2: 10
3: 8
4: 4

0: 10
1: 4
2: 8
3: 11
4: 26

0: 4
1: 8
2: 11
3: 26
4: 10

0: 26
1: 11
2: 8
3: 4
4: 10

0: 10
1: 26
2: 11
3: 8
4: 4


## 解説

+ 最初この関数を見た時よくわからず面食らった
+ とりあえず、各要素を比較して数値を与えてソートしているということはわかった
+ コールバック関数の不等式の部分で、`<`だったら、昇順になり、`>`だったら降順になる
+ ある数を固定して一番左にしたい時は、＄aがその数の時、「１」を与え、＄ｂの場合は「ー１」を与える。右の場合は逆
　それ以外は既存のになるようにif文をつくればよい
+ ルールは分かった、だけど、なぜ正の数なら左に、負の数なら右に移動していくのか調べてもさっぱりわからず
+ クイックソートというのが関係しているらしいけど・・・ひとまず宿題
+ ひとまず上の問題（実験）はあれこれ不等号とか逆にしてみたりしてルールがわかったのでメモ

つまり、
-1だったら、左へ繰り下がる。１だったら右へ繰り上がる。これがルール。
昇順に並べたいなら、番号が大きい場合はとにかく１へ。小さいものが−１になるようにすればいい。
もちろん、固定する場合は、左は必ず−１。右は１。
\$xと\$yがあってややこしいが、とにかく\$xを軸に。
たとえば、\$xが５で、必ず左に固定したい場合は、-1。
\$yが５だったら、\$xはどんな数値でも必ず１にして繰り上げたいいから、１にするということ。
\$x<\$yだったら、yのほうが大きいから、昇順だったら１にして、降順だったら-1にする、と考えればオッケー。


## 簡単な配列で解説してみる

$a = array(8, 3, 5)　
で、これを比べるとする
場合分けすると、
8,3
8,5
3,8
3,5
5,8
5,3
の６通りある。

昇順に並べ替えるとして

まずは　８と３をくらべる
昇順に並べ替える時、下記のルールになるので、

```index.php
function cmp($a, $b)
{
    if ($a == $b) {
        return 0;
    }
    return ($a < $b) ? -1 : 1;
}
```

もしクイックソートという形になるとすると
[参考クイックソートのアルゴリズムをわかりやすく解説します｜じゃぱざむ](https://jpazamu.com/quick_sort/）

8をピボットにすると　３と５は「１」になるので、８の左に置く

5，3，８

次に３をピボットにすると 3は５より小さいので−１，つまり右におくことになる

３，５，８

ソートの数は１つだけになったので、これにて終了って感じかな

では、逆に固定する場合を考える

$a = array(8, 3, 5)　
で、これを比べるとするとして、３を一番左に置きたいとする
場合分けすると、
8,3
8,5
3,8
3,5
5,8
5,3

では、８と３を比べた時　左へ移動するのは−１、右へ移動するのは１を示してくれればOKなわけなので、
３を固定する場合は、a=3のときはa<b の結果を-1or1にしたり、 b<aの結果を-1or1にしたりすればよい。
簡単な配列でやってみたら意外と理解できたかな。

## 他ソート関数

（１） 複数のインデックス配列を一度にソートするのは？
（２） 配列の逆に並べ替えるのは？
（３） 配列のキーや値を入れかえるのは？
（４） 配列をシャッフルするのは？
（５） $a = array('c','b','a','b');で、＄aをシャッフルし出力せよ

（6） $b = array(1,2,2,3);で、＄bを逆順番にして出力せよ

（７） \$a,$bを昇順で同時に並べ替えよ

##

解答
（１） array_multisort(array \$array [, $arg = SORT_ASC [, mixed \$arg = SORT_REGULAR [,mixed...]]]);
（２） array_reverse(array);
（３） array_flip(array);
（４） shuffle(array);

```index.php
$a = array('c','b','a','b');
var_dump($a);
print '<br>';
shuffle($a);
var_dump($a);
print '<br>';
$b = array(1,2,2,3);
var_dump($b);
print '<br>';
array_reverse($b);
var_dump($b);
print '<br>';


array_multisort($a,$b);
var_dump($a);
print '<br>';
var_dump($b);
```

##　配列集合演算

（１） 複数の配列をマージするのは？
（２） 配列の差、つまり、ある配列にはあり、他の配列にはない値を集めた配列を返すのは？
（３） ある特定の条件を満たす値だけを集めた配列を返すのは？
（４） 配列から重複する値を取り除くのは？
（５） 複数の配列に共通して含まれる値を集めるのは？
（６） \$a =　\$b= array('php');という配列がありこれらをマージした＄cを出力せよ
（７） \$ｃ = array('php' => 5.3); \$ｄ = array('php' => 5.4);　これらをマージした\$eを出力せよ
（８） \$x = array('a','b','c','d'); \$y= array('a','b'); \$z= array('b','c');　ある配列には含まれていて、他の配列には含まれている要素を配列にして出力せよ　ただし、ｘとy,xとyとｚの２つを出力する。
（９） \$f = array(78,79,80,81,102); で、８０以上の数値を抽出するコールバック関数\$my_funcをつくって抽出して出力せよ。引数は$sourceとする

（10） \$h = array(1,2,3,1,2,4,3,4,5)で重複する値を取り除いた配列＄iを出力せよ
（11） \$j = array (2,4,6,8); （１０）の＄iと＄jで、共通して含まれる値を集めた配列$kを出力せよ
　
　
　

解答
（１） array_merge(array1,array2...)
（２） array_diff(array1,array2...)
（３） array_fliter(array \$array, callable $callback = "")
（４） array_unique(array)
（５） array_intersect(array1,array2...)

```index.php
//（６）
$a = $b = array('php');
$c =array_merge($a,$b);
var_dump($c);
print '<br>';
//(7)
$c = array('php' => 5.3);
$d = array('php' => 5.4 );
$e = array_merge($c,$d);
var_dump($e);
print '<br>';
//（８）
$x = array('a','b','c','d');
$y= array('a','b');
$z= array('b','c');
print_r(array_diff($x,$y));
print '<br>';
print_r(array_diff($x,$y,$z));
print '<br>';
//（９）
$f = array(78,79,80,81,102);
function my_func($source){
   return $source >= 80;
}
$g = array_filter($f,'my_func');
var_dump($g);
//（10）
print '<br>';
$h = array(1,2,3,1,2,4,3,4,5);
$i = array_unique($h);
var_dump($i);
//（11）
print '<br>';
$j = array (2,4,6,8);
$k = array_intersect($i,$j);
var_dump($k);

```

##　配列への値の追加と削除
JSをかじってたら難しくない

（１） 配列の先頭へ追加または削除する関数は？
（２） 配列の末尾に追加または削除する関数は？
（３） array(1,2,3);で　先頭の１を削除し、4を追加して出力せよ
（４） (3)からさらに末尾の３を削除し、５を追加して出力せよ
　
　
　
解答
（１） array_unshift(),array_shift()
（２） array_push(),array_pop()

```index.php
//(3)
$a = array(1,2,3);
array_shift($a);
array_unshift($a,4);
var_dump($a);
print '<br>';
//(4)
array_pop($a);
array_push($a,5);
var_dump($a);

```
出力結果
array(3) { [0]=> int(4) [1]=> int(2) [2]=> int(3) }
array(3) { [0]=> int(4) [1]=> int(2) [2]=> int(5) }

イメージは頭から移動させるのがshiftなのでその反対はもとに戻すってことでunshift
pushするってのはお尻に押し込むイメージでpopはお尻から飛び出るイメージ。


# まとめ

配列は非常に沢山の関数があり、そして複雑な関数もある、難しいところだった
とくにユーザー定義型のソート関数や、array_walk系の関数は理解しておかないと永遠に覚えられないので、きっちり理解してから覚えるようにしたい

前編では配列の基本、とくに通常でしなさそうな指定方法での出力がどうなるかということ、
中編から後編まで各テーマごとにいろいろな関数が出てくるので中編〜後編はドリルもあるのでしっかりスマホで復習しておきたい。
ソラでコードがかけるようにしたいところ。
後編は上記の関数ドリルに加え、さっきも書いたとおり、ユーザ定義型のソート関数の仕組みと、array_walk系の関数を理解しておきたい。

これらを理解すればおそらく配列は解答率が上がるはずだ。