JavaScriptでmap()、filter()、reduce()を使用する方法
序章
JavaScriptの関数型プログラミングは、コードの可読性、保守性、およびテスト性に役立ちます。 関数型プログラミングの考え方のツールの1つは、配列処理スタイルでのプログラミングです。 これには、基本的なデータ構造として配列を使用する必要があります。 次に、プログラムは配列内の要素に対する一連の操作になります。
これが役立つコンテキストはたくさんあります。たとえば、AJAXの結果をmapでReactコンポーネントにマッピングしたり、 filter
、および使用 reduce
. 「ArrayExtras」と呼ばれるこれらの関数は、 for
ループします。 これらの機能では、達成できないことは何もできません。 for
、 およびその逆。
このチュートリアルでは、JavaScriptの関数型プログラミングについて、以下を見て理解を深めます。 filter
, map
、 と reduce
.
前提条件
このチュートリアルを完了するには、次のものが必要です。
- JavaScriptの実用的な理解。 詳細については、JavaScriptでコーディングする方法シリーズを確認してください。
- 構築および実装方法の理解
for
JavaScriptでループします。 JavaScriptのforループに関するこのの記事は、始めるのに最適な場所です。 - Node.jsはローカルにインストールされます。これは、Node.jsのインストール方法とローカル開発環境の作成に従って実行できます。
ステップ1—で繰り返す forEach
for
ループは、配列内のすべてのアイテムを反復処理するために使用されます。 通常、途中で各アイテムに何かが行われます。
1つの例は、配列内のすべての文字列を大文字にすることです。
const strings = ['arielle', 'are', 'you', 'there'];
const capitalizedStrings = [];
for (let i = 0; i < strings.length; i += 1) {
const string = strings[i];
capitalizedStrings.push(string.toUpperCase());
}
console.log(capitalizedStrings);
このスニペットでは、次のような小文字のフレーズの配列から始めます。 strings
. 次に、と呼ばれる空の配列 capitalizedStrings
初期化されます。 The capitalizedStrings
配列は大文字の文字列を格納します。
の内部 for
ループでは、すべての反復の次の文字列が大文字になり、にプッシュされます capitalizedStrings
. ループの終わりに、 capitalizedStrings
のすべての単語の大文字バージョンが含まれています strings
.
The forEach
関数を使用して、このコードをより簡潔にすることができます。 これは、リストを「自動的に」ループする配列メソッドです。 つまり、カウンターの初期化とインクリメントの詳細を処理します。
上記の代わりに、手動でインデックスを作成します strings
、あなたは呼び出すことができます forEach
そして、すべての反復で次の文字列を受け取ります。 更新されたバージョンは次のようになります。
const strings = ['arielle', 'are', 'you', 'there'];
const capitalizedStrings = [];
strings.forEach(function (string) {
capitalizedStrings.push(string.toUpperCase());
})
console.log(capitalizedStrings);
これは初期関数に非常に近いです。 しかし、それは必要性を取り除きます i
カウンター、コードを読みやすくします。
これはまたあなたが何度も何度も見るであろう主要なパターンを紹介します。 つまり、メソッドを使用するのが最善です Array.prototype
カウンターの初期化やインクリメントなどの詳細を抽象化します。 そうすれば、重要なロジックに集中できます。 この記事で説明する配列メソッドは他にもいくつかあります。 今後は、暗号化と復号化を使用して、これらの方法で何ができるかを完全に示します。
ステップ2—シーザー暗号と暗号化および復号化を理解する
以下のスニペットでは、配列メソッドを使用します map
, reduce
、 と filter
文字列を暗号化および復号化します。
最初に暗号化とは何かを理解することが重要です。 次のような通常のメッセージを送信する場合 'this is my super-secret message'
友人や他の誰かがそれを手に入れると、意図した受信者でなくてもすぐにメッセージを読むことができます。 パスワードなど、誰かが聞いている可能性のある機密情報を送信する場合、これは悪いことです。
文字列を暗号化するということは、「文字列をスクランブリングして、スクランブリングを解除せずに読みにくくする」ことを意味します。 このように、誰かが聞いていて、彼らがあなたのメッセージを傍受したとしても、彼らがそれを解読するまで、それは読めないままになります。
暗号化にはさまざまな方法があり、シーザー暗号はこのような文字列をスクランブルする1つの方法です。 この暗号は、コードで使用できます。 と呼ばれる定数変数を作成します caesar
. コード内の文字列を暗号化するには、次の関数を使用します。
var caesarShift = function (str, amount) {
if (amount < 0) {
return caesarShift(str, amount + 26);
}
var output = "";
for (var i = 0; i < str.length; i++) {
var c = str[i];
if (c.match(/[a-z]/i)) {
var code = str.charCodeAt(i);
if (code >= 65 && code <= 90) {
c = String.fromCharCode(((code - 65 + amount) % 26) + 65);
}
else if (code >= 97 && code <= 122) {
c = String.fromCharCode(((code - 97 + amount) % 26) + 97);
}
}
output += c;
}
return output;
};
このGitHubの要点には、EvanHahnによって作成されたこのシーザー暗号関数の元のコードが含まれています。
シーザー暗号で暗号化するには、キーを選択する必要があります n
、1〜25で、元の文字列のすべての文字を1文字に置き換えます n
アルファベットのさらに下の文字。 したがって、キー2を選択すると、 a
になります c
; b
になります d
; c
になります e
; 等
このような文字に置き換えると、元の文字列が判読できなくなります。 文字列は文字をずらしてスクランブルをかけているので、元に戻すとスクランブルを解除できます。 キー2で暗号化されていることがわかっているメッセージを受け取った場合、復号化するために必要なのは、文字を2スペース戻すことだけです。 そう、 c
になります a
; d
になります b
; 等 シーザー暗号の動作を確認するには、 caesarShift
関数と文字列を渡します 'this is my super-secret message.'
最初の引数と数として 2
2番目の引数として:
const encryptedString = caesarShift('this is my super-secret message.', 2);
このコードでは、メッセージは各文字を2文字前方にシフトすることによってスクランブルされます。 a
になります c
; s
になります u
; 等 結果を確認するには、 console.log
印刷する encryptedString
コンソールへ:
const encryptedString = caesarShift('this is my super-secret message.', 2);
console.log(encryptedString);
上記の例を引用すると、メッセージ 'this is my super-secret message'
スクランブルメッセージになります 'vjku ku oa uwrgt-ugetgv oguucig.'
.
残念ながら、この形式の暗号化は簡単に破られます。 シーザー暗号で暗号化された文字列を復号化する1つの方法は、可能なすべてのキーを使用して文字列を復号化することです。 結果の1つは正しいでしょう。
今後のコード例のいくつかでは、暗号化されたメッセージを復号化する必要があります。 これ tryAll
関数を使用してそれを行うことができます:
const tryAll = function (encryptedString) {
const decryptionAttempts = []
while (decryptionAttempts.length < 26) {
const decryptionKey = -decryptionAttempts.length;
const decryptionAttempt = caesarShift(encryptedString, decryptionKey);
decryptionAttempts.push(decryptionAttempt)
}
return decryptionAttempts;
};
上記の関数は暗号化された文字列を受け取り、可能なすべての復号化の配列を返します。 それらの結果の1つは、必要な文字列になります。 したがって、これは常に暗号を破ります。
26の可能な復号化の配列をスキャンすることは困難です。 間違いなく間違っているものを排除することは可能です。 この機能を使用できます isEnglish
これをする:
'use strict'
const fs = require('fs')
const _getFrequencyList = () => {
const frequencyList = fs.readFileSync(`${__dirname}/eng_10k.txt`).toString().split('\n').slice(1000)
const dict = {};
frequencyList.forEach(word => {
if (!word.match(/[aeuoi]/gi)) {
return;
}
dict[word] = word;
})
return dict;
}
const isEnglish = string => {
const threshold = 3;
if (string.split(/\s/).length < 6) {
return true;
} else {
let count = 0;
const frequencyList = _getFrequencyList();
string.split(/\s/).forEach(function (string) {
const adjusted = string.toLowerCase().replace(/\./g, '')
if (frequencyList[adjusted]) {
count += 1;
}
})
return count > threshold;
}
}
このGitHubの要点には、両方の元のコードが含まれています tryAll
と isEnglish
PelekeSengstackeによって作成されました。
必ずこの英語で最も一般的な1,000語のリストを次のように保存してください eng_10k.txt
.
これらの関数をすべて同じJavaScriptファイルに含めることも、各関数をモジュールとしてインポートすることもできます。
The isEnglish
functionは文字列を読み取り、その文字列に英語で最も一般的な1,000語がいくつ出現するかをカウントし、文中に3つ以上の単語が見つかった場合、その文字列を英語として分類します。 文字列にその配列からの単語が3つ未満含まれている場合、文字列は破棄されます。
上のセクションで filter
、を使用します isEnglish
関数。
これらの関数を使用して、配列メソッドの方法を示します map
, filter
、 と reduce
仕事。 The map
メソッドについては、次のステップで説明します。
ステップ3—使用 map
配列を変換する
リファクタリング for
使用するループ forEach
このスタイルの利点を示唆しています。 しかし、まだ改善の余地があります。 前の例では、 capitalizedStrings
配列はコールバック内で更新されています forEach
. これには本質的に何も問題はありません。 ただし、可能な限り、このような副作用を回避することをお勧めします。 別のスコープにあるデータ構造を更新する必要がない場合は、更新を避けるのが最善です。
この特定のケースでは、すべての文字列を strings
大文字のバージョンに。 これは、 for
ループ:すべてを配列に取り、それを別のものに変換して、結果を新しい配列に収集します。
配列内のすべての要素を新しい要素に変換し、結果を収集することをマッピングと呼びます。 JavaScriptには、このユースケース用の組み込み関数があります。 map
. The forEach
メソッドが使用されるのは、反復変数を管理する必要性を抽象化するためです。 i
. これは、本当に重要なロジックに集中できることを意味します。 同様に、 map
空の配列の初期化とプッシュを抽象化するために使用されます。 と同じように forEach
各文字列値で何かを行うコールバックを受け入れ、 map
各文字列値で何かを行うコールバックを受け入れます。
最後の説明の前に、簡単なデモを見てみましょう。 次の例では、暗号化関数が使用されます。 あなたは使用することができます for
ループまたは forEach
. しかし、使用するのが最善です map
この場合には。
使用方法を示すために map
関数、2つの定数変数を作成します:1つは呼び出されます key
値が12で、配列が messages
:
const key = 12;
const messages = [
'arielle, are you there?',
'the ghost has killed the shell',
'the liziorati attack at dawn'
]
次に、という定数を作成します encryptedMessages
. 使用 map
上の機能 messages
:
const encryptedMessages = messages.map()
内部 map
、パラメータを持つ関数を作成します string
:
const encryptedMessages = messages.map(function (string) {
})
この関数内で、 return
暗号関数を返すステートメント caesarShift
. The caesarShift
関数は持っている必要があります string
と key
その引数として:
const encryptedMessages = messages.map(function (string) {
return caesarShift(string, key);
})
印刷 encryptedMessages
結果を確認するには、コンソールに移動します。
const encryptedMessages = messages.map(function (string) {
return caesarShift(string, key);
})
console.log(encryptedMessages);
ここで何が起こったかに注意してください。 The map
メソッドが使用されました messages
各文字列を暗号化するには caesar
関数を実行し、結果を新しい配列に自動的に格納します。
上記のコードを実行した後、 encryptedMessages
次のようになります: ['mduqxxq, mdq kag ftqdq?', 'ftq staef tme wuxxqp ftq etqxx', 'ftq xuluadmfu mffmow mf pmiz']
. これは、手動で配列にプッシュするよりもはるかに高いレベルの抽象化です。
リファクタリングできます encryptedMessages
矢印関数を使用してコードをより簡潔にします。
const encryptedMessages = messages.map(string => caesarShift(string, key));
これで、方法を完全に理解できました map
動作します、あなたは使用することができます filter
配列メソッド。
ステップ4—使用 filter
配列から値を選択するには
別の一般的なパターンは、 for
配列内のアイテムを処理するためにループしますが、配列アイテムの一部のみをプッシュ/保持します。 通常は、 if
ステートメントは、保持するアイテムと破棄するアイテムを決定するために使用されます。
生のJavaScriptでは、これは次のようになります。
const encryptedMessage = 'mduqxxq, mdq kag ftqdq?';
const possibilities = tryAll(encryptedMessage);
const likelyPossibilities = [];
possibilities.forEach(function (decryptionAttempt) {
if (isEnglish(decryptionAttempt)) {
likelyPossibilities.push(decryptionAttempt);
}
})
The tryAll
関数は復号化に使用されます encryptedMessage
. つまり、26の可能性の配列になってしまうことを意味します。
ほとんどの復号化の試みは読み取り可能ではないため、 forEach
ループは、復号化された各文字列が英語であるかどうかを確認するために使用されます。 isEnglish
関数。 英語の文字列は、という配列にプッシュされます likelyPossibilities
.
これは一般的な使用例です。 だから、それのための組み込みがあります filter
. と同じように map
, filter
各文字列も取得するコールバックが与えられます。 違いは、 filter
コールバックが戻った場合にのみ、アイテムを配列に保存します true
.
上記のコードスニペットをリファクタリングして使用できます filter
それ以外の forEach
. The likelyPossibilities
変数は空の配列ではなくなります。 代わりに、 possibilities
配列。 電話する filter
上の方法 possibilities
:
const likelyPossibilities = possibilities.filter()
内部 filter
、と呼ばれるパラメータを受け取る関数を作成します string
:
const likelyPossibilities = possibilities.filter(function (string) {
})
この関数内で、 return
の結果を返すステートメント isEnglish
と string
引数として渡されます:
const likelyPossibilities = possibilities.filter(function (string) {
return isEnglish(string);
})
もしも isEnglish(string)
戻り値 true
, filter
保存します string
新しいで likelyPossibilities
配列。
このコールバックは isEnglish
、このコードはさらに簡潔にするためにリファクタリングできます。
const likelyPossibilities = possibilities.filter(isEnglish);
The reduce
メソッドは、知っておくことが非常に重要なもう1つの抽象化です。
ステップ5—使用 reduce
配列を単一の値に変換するには
配列を反復処理してその要素を単一の結果に収集することは、非常に一般的な使用例です。
この良い例は、 for
ループして数値の配列を反復処理し、すべての数値を合計します。
const prices = [12, 19, 7, 209];
let totalPrice = 0;
for (let i = 0; i < prices.length; i += 1) {
totalPrice += prices[i];
}
console.log(`Your total is ${totalPrice}.`);
の数字 prices
ループスルーされ、各番号がに追加されます totalPrice
. The reduce
メソッドは、このユースケースの抽象化です。
上記のループは次のようにリファクタリングできます reduce
. あなたは必要ありません totalPrice
もう変数。 電話する reduce
上の方法 prices
:
const prices = [12, 19, 7, 209];
prices.reduce()
The reduce
メソッドはコールバック関数を保持します。 ようではない map
と filter
、に渡されるコールバック reduce
2つの引数を受け入れます。合計累積価格と、合計に追加される配列内の次の価格です。 これは次のようになります totalPrice
と nextPrice
それぞれ:
prices.reduce(function (totalPrice, nextPrice) {
})
これをさらに分解するには、 totalPrice
のようなものです total
最初の例では。 これまでに受け取ったすべての価格を合計した後の合計価格です。
前の例と比較して、 nextPrice
に対応 prices[i]
. それを思い出します map
と reduce
配列に自動的にインデックスを付け、この値をコールバックに自動的に渡します。 The reduce
メソッドは同じことを行いますが、その値を2番目の引数としてコールバックに渡します。
2つ含める console.log
印刷する関数内のステートメント totalPrice
と nextPrice
コンソールへ:
prices.reduce(function (totalPrice, nextPrice) {
console.log(`Total price so far: ${totalPrice}`)
console.log(`Next price to add: ${nextPrice}`)
})
更新する必要があります totalPrice
それぞれの新しいものを含める nextPrice
:
prices.reduce(function (totalPrice, nextPrice) {
console.log(`Total price so far: ${totalPrice}`)
console.log(`Next price to add: ${nextPrice}`)
totalPrice += nextPrice
})
と同じように map
と reduce
、各反復で、値を返す必要があります。 この場合、その値は totalPrice
. だから作成する return
の声明 totalPrice
:
prices.reduce(function (totalPrice, nextPrice) {
console.log(`Total price so far: ${totalPrice}`)
console.log(`Next price to add: ${nextPrice}`)
totalPrice += nextPrice
return totalPrice
})
The reduce
メソッドは2つの引数を取ります。 1つ目は、すでに作成されているコールバック関数です。 2番目の引数は、の開始値として機能する数値です。 totalPrice
. これはに対応します const total = 0
前の例では。
prices.reduce(function (totalPrice, nextPrice) {
console.log(`Total price so far: ${totalPrice}`)
console.log(`Next price to add: ${nextPrice}`)
totalPrice += nextPrice
return totalPrice
}, 0)
あなたが今見たように、 reduce
数値の配列を合計に収集するために使用できます。 しかし reduce
用途が広いです。 数値だけでなく、配列を単一の結果に変換するために使用できます。
例えば、 reduce
文字列を作成するために使用できます。 これが実際に動作することを確認するには、最初に文字列の配列を作成します。 以下の例では、次のような一連のコンピュータサイエンスコースを使用しています。 courses
:
const courses = ['Introduction to Programming', 'Algorithms & Data Structures', 'Discrete Math'];
と呼ばれる定数変数を作成します curriculum
. 電話する reduce
上の方法 courses
. コールバック関数には2つの引数が必要です。 courseList
と course
:
const courses = ['Introduction to Programming', 'Algorithms & Data Structures', 'Discrete Math'];
const curriculum = courses.reduce(function (courseList, course) {
});
courseList
それぞれの新しいものを含めるために更新する必要があります course
:
const courses = ['Introduction to Programming', 'Algorithms & Data Structures', 'Discrete Math'];
const curriculum = courses.reduce(function (courseList, course) {
return courseList += `\n\t${course}`;
});
The \n\t
それぞれの前にインデント用の改行とタブを作成します course
.
の最初の引数 reduce
(コールバック関数)が完了しました。 数値ではなく文字列が作成されているため、2番目の引数も文字列になります。
以下の例では、 'The Computer Science curriculum consists of:'
の2番目の引数として reduce
. 追加する console.log
印刷するステートメント curriculum
コンソールへ:
const courses = ['Introduction to Programming', 'Algorithms & Data Structures', 'Discrete Math'];
const curriculum = courses.reduce(function (courseList, course) {
return courseList += `\n\t${course}`;
}, 'The Computer Science curriculum consists of:');
console.log(curriculum);
これにより、出力が生成されます。
OutputThe Computer Science curriculum consists of:
Introduction to Programming
Algorithms & Data Structures
Discrete Math
先に述べたように、 reduce
用途が広いです。 配列を任意の種類の単一の結果に変換するために使用できます。 その単一の結果は配列でさえありえます。
文字列の配列を作成します。
const names = ['arielle', 'jung', 'scheherazade'];
The titleCase
関数は、文字列の最初の文字を大文字にします。
const names = ['arielle', 'jung', 'scheherazade'];
const titleCase = function (name) {
const first = name[0];
const capitalizedFirst = first.toUpperCase();
const rest = name.slice(1);
const letters = [capitalizedFirst].concat(rest);
return letters.join('');
}
titleCase
で文字列の最初の文字をつかむことにより、文字列の最初の文字を大文字にします 0
インデックス、使用 toUpperCase
その手紙に、文字列の残りをつかんで、すべてを一緒に結合します。
と titleCase
その場で、と呼ばれる定数変数を作成します titleCased
. に等しく設定します names
と呼び出す reduce
上の方法 names
:
const names = ['arielle', 'jung', 'scheherazade'];
const titleCase = function (name) {
const first = name[0];
const capitalizedFirst = first.toUpperCase();
const rest = name.slice(1);
const letters = [capitalizedFirst].concat(rest);
return letters.join('');
}
const titleCased = names.reduce()
The reduce
メソッドには、取るコールバック関数があります titleCasedNames
と name
引数として:
const names = ['arielle', 'jung', 'scheherazade'];
const titleCase = function (name) {
const first = name[0];
const capitalizedFirst = first.toUpperCase();
const rest = name.slice(1);
const letters = [capitalizedFirst].concat(rest);
return letters.join('');
}
const titleCased = names.reduce(function (titleCasedNames, name) {
})
コールバック関数内で、と呼ばれる定数変数を作成します titleCasedName
. 電話する titleCase
関数とパスイン name
引数として:
const names = ['arielle', 'jung', 'scheherazade'];
const titleCase = function (name) {
const first = name[0];
const capitalizedFirst = first.toUpperCase();
const rest = name.slice(1);
const letters = [capitalizedFirst].concat(rest);
return letters.join('');
}
const titleCased = names.reduce(function (titleCasedNames, name) {
const titleCasedName = titleCase(name);
})
これにより、の各名前が大文字になります names
. コールバック関数の最初のコールバック引数 titleCasedNames
配列になります。 押す titleCasedName
(大文字版の name
)この配列に戻り、 titleCaseNames
:
const names = ['arielle', 'jung', 'scheherazade'];
const titleCase = function (name) {
const first = name[0];
const capitalizedFirst = first.toUpperCase();
const rest = name.slice(1);
const letters = [capitalizedFirst].concat(rest);
return letters.join('');
}
const titleCased = names.reduce(function (titleCasedNames, name) {
const titleCasedName = titleCase(name);
titleCasedNames.push(titleCasedName);
return titleCasedNames;
})
The reduce
メソッドには2つの引数が必要です。 1つ目は、コールバック関数が完了したことです。 このメソッドは新しい配列を作成しているため、初期値は空の配列になります。 また、含める console.log
最終結果を画面に印刷するには:
const names = ['arielle', 'jung', 'scheherazade'];
const titleCase = function (name) {
const first = name[0];
const capitalizedFirst = first.toUpperCase();
const rest = name.slice(1);
const letters = [capitalizedFirst].concat(rest);
return letters.join('');
}
const titleCased = names.reduce(function (titleCasedNames, name) {
const titleCasedName = titleCase(name);
titleCasedNames.push(titleCasedName);
return titleCasedNames;
}, [])
console.log(titleCased);
コードを実行すると、次の大文字の名前の配列が生成されます。
Output["Arielle", "Jung", "Scheherazade"]
使いました reduce
小文字の名前の配列をタイトル大文字の名前の配列に変換します。
前の例はそれを証明します reduce
数値のリストを単一の合計に変換するために使用でき、文字列のリストを単一の文字列に変換するために使用できます。 ここでは、 reduce
小文字の名前の配列を大文字の名前の単一の配列に変換します。 大文字の名前の単一のリストは依然として単一の結果であるため、これは依然として有効なユースケースです。 プリミティブ型ではなく、たまたまコレクションです。
結論
このチュートリアルでは、使用方法を学びました map
, filter
、 と reduce
より読みやすいコードを書くために。 を使用しても問題はありません for
ループ。 しかし、これらの機能を使用して抽象化のレベルを上げると、当然のことながら、読みやすさと保守性にすぐにメリットがあります。
ここから、次のような他の配列メソッドの探索を開始できます。 flatten
と flatMap
. flat()およびflatMap()を使用したVanillaJavaScriptのFlatten Arraysと呼ばれるこの記事は、優れた出発点です。