序章

JavaScriptの関数型プログラミングは、コードの可読性、保守性、およびテスト性に役立ちます。 関数型プログラミングの考え方のツールの1つは、配列処理スタイルでのプログラミングです。 これには、基本的なデータ構造として配列を使用する必要があります。 次に、プログラムは配列内の要素に対する一連の操作になります。

これが役立つコンテキストはたくさんあります。たとえば、AJAXの結果をmapでReactコンポーネントにマッピングしたり、 filter、および使用 reduce. 「ArrayExtras」と呼ばれるこれらの関数は、 for ループします。 これらの機能では、達成できないことは何もできません。 for、 およびその逆。

このチュートリアルでは、JavaScriptの関数型プログラミングについて、以下を見て理解を深めます。 filter, map、 と reduce.

前提条件

このチュートリアルを完了するには、次のものが必要です。

ステップ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の要点には、両方の元のコードが含まれています tryAllisEnglish 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 関数は持っている必要があります stringkey その引数として:

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 の結果を返すステートメント isEnglishstring 引数として渡されます:

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 メソッドはコールバック関数を保持します。 ようではない mapfilter、に渡されるコールバック reduce 2つの引数を受け入れます。合計累積価格と、合計に追加される配列内の次の価格です。 これは次のようになります totalPricenextPrice それぞれ:

prices.reduce(function (totalPrice, nextPrice) {

})

これをさらに分解するには、 totalPrice のようなものです total 最初の例では。 これまでに受け取ったすべての価格を合計した後の合計価格です。

前の例と比較して、 nextPrice に対応 prices[i]. それを思い出します mapreduce 配列に自動的にインデックスを付け、この値をコールバックに自動的に渡します。 The reduce メソッドは同じことを行いますが、その値を2番目の引数としてコールバックに渡します。

2つ含める console.log 印刷する関数内のステートメント totalPricenextPrice コンソールへ:

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
})

と同じように mapreduce、各反復で、値を返す必要があります。 この場合、その値は 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つの引数が必要です。 courseListcourse:

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);

これにより、出力が生成されます。

Output
The 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 メソッドには、取るコールバック関数があります titleCasedNamesname 引数として:

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 ループ。 しかし、これらの機能を使用して抽象化のレベルを上げると、当然のことながら、読みやすさと保守性にすぐにメリットがあります。

ここから、次のような他の配列メソッドの探索を開始できます。 flattenflatMap. flat()およびflatMap()を使用したVanillaJavaScriptのFlatten Arraysと呼ばれるこの記事は、優れた出発点です。