見出し画像

コールバック関数(反復メソッド)を使ってみて

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ループなんです!

じゃあなんで反復メソッドには種類があるの?

これが、

3.しかもコールバック関数内で何か値をreturnすると
4.不思議な機能を発揮する

この部分ですね。
反復メソッドの奇跡の機能の部分です。上の実験では、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ループで書かれてるんじゃないでしょうか。今度は、クラスの使い方をお勉強するために、反復メソッドを自作してみようかな。

この記事が気に入ったらサポートをしてみませんか?