0. はじめに
突然ですが、皆さんはこの問題、解けますか?
【問題】
あなたはDBからユーザーのリストを取得しました。データはこんな感じのシンプルな配列です。
この配列から、ユーザーIDをキーに、ユーザー名を値に持つ、以下のような連想配列を作ってください。
$users = [
['id' => 21, 'name' => '佐藤'],
['id' => 35, 'name' => '鈴木'],
['id' => 58, 'name' => '高橋'],
];
【期待される結果】
/*
Array
(
[21] => '佐藤',
[35] => '鈴木',
[58] => '高橋'
)
*/
なお、もちろん、foreachでループを回せば数行で書けますが...
$user_map = [];
foreach ($users as $user) {
$user_map[$user['id']] = $user['name'];
}
PHPにはこのためだけにあるような、一行で終わる標準関数が用意されています。
これ、知っていますか?
『解答・解説』
解答: array_column関数の第三引数を使う
// 'name'の値を「値」、'id'の値を「キー」として、新たに配列を作成する
array_column($users, 'name', 'id')
/* (結果)
[21] => '佐藤',
[35] => '鈴木',
[58] => '高橋'
*/
解説: array_column関数は配列の特定のキーを持つ値のみを取り出す関数です。
このとき第三引数を指定すると、任意でキーも設定することができるというわけですね。
こんにちは!新卒エンジニアのカンゾーです。
突然煽るような出だしになってしまってすみません!!!
僕は実務でこの配列操作が全然分からず、DBから取得したデータを表示用に加工する際に酷く悩み、たくさん先輩に聞きました(笑)
「array_pushやarray_mergeは使うけど、array_mapやarray_filterはよく分からない…」
「多次元配列の扱いでいつも手が止まってしまう…」
そんな初心者エンジニアの皆さんに向けてこの記事では、PHPの配列操作について「基礎的な関数から実務で頻出の応用関数まで」を網羅的に解説し、最後に知識を定着させるための「配列操作10本ノック」を用意しました!
もちろん、難しい箇所は読み飛ばして、必要な箇所だけ読むのも全然OK。
問題をやるのは後回しで、一旦関数だけの勉強もウェルカムです。
少しずつ、一緒に『配列操作マスター』を目指しましょう~!!
1. 配列のおさらい
まずはおさらいです。
すでに知っている方も、知識の再確認として軽く目を通してみてください。
配列(インデックス配列)
配列とは複数の値を順番に格納できる、引き出しが連なった棚のようなものです。
各引き出しには 0, 1, 2... という番号(インデックス)が自動で振られており、その番号を使って値を取り出すことができますね!
// 配列を定義
$fruits = ['りんご', 'みかん', 'ぶどう'];
// 0番目の'りんご'を取り出す
echo $fruits[0];
イメージ図:
連想配列
連想配列は、0, 1, 2 のようなインデックスの代わりに、'name' や 'price' のような自分で決めた名前(キー)を使って値を管理する配列です。
キーと値がペアになっているため、データの内容が直感的に分かりやすくなりますね!
// インデックスの代わりに「キー」を定義
$product = [
'name' => 'りんご',
'price' => 150,
'origin' => '青森県'
];
// 'name'キーの'りんご'を取り出す
echo $product['name'];
イメージ図:
多次元配列
多次元配列とは、配列の中にさらに配列が入っている、入れ子構造の配列のことです。これにより、Excelの表のような、「行と列で管理される複雑なデータ」を表現することができますね!
$products = [
['name' => 'りんご', 'price' => 150],
['name' => 'みかん', 'price' => 100],
['name' => 'ぶどう', 'price' => 300]
];
// 0番目の商品の'name'である'りんご'を取り出す
echo $products[0]['name'];
イメージ図:
2. 【基本編】配列操作 ~加工~
それでは、ここから本番です!
まずは基本編として、「配列の追加~削除、取得」までの関数を見ていきましょう。
配列の作成と要素の追加・削除
$fruits = ['apple', 'orange', 'grape'];
// 要素の追加(末尾)
array_push($fruits, 'banana', 'melon');
// $fruits は ['apple', 'orange', 'grape', 'banana', 'melon']
// 要素の削除(末尾)
$last_fruit = array_pop($fruits);
// $last_fruit は 'melon'
// $fruits は ['apple', 'orange', 'grape', 'banana']
| 関数 | 説明 |
|---|---|
array_push() |
配列の末尾に1つ以上の要素を追加する |
array_pop() |
配列の末尾の要素を取り出す |
【Tips】配列をキューとして扱うとき
稀にメールの一斉送信など、シンプルなタスク処理で配列をキュー1として用いたいときは、以下のような関数も使えるようです。
| 関数 | 説明 |
|---|---|
array_unshift() |
配列の先頭に1つ以上の要素を追加する |
array_shift() |
配列の先頭の要素を取り出す |
なお、以下の点に注意すること!
- 元配列の最初の要素を完全に取り除く、破壊的な操作である。
- 挿入・削除後、「全要素のインデックスを1つずつずらす」ためパフォーマンスが低下しやすい。
配列情報の取得やチェック
$numbers = [1, 2, 3, 4, 5];
// 配列の要素数を取得
echo count($numbers); // 5
// 指定した値が配列に存在するかチェック
if (in_array(3, $numbers)) {
echo '3は存在します';
}
$user = ['name' => 'hoge', 'age' => 25];
// 指定したキーが配列に存在するかチェック
if (array_key_exists('name', $user)) {
echo 'nameキーは存在します';
}
| 関数 | 説明 |
|---|---|
count() |
配列の要素数を返す |
in_array() |
配列内に指定した値が存在するかチェックする ※オプションで第三引数を取り、 [ true / false ]を設定することで厳密比較が可能。 |
array_key_exists() |
配列内に指定したキーが存在するかチェックする |
実務ではarray_key_existsに代わって、isset()が頻繁に使われます。
どちらも「指定のキーが存在するか」をチェックする関数ですが、
isset()は、そのキーの値が'null'だった場合、falseを返すので便利なんです!
3. 【応用編】配列操作 ~抽出・変換~
さて、ここからは応用編です!
ちなみにこの辺の関数は知らないものも多く、とっても勉強になりました(笑)
ここでは、「配列の抽出~変換」までの関数が登場します。
foreachでゴリゴリ書いていた処理も、これらの関数を使えばスマートに記述できますね!
【Tips】(少し詳しく) 無名関数とは
ここから登場する『無名関数』について解説しておきます。
(筆者が付け焼き刃かつ、言語によって意味合いが若干変わるので、ご参考までに!)
・無名関数(Anonymous Function)
名前が無い、「使い捨ての関数」のこと。
function($arg) { ... }という構文で用いられ、その場で関数を定義する。
// 引数1と引数2を足し算し、結果を返す無名関数
$add = function($a, $b) {
return $a + $b;
};
// 結果を出力
echo $add(1, 2);
なおこちら、PHPでは『クロージャ』とも呼ばれていますが、厳密には意味は少し違います。
・クロージャ
自分が定義された場所の変数を記憶できる、無名関数のうち、特殊な性質を指す。
useというキーワードを用いて、外部のスコープにある変数を引き継ぐことが可能。
$modifier = 2;
// use ($modifier) で、外にある$modifier変数を記憶している
$doubler = function($n) use ($modifier) {
return $n * $modifier;
};
// 結果を出力
echo $doubler(10);
PHPではすべての『無名関数』が内包的にこの性質を持つ(Closureクラスのオブジェクトとして扱われる)ため、「PHPにおける無名関数はクロージャとも呼ぶ」のである。
ちなみに、対義語は外部の変数を保持しない『ラムダ関数』とのこと。
筆者もチンプンカンプンですが、ご興味ある方はこちらも参考になるかと!
array_map(): 全要素に同じ処理を適用する
array_map()は、配列の各要素に指定した関数(コールバック関数2)を適用し、その結果からなる新しい配列を返します。
例:数値配列の各要素を2倍にする
$numbers = [1, 2, 3, 4, 5];
// 各要素を2倍にする
$doubled_numbers = array_map(function($n) {
return $n * 2;
}, $numbers);
print_r($doubled_numbers);
// Array ( [0] => 2 [1] => 4 [2] => 6 [3] => 8 [4] => 10 )
foreachでループを回して新しい配列に詰め直すよりも、ずっと簡潔ですね!
array_filter(): 条件に合う要素だけを抽出する
array_filter()は、配列の各要素をコールバック関数で評価し、trueを返した要素だけを抽出した新しい配列を返します。
例:配列から偶数だけを取り出す
$numbers = [1, 2, 3, 4, 5, 6];
// 偶数だけをフィルタリング
$even_numbers = array_filter($numbers, function($n) {
return $n % 2 === 0;
});
print_r($even_numbers);
// Array ( [1] => 2 [3] => 4 [5] => 6 )
// キーを0から振り直したい場合は `array_values()`を合わせて使う
print_r(array_values($even_numbers));
// Array ( [0] => 2 [1] => 4 [2] => 6 )
このarray_filterは、キーが元のまま保持されます。
キーを0から振り直した配列を作る場合は、array_values()を使いましょう!
array_column(): 多次元配列から特定の列を抜き出す
APIのレスポンスやDBからの取得結果など、多次元配列(連想配列の配列)を扱う際に絶大な効果を発揮します。
例:ユーザーリストから名前だけを抜き出す
$users = [
['id' => 1, 'name' => 'Sato', 'age' => 25],
['id' => 2, 'name' => 'Suzuki', 'age' => 30],
['id' => 3, 'name' => 'Takahashi', 'age' => 22],
];
// 'name'キーの値だけを抜き出す
$names = array_column($users, 'name');
print_r($names);
// Array ( [0] => Sato [1] => Suzuki [2] => Takahashi )
// idをキーにしてnameを値にした配列を作る
$name_with_id_key = array_column($users, 'name', 'id');
print_r($name_with_id_key);
// Array ( [1] => Sato [2] => Suzuki [3] => Takahashi )
指定のキーの値をまとめて全部取得できるのは非常に便利ですね!
(なお、3次元以上の深い階層にあるキーの値は直接引っ張れないので注意)
array_reduce(): 配列の値を集約して1つの結果にする
配列の全要素を繰り返し処理し、最終的に1つの値(数値、文字列、配列など)を生成します。
非常に強力な分、かなり複雑なため、実務ではforeach文やフレームワークのcollection操作の方が主流だと言われています。
「こんな関数もあるんだ~」程度でぜひ!
例:配列の全要素の合計値を求める
$prices = [100, 250, 80, 120];
$total_price = array_reduce($prices, function($carry, $item) {
// $carryには前回のループの戻り値が、$itemには現在の要素が入る
return $carry + $item;
}, 0); // 第3引数は初期値
echo $total_price; // 550
合計値だけでなく、条件に合う要素をグルーピングするなど、複雑な処理も実現できます。
例:多次元配列を指定のキーでグルーピングする
// このバラバラのログを、`user_id` ごとにまとめたい
$purchase_logs = [
['user_id' => 101, 'item' => 'Book', 'price' => 2000],
['user_id' => 102, 'item' => 'Pen', 'price' => 150],
['user_id' => 101, 'item' => 'Notebook', 'price' => 500],
['user_id' => 103, 'item' => 'Stapler', 'price' => 800],
['user_id' => 102, 'item' => 'Eraser', 'price' => 100],
];
$grouped_by_user = array_reduce($purchase_logs, function ($carry, $item) {
// 現在の$itemからuser_idを取得
$userId = $item['user_id'];
// carry配列(=最終結果の配列)の$userIdキーの配下に、現在の$itemを追加
$carry[$userId][] = $item;
// 次のループのために、更新したcarry配列を返す
return $carry;
}, []); // 初期値は空の配列 []
print_r($grouped_by_user);
/*
Array
(
[101] => Array
(
[0] => ['user_id' => 101, 'item' => 'Book', ...],
[1] => ['user_id' => 101, 'item' => 'Notebook', ...]
)
[102] => Array(...)
[103] => Array(...)
)
*/
| 関数 | 説明 | 第一引数 | 第二引数 |
|---|---|---|---|
array_map() |
全要素に同じ処理を適用する |
callable処理 |
array |
array_filter() |
条件に合う要素だけを抽出する | array |
callable条件 |
array_column() |
多次元配列から特定の列を抜き出す | array |
mixed(キー) |
array_reduce() |
配列の値を集約して1つの結果にする | array |
callable処理 |
(すべて任意の第三引数を取ることが可能)
その他の便利関数
| 関数 | 説明 |
|---|---|
array_keys() |
配列の全てのキーを新しい配列として返す |
array_values() |
配列の全ての値を新しい配列として返す |
array_unique() |
配列から重複した値を削除する |
array_search() |
指定した値を検索し、対応する最初のキーを返す |
sort(), rsort()
|
値でソート(昇順/降順)する |
ksort(), krsort()
|
キーでソート(昇順/降順)する |
usort() |
ユーザー定義の比較関数で値をソートする |
【Tips】実務で知っておくと便利なsort
asort(), arsort(): 連想配列の「キーと値のペアを維持したまま」並び替える
// 徒競走のタイムが早い順にランキングを表示したい
$scores = ['Suzuki' => 8.5, 'Sato' => 9.2, 'Takahashi' => 7.3];
// 昇順でソートする(配列は直接ソートされる)
asort($scores);
print_r($scores);
// Array ( 'Takahashi' => 7.3, 'Suzuki' => 8.5, 'Sato' => 9.2 )
usort(): 複雑な配列を「独自のルールで」並び替える
// 商品を価格(price)が安い順に並び替えたい
$products = [
['name' => 'Desk', 'price' => 15000],
['name' => 'Chair', 'price' => 8000],
['name' => 'Lamp', 'price' => 3000],
];
// キーの'価格'の値が安い順にソートする
usort($products, function($a, $b) {
return $a['price'] <=> $b['price']; // 宇宙船演算子
});
print_r($products)
/*
Array
(
[0] => Array
(
[name] => Lamp
[price] => 3000
)
[1] => Array
(
[name] => Chair
[price] => 8000
)
[2] => Array
(
[name] => Desk
[price] => 15000
)
)
*/
宇宙船演算子 ... 二つの値の大きさを比較するときに使う。(PHP7より登場)
左辺が大きいときは1, 右辺が大きいときは-1, 等しいときは0を返す。
| 関数 | 説明 |
|---|---|
asort(), arsort()
|
連想配列を値を維持したまま並び替える |
usort() |
複雑な配列を独自のルールで並び替える |
なお、ここまで意気揚々と関数の解説を続けてきましたが、ひとつ注意。
コードを書く上で大切なのは、可読性です。
特にarray_reduceなどは非常に煩雑なので、これを使って5行でコードを書くよりも、
foreach文を使って15行で書いた方がチームメンバーにとっては分かりやすいケースもあるでしょう。ケースバイケースで、相談しながら使ってみてくださいね!
4. 💪 実践!配列操作10本ノック
さあ、ここからは実践です!学んだ知識を使って、以下の問題を解いてみましょう。
第1問:基本のcount
$fruits = ['apple', 'banana', 'orange']; の要素数を数えてください。
解答例
$fruits = ['apple', 'banana', 'orange'];
echo count($fruits); // 解答: 3
第2問:基本のin_array
$colors = ['red', 'blue', 'green']; に 'yellow' が含まれているか判定してください。
解答例
$colors = ['red', 'blue', 'green'];
if (in_array('yellow', $colors)) {
echo '含まれています';
} else {
echo '含まれていません'; // 解答: こちらが出力される
}
第3問:array_mapの活用
$names = ['sato', 'suzuki', 'takahashi']; の全要素の最初の文字を大文字にした新しい配列を作成してください。
解答例
$names = ['sato', 'suzuki', 'takahashi'];
$capitalized_names = array_map('ucfirst', $names); // 第一引数はcallableでもOK
print_r($capitalized_names);
// Array ( [0] => Sato [1] => Suzuki [2] => Takahashi )
第4問:array_filterの活用
$scores = [85, 92, 78, 64, 95, 55]; から、80点以上のスコアだけを抽出した配列を作成してください。
解答例
$scores = [85, 92, 78, 64, 95, 55];
$high_scores = array_filter($scores, function($score) {
return $score >= 80;
});
print_r($high_scores);
// Array ( [0] => 85 [1] => 92 [4] => 95 )
第5問:array_columnの活用
以下の $products 配列から、商品名(name)だけを抜き出した配列を作成してください。
$products = [
['id' => 'p001', 'name' => 'Desk', 'price' => 15000],
['id' => 'p002', 'name' => 'Chair', 'price' => 8000],
['id' => 'p003', 'name' => 'Lamp', 'price' => 3000],
];
解答例
// (問題文の$products配列は省略)
$product_names = array_column($products, 'name');
print_r($product_names);
// Array ( [0] => Desk [1] => Chair [2] => Lamp )
第6問:array_reduceの活用
以下の$cart_items配列の合計金額を計算してください
$cart_items = [
['price' => 1000, 'quantity' => 2],
['price' => 500, 'quantity' => 3],
['price' => 800, 'quantity' => 1]
];
解答例
// (問題文の$cart_items配列は省略)
$total_amount = array_reduce($cart_items, function($carry, $item) {
return $carry + ($item['price'] * $item['quantity']);
}, 0);
echo $total_amount; // 4300 (2000 + 1500 + 800)
第7問:関数の組み合わせ ①
以下の $users 配列から、20代(20歳〜29歳)のユーザーの名前だけを抜き出した配列を作成してください。
$users = [
['id' => 1, 'name' => 'Sato', 'age' => 25],
['id' => 2, 'name' => 'Suzuki', 'age' => 30],
['id' => 3, 'name' => 'Takahashi', 'age' => 22],
['id' => 4, 'name' => 'Tanaka', 'age' => 19],
];
解答例
// (問題文の$users配列は省略)
// まず20代のユーザーを抽出
$users_in_20s = array_filter($users, function($user) {
return $user['age'] >= 20 && $user['age'] < 30;
});
// 抽出したユーザーから名前だけを抜き出す
$names_in_20s = array_column($users_in_20s, 'name');
print_r($names_in_20s);
// Array ( [0] => Sato [1] => Takahashi )
第8問:関数の組み合わせ ②
$tags = ['php', 'laravel', 'javascript', 'php', 'vue.js', 'laravel']; から、重複を削除し、アルファベット順にソートした配列を作成してください。
解答例
$tags = ['php', 'laravel', 'javascript', 'php', 'vue.js', 'laravel'];
// 重複を削除
$unique_tags = array_unique($tags);
// ソートする
sort($unique_tags);
print_r($unique_tags);
// Array ( [0] => javascript [1] => laravel [2] => php [3] => vue.js )
第9問:usortによる複雑なソート
以下の $items を、priceが安い順に並び替えてください。
$items = [
['name' => 'Desk', 'price' => 15000],
['name' => 'Chair', 'price' => 8000],
['name' => 'Lamp', 'price' => 3000],
];
解答例
// (問題文の$items配列は省略)
usort($items, function($a, $b) {
// $a['price'] と $b['price'] を比較
return $a['price'] <=> $b['price']; // 宇宙船演算子(PHP7+)
// PHP5の場合: return $a['price'] - $b['price'];
});
print_r($items);
/*
Array
(
[0] => Array
(
[name] => Lamp
[price] => 3000
)
[1] => Array
(
[name] => Chair
[price] => 8000
)
[2] => Array
(
[name] => Desk
[price] => 15000
)
)
*/
第10問:総合問題
以下のユーザーデータがあります。
ここから、アクティブ(isActiveがtrue)なユーザーのうち、所持ポイント(points)が500以上のユーザーをポイントが高い順に並び替え、最終的に'名前: XXポイント'という形式の文字列配列を作成してください。
$users = [
['id' => 1, 'name' => 'A', 'points' => 1200, 'isActive' => true],
['id' => 2, 'name' => 'B', 'points' => 450, 'isActive' => true],
['id' => 3, 'name' => 'C', 'points' => 2000, 'isActive' => false],
['id' => 4, 'name' => 'D', 'points' => 800, 'isActive' => true],
['id' => 5, 'name' => 'E', 'points' => 300, 'isActive' => true],
['id' => 6, 'name' => 'F', 'points' => 1500, 'isActive' => true],
];
解答例
// (問題文の$users配列は省略)
// 1. アクティブかつ500ポイント以上のユーザーをフィルタリング
$filtered_users = array_filter($users, function($user) {
return $user['isActive'] && $user['points'] >= 500;
});
// 2. ポイントで降順ソート
usort($filtered_users, function($a, $b) {
return $b['points'] <=> $a['points']; // 降順なのでbとaを入れ替え
});
// 3. 指定の文字列形式に変換
$result = array_map(function($user) {
return "{$user['name']}: {$user['points']}ポイント";
}, $filtered_users);
print_r($result);
/*
Array
(
[0] => F: 1500ポイント
[1] => A: 1200ポイント
[2] => D: 800ポイント
)
*/
5. まとめ
長い時間、お疲れ様でした~!!!
この記事では、PHPの配列操作について、基礎から応用、演習までを駆け足で解説しました。
array_mapやarray_filter、array_columnといった関数は、多くの場面で僕たちのコードをよりスマートにしてくれる強力な武器だと思います。10本ノックなどを通して、一緒にこれらの使い方へ慣れていきましょう!
実務でのコードの可読性について
一方で、「最も短いコードが、常に最も良いコードとは限らない」という点は、チームで開発を進める上で非常に重要です。
繰り返しになりますが、array_reduceのような複雑な関数は、慣れていない人にとっては処理の流れを追うのが難しい場合があります。
そのような場面では、あえてシンプルなforeachループを使ったり、Laravelなどのフレームワークが提供するcollect()のようなメソッドチェーンを使ったりする方が、チームメンバーにとって直感的で分かりやすい(=リーダブルな)コードになることも少なくないのでしょう。
と言いつつも、今回のような標準関数はPHPが動く場所ならどこでも使える『共通言語』だとも思います!
パフォーマンスの限界を追求する場面や、フレームワークの裏側を理解する上で、『揺るぎない土台』となってくれることを信じて、今日も我々の武器を増やしていきましょう。
6. 参考
【書籍】
【ドキュメント】
【記事】
-
「先入れ先出しの待ち行列」のこと。対義語は「後入れ先出しの待ち行列」である「スタック」で、どちらもデータ構造として基本的な概念。参考: https://qiita.com/drken/items/6a95b57d2e374a3d3292 ↩
-
関数に対して「引数として渡される関数」のこと。突っ込んで調べると筆者も全然分からないが、そのまま「引数として、関数型のなにかをとる」くらいのイメージでも良いかもしれない...。(なお往々にして無名関数が渡されることが多いのでそれを含めて覚えておいても良いかも...?)参考: https://wa3.i-3-i.info/word12295.html ↩