最近、学生向けのチュートリアルを作成していて、誰もが一度は遭遇するであろう問題に遭遇しました。 オブジェクトの配列があり、それぞれのオブジェクトにはidがありました。
この記事では、4つの異なるソリューションをご紹介します。
- Array.forEach
- Array.map
- Array.reduce
- Math.max
Setting up your project
この例を実行するためにNodeを使用していますが、JavaScriptを使用してWebページにドロップすることも同様に簡単です。
const assert = require("assert");
次に、キャラクターの配列を作成します。 私は最近「Bad Blood」という本を読んだので、その本に出てくるキャラクターを使うことにしました。 注: この本をまだ読んでいない方は、ぜひ読んでみてください。
const characters = ;
考えられる各ソリューションにおいて、配列内の最大の id を返すロジックを書きたいと思います。
Array.forEach
最初に思いつくのは、何らかのループを使用して配列を繰り返し処理することかもしれません。
let max = 0;characters.forEach(character => { if (character.id > max) { max = character.id; }});assert(max === 444);
私がコードを書くときの主な目的は、まず何かを動作させ、後でそれを改善することです。 このコードを実行してみると、確かに動作しますが、私には正しいとは思えません。 配列の中に1000個や100万個のオブジェクトがあったらどうしますか?
Array.map
配列を繰り返しているという赤信号が出てきたら、私はすぐに、これは map/filter/reduce で解決できることなのかと自問します。 という疑問が湧いてきます。もし、このようなアプローチを取るのであれば、mapメソッドから始めることができます。 map()
メソッドは、呼び出した配列の各要素に対して指定した関数を呼び出した結果を持つ新しい配列を作成します。 mapメソッドを使用して、idだけを含む新しい配列を作成するので、もはやオブジェクトを扱う必要はありません。
const ids = characters.map(user => user.id);const sorted = ids.sort((a, b) => a - b);assert(sorted === 444);
もし本当に凝ったことをしたいのであれば、これらすべてを 1 つのステートメントで行うことができます。
assert( characters.map(user => user.id).sort((a, b) => a - b) === 444);
Array.reduce
reduce()
メソッドは、(あなたが提供する)reducer関数を配列の各メンバーに対して実行し、結果として単一の出力値を得ます。 ここでは、文字の配列に対してreduceを呼び出すことにします。 また、コールバックの戻り値を蓄積するアキュムレータを定義します。 コールバック関数が呼び出されるたびに値が返され、それがmaxに格納されます。 コールバックメソッドは、キャラクターIDを見て、それがmaxより大きければそれを返し、そうでなければmaxを返します。
const maxId = characters.reduce( (max, character) => (character.id > max ? character.id : max), characters.id);assert(maxId === 444);
お気づきのように、これは forEach メソッドを使用して各要素を繰り返し処理し、それを max と比較するのと同様のアプローチです。 ここでの違いは、reduce を使用する方がはるかに高速であるということです。
拡散演算子
配列を繰り返し処理するよりも、mapとreduceを組み合わせたアプローチの方がはるかに良いのですが、私にとってはまだ良いとは言えません。
つまり、id の配列を取得するだけで、それを max()
関数に渡すことができるのです。 もしあなたが注意を払っていたならば、先ほどmapを使ってすでにそれを行っていました。 つまり、map関数を使ってidの配列を取得し、それをspread演算子を使ってmax()
関数に渡すことができるのです。
assert(Math.max(...characters.map(user => user.id)) === 444);
スプレッド構文では、配列式や文字列などの反復可能なものを、0個以上の引数(関数呼び出しの場合)や要素(配列リテラルの場合)が予想される場所で展開することができます。 また、オブジェクト式は、ゼロ個以上のキーと値のペア(オブジェクトリテラルの場合)が期待される場所に展開されます。
これは私にとって最もすっきりした外観のソリューションで、たまたまとても気に入っています。
パフォーマンスの結果
これを JavaScript の関数型プログラミングに関する記事にするつもりはありませんでしたが、ここにたどり着きました。 各関数の基本的なタイミングを計ってみたところ (console.time()
&console.timeEnd()
を使用)、次のような結果が得られました。
- iterate テスト。 0.217ms
- map test: 0.191ms
- reduce test: 0.144ms
- spread test: 0.148ms
結論
reduce を使用することでほんの少しだけパフォーマンスが向上しましたが、ここでは Math.max と spread 演算子を使用することをお勧めします。 見た目もすっきりしていますし、書いていて楽しいからです。 もしあなたがこの問題を与えられたら、どのような解決策をとりますか? 何か見逃していませんか?
Happy CodingDan
私がこの問題をどのように解決するか、楽しんでいただけたなら幸いです。