コールバック関数(反復メソッド)を使ってみて
const transpose = array => array[0].map((col, i) => array.map(row => row[i]));
今年4月からプログラミングを勉強し始めました。
初めて知った言語は、GAS(google apps script)え?今まで何の気無しに使ってたスプレッドシートで、こんな事までできるの?!って衝撃は、半端じゃなかったです。
でもやっぱり素人には、コールバック関数はずっと敷居が高かったです。昨日コールバック関数を勉強して、気づいたことを忘れないようにノートしておきます。
コールバック関数ってそもそも何?
前置きですが、ド素人が初めてコールバック関数を知った状態なので、間違いだらけかも知れません。あと、今回の記事は、配列に対するコールバック関数(反復メソッド)のみを対象にしている気がします。コールバック関数の全体像は、もっと奥が深いものみたいです。とりあえず、何かの関数の引数が、また関数になっているっていう、関数の数珠つなぎ状態を、コールバック関数っていうんかなーと理解しています。
反復メソッドとは?
配列には反復メソッドというものが用意されています。全ての反復メソッドは、コールバック関数の形になっています(と私は思っています)。
反復メソッドは、
1.配列を
2.自動的にforループしてくれて
3.しかもコールバック関数内で何か値をreturnすると
4.不思議な機能を発揮する
大事なのは、2です。配列の処理でforループを自動化してくれるんです!
それだけで私は大満足です。全ての反復メソッドは、この機能を持っています。それだけじゃなくて、更に3、4の機能をつかって、すんごいことが実現できるんですが、とりあえず、初心者は、2の機能さえ理解すれば、感動で涙がでます。
配列に対する反復メソッドには、(私の知ってる限り)以下の種類があります。foreachは、もう使わない方が良いってウワサを聞いたので省きます。
.filter(function) //functionの実行結果がtrueのみの配列を作る
.map(function) //functionの実行結果を、配列にして返す
.every(function) //functionの実行結果が、全てtrueならtrueを返す
.some(function) //functionの実行結果が、1つでもtrueならtrueを返す
.reduce(function) //functionの実行結果を、順番に足して行く
実際に、試してみましょう。filterメソッドです!
実験A:反復メソッドの実験 → 結果は、[2]
function myCoolFunction() {
const originalArr = [1,2,3,4,5]; //元の配列
//ココカラ
const newArr = originalArr.filter(function(value) { return value == 2 });
//ココマデ
console.log(newArr); // [2]
}
実験B:反復メソッドを使わない実験 → 結果は、[2]
function myEasyFunction() {
const originalArr = [1,2,3,4,5]; //元の配列
//ココカラ
const newArr = [];
for(let i=0; i<originalArr.length; i++) {
if(originalArr[i] == 2) {
newArr.push(originalArr[i]);
}
}
//ココマデ
console.log(newArr); // [2]
}
Aの実験、Bの実験共に、やってることも、結果も一緒です。ココカラ~ココマデまでのコードに注目すると、自動的にforループをしてくれていることが分かると思います。
別の実験をします。
function myFantasticTest() {
const arr = [1,2,3,4,5];
//反復メソッドの実験
let a = 0;
arr.filter(function(){ a++; });
let b = 0;
arr.map(function(){ b++; });
let c = 0;
arr.some(function(){ c++; });
let d = 0;
arr.every(function(){ d++; });
let e = 0;
arr.reduce(function(){ e++; });
//forループの実験
let g = 0;
for (let i=0; i<arr.length;i++) {
g++;
}
console.log(`a:${a},b:${b},c:${c},d:${d},e:${e},f:${f},g:${g}`);
//a:5,b:5,c:5,d:1,e:4,f:4,g:5
}
配列に自動的にforループをしてくれるのなら、配列自体と全く関係のない処理をコールバック関数に指定したら、答えが全部いっしょになるはずです。a~eまでの色々な反復メソッドの処理は、全て、gのforループ処理と同じことをしているだけです。
、、、と思ったら意外と答えが違った!everyとreduceだけ答えが違う~。これは意味がわからないので、後日調べます(^_^;)
とにかく、反復メソッドは、配列の要素に対するforループなんです!
じゃあなんで反復メソッドには種類があるの?
これが、
この部分ですね。
反復メソッドの奇跡の機能の部分です。上の実験では、a~eまでコールバック関数内で、何もreturnしていません。単純に、変数++の処理をして終わっています。しかし反復メソッドのコールバック関数内で、何かをreturnしてみると、奇跡が起こります。
反復メソッドの機能
filterメソッド(一番わかりやすい)
→returnがtrueになる要素のみで、新しい配列を返す
感覚的にもわかりやすいですが、条件にあうものだけ残して、後は削除された配列を作れます。
//2次元配列から、1を含む行だけ抜き出す
function myCuteFunction() {
const arr = [
[1,2,3],
[2,2,3],
[3,2,3]
];
//反復メソッドで しかも アロー関数を使うと
const newArr1 = arr.filter(value => value.includes(1));
//forループでやると
const newArr2 = [];
for(let i=0;i<arr.length;i++) {
if(arr[i].includes(1)) {
newArr2.push(arr[i]);
}
}
console.log(newArr1); //[[1,2,3]]
console.log(newArr2); //[[1,2,3]]
}
everyメソッド、someメソッド(この2つはセット)
→returnが全てtrueなら、trueを返す(everyメソッド)
→returnが1つでもtrueなら、trueを返す(someメソッド)
配列の要素を順番に判定していきたい時とかに使えます。
mapメソッド(けっこう分かりづらいけど、結構使われてる)
→returnの「値」を、配列として結合して返す
配列の要素を順番に処理して、結果を再配置してくれます。
reduceメソッド(一番分かりづらいけど、意外と使いやすい)
→returnの「値」を、累積して一つの値にまとめる
コールバック関数のreturnは? メソッドは何を返す?
.filter Boolean フィルターされた配列
.every Boolean 全部がtrueならtrue
.some Boolean 1つでもtrueならtrue
.map 値(数値や文字列、オブジェクト) ←を要素に持つ配列
.reduce 値 ←を累積した値
反復メソッドがとっつきにくい理由(仮引数)
私にとっては、反復メソッドのコールバック関数内の仮引数が謎でした。各反復メソッドは決まった引数のパターンを持っていて、これを知らないとコールバック関数はうまく使えません。
基本形(ほぼ全てこのパターンです)
.filter(function( value, index, array ){ ... });
このvalue, index, arrayの順番と意味が理解できないと、せっかくの反復メソッドは、全然使いこなせないんです。それならそれで、もうこの仮引数はセットにして、固定の仮引数にしてくれたら良いのに、なぜか自由に仮引数名を付けれるようになっていて、しかも、省略までできちゃう。なので、人の書いたコードで勉強しようと思っても、いろんなパターンがあって、初心者の壁として立ちはだかります。
順番と意味(forループに置き換えて考えましょう)
const newArr = [];
for(let i=0;i<arr.length;i++) {
if(arr[i] === 'a') {
newArr.push('a');
}
}
1番目(value):arr[i] にあたる値です。配列の各要素です。
2番目(index):" i " そのものです。配列のインデックス番号です。
3番目(array):もとの配列自体、arrです。
反復メソッド内の関数の仮引数は、この順番で、この意味合いで使用する必要があります。reduceとreduceRightだけは、イレギュラーでちょっと要素が増えますけど、それはまた別の機会に。
配列の各要素のみを使用して処理を行う場合は、
.map(function(value){ ... }) と書けば、2番目、3番目は省略できます。
for( let value of arr ) { ... }と、ほぼ同じ意味を持ちます。
ただfor of文って、便利ですけど、今のインデックス番号がほしくて苦労した事、ないですかね。「しまった!for in文にしておけばよかった」みたいな。
その場合は、2番目のindexを省略せずに書きます。
.map(function(value, index) { ... })と書けば、3番目は省略でOKです。
for( let index in arr ) { ... }と、同じ意味になり、value と arr[index] は、
同じ値です。
前述のようにelement, index, array の仮引数名に制約は無く、好きに命名できます。
最後に、反復メソッドを使ったすんごい技です。
const transpose = array => array[0].map((col, i) => array.map(row => row[i]));
ここで、細かくは説明しませんが、アロー関数と、反復メソッドを贅沢に使い倒す大技です。なんと、このたった1行のコードをコピペして、
const newArr = transpose(arr);
すると、newArrに、元の2次元配列arrの行列を入れ替えた配列が入ります。2次元配列の行列入れ替え、できたら良いなと思ったこと有りませんでしたか?
おわり
コールバック関数のうち、反復メソッドについて、自分の復習のためにまとめてみました。反復メソッドを、forループに置き換えて考えることで、私はアタマの整理ができました!というか、反復「メソッド」ってすごいけど、じつは特別ではなくて、普通に「オブジェクト」の「メソッド」なんですよね。中身は普通にforループで書かれてるんじゃないでしょうか。今度は、クラスの使い方をお勉強するために、反復メソッドを自作してみようかな。
この記事が気に入ったらサポートをしてみませんか?