プログラミングにおいて、配列から特定の要素だけを取り出して新しい配列を作りたいということはよくあります。
この記事では、JavaScriptで配列の特定の要素を取り出す方法を3つ紹介します。
1つ目の方法は、Array.prototype.filter() であらかじめ対象の要素のみをフィルタする方法です。
先に取り出したい要素を Array.prototype.filter() しておき、Array.prototype.map() を使って任意の配列に加工します。
下記は、18歳以上のユーザーのidリストを作るコード例です。
const users = [
{id: 1, age: 12},
{id: 2, age: 18},
{id: 3, age: 20},
]
const adaltUserIds = users.filter((user) => user.age >= 18).map((user) => user.id)
// => [2, 3] 2つ目の方法は、Array.prototype.filter()で後からfalse値を除外する方法です。
この方法は、対象とならない値が null などのfalse値になる場合に利用できます。
下記は、有効なユーザーのニックネームリストを作るコード例です。
const users = [
{id: 1, nickname: 'John'},
{id: 2, nickname: ''},
{id: 3, nickname: 'Sam'},
]
const nicknames = users.map((user) => user.nickname).filter(Boolean)
// => ['John', 'Sam']
.filter(Boolean) とすることでfalse値が除外され、true値のみフィルタされます。
JavaScriptにおいて空文字 '' はfalse値になるので、id: 2 のユーザーを除いたニックネームが得られます。
さて、ここまで紹介した2つの方法はシンプルで考えやすい反面、ほんの少しデメリットがあります。
それはループが2回あるということです。
つまり、1つ目の方法ならば .filter() で配列を走査し、.map() で再度配列を走査しています。
2つ目の方法も順番が異なるだけで .map() と .filter() で2回配列を走査しています。
もし、1回のループで目的の配列を作ることができれば、その方が効率が良さそうです。
そんなとき Array.prototype.reduce() を使えば1回のループで配列を任意に加工することができます。
1つ目の18歳以上のユーザーのidリストを作るコードは、 Array.prototype.reduce() を使って下記のように書き換えることができます。
const users = [
{id: 1, age: 12},
{id: 2, age: 18},
{id: 3, age: 20},
]
const adaltUserIds = users.reduce((acc, user) => {
if (user.age >= 18) {
acc.push(user.id)
}
return acc
}, [])
// => [2, 3]
Array.prototype.reduce() は第1引数にコールバック関数、第2引数に初期値を受け取ります。
ここでコールバック関数とは、渡された配列の要素を加工する関数です。このコールバック関数で返された値が、次のループへと渡ります。
コールバック関数は仮引数を持ち、第1引数が前回のコールバック関数の結果、第2引数がループの対象となっている配列の要素です。ここでは前者を acc (accumulatorの略)、後者を user という名前で受け取っています。
acc は前回のコールバック関数の結果ですが、1回目のループでは前回の結果は存在しません。そこで初期値が使われます。ここでは空配列 [] をセットしていますね。
1ループずつ順を追って処理を見てみましょう。
1ループ目は acc は初期値に設定した空配列 [] になります。user は0番目の {id: 1, age: 12} がセットされます。
コールバック関数内のif文は age >= 18 ならばという条件のためif文の中には入りません。
コールバック関数の戻り値として空の配列 [] が返されます。
2ループ目は acc は1ループ目の戻り値である空配列 [] がセットされ、user は0始まりで1番目の {id: 2, age: 18} がセットされます。
コールバック関数内のif文の条件 age >= 18 に当てはまるため、今回はif文の中に入ります。
acc.push(user.id) によって acc そのものが [2] に変化し、これが2ループ目の戻り値となります。
3ループ目は acc は2ループ目の戻り値である配列 [2] がセットされ、user は0始まりで2番目の {id: 3, age: 20} がセットされます。
今回もコールバック関数内のif文の条件 age >= 18 に当てはまるためif文の中に入ります。
acc.push(user.id) によって acc は [2, 3] に変化し、これが最終的な Array.prototype.reduce() の戻り値となります。
1つずつ順を追って見ていくと理解できたのではないでしょうか?
最初のうちはなかなか慣れないかもしれませんが、使いこなせれば Array.prototype.reduce() で配列ではなくオブジェクトを返すなんてこともできるので非常に便利です。
使っているうちに慣れてくるのでぜひチャレンジしてみてください。
練習として、2つ目の有効なユーザーのニックネームリストを作るコードを、Array.prototype.reduce() を使って書き換えにチャレンジしてみてください。
解答例
const users = [
{id: 1, nickname: 'John'},
{id: 2, nickname: ''},
{id: 3, nickname: 'Sam'},
]
const nicknames = users.reduce((acc, user) => {
if (user.nickname) {
acc.push(user.nickname)
}
return acc
}, [])
// => ['John', 'Sam'] プログラミングにおいて計算量はオーダーという尺度で表されます。
この記事では、計算の無駄を減らすために2回のループを1回に減らす例を紹介しましたが、今回紹介した1~3のいずれのケースも計算量のオーダーは同じ となります。
これは、計算量はデータの個数に単純比例するという意味で、一般的には問題ない計算量とされます。
ではどんなときにオーダーが増加するかというと、ループの内側にさらにループを作ったときです。
例えば2重ループの場合は、 という、データの個数の2乗に比例するというオーダーになります。
しかし、 であっても、競技プログラミングなどの特殊な状況を除けば問題のない計算量です。
個人的な所感ですが、大量のデータ(100万〜)を高速に処理しなければならないという状況を除いて、3重ループ 程度までであればそれほど問題視する必要はありません。
どうしてもオーダーを落としたい場合に、2重3重ループを減らすプログラミングテクニックもあります。それはまたいずれ別の記事で紹介したいと思います。
以上です。この記事では、JavaScriptで配列の特定の要素を取り出す方法を3つ紹介しました。
コメントを送る
コメントはブログオーナーのみ閲覧できます