JavaScriptにメモを実装します

Christopher Nolan
リリース: 2025-02-26 01:50:10
オリジナル
931 人が閲覧しました

Implementing Memoization in JavaScript

コアポイント

  • 暗記は、関数の以前の計算の結果をキャッシュすることにより、関数のパフォーマンスを改善するプログラミング手法です。これは、同じパラメーターをよく使用する再帰的および数学的機能に特に役立ちます。
  • メモリの実装には、関数によって入力パラメーターによってインデックス付けされたキャッシュを使用することが含まれます。パラメーターがキャッシュに存在する場合、キャッシュ値が返されます。
  • 複数のパラメーターを使用した関数に記憶が使用される場合、多次元キャッシュを使用するか、すべてのパラメーターを組み合わせて単一のインデックスを形成することができます。オブジェクトパラメーターは、インデックスとして使用される前に弦を作成する必要があります。
  • 暗記には特定の制限があります。メモリの消費を増加させ、迅速に実行するか、低周波数を呼び出す機能には適していない場合があります。透明な機能への参照でのみ自動化できます。つまり、その出力はその入力のみに依存し、副作用を生成しません。

プログラムは、同じ結果を繰り返し再計算する関数を呼び出す時間を無駄にします。これは、再帰的および数学的な機能に特に当てはまります。フィボナッチ数ジェネレーターは完璧な例です。フィボナッチシーケンスは、ゼロサムから始まる一連の整数であり、各値はシーケンス内の最初の2つの数値の合計です。この定義によると、最初の10個のフィボナッチ数は次のとおりです。0、1、1、2、3、5、8、13、21、34。プログラミングの観点から、フィボナッチ数は通常、次の関数を使用して再帰的に計算されます。 この関数は、より小さな「n」値に対してうまく機能します。ただし、「N」が増加すると、パフォーマンスは急速に低下します。これは、2つの再帰的な呼び出しが同じ作業を繰り返すためです。たとえば、50番目のフィボナッチ数を計算するには、再帰関数を40億回以上呼び出す必要があります(正確には40,730,022,147)!さらに悪いことに、51番目の数値を計算するには、作業をほぼ2回繰り返す必要があります。関数が以前に計算したことを覚えている場合、繰り返し作業の問題を​​軽減できます。

function fibonacci(n) {
  if (n === 0 || n === 1)
    return n;
  else
    return fibonacci(n - 1) + fibonacci(n - 2);
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
暗記の基本

暗記は、関数の以前の計算の結果をキャッシュすることにより、関数のパフォーマンスを改善しようとするプログラミング手法です。 JavaScriptオブジェクトは連想配列のように振る舞うため、キャッシュとして機能するのに理想的です。メモリ関数が呼び出されるたびに、そのパラメーターはインデックスキャッシュに使用されます。データが存在する場合、関数全体を実行せずに返すことができます。ただし、データがキャッシュされていない場合、関数が実行され、結果がキャッシュに追加されます。

次の例では、元のフィボナッチ関数を書き直してメモリを含めます。この例では、自己執行匿名関数は、フィボナッチ関数として使用される内部関数f()を返します。 f()が返されると、その閉鎖により、以前のすべての結果を保存する「メモ」オブジェクトへのアクセスを継続できます。 f()が実行されるたびに、最初に現在の「n」値の結果が存在するかどうかを確認します。存在する場合、キャッシュ値が返されます。それ以外の場合は、元のFibonacciコードを実行します。 「メモ」はf()の外側で定義されているため、複数の関数呼び出しで値を保持できることに注意してください。元の再帰関数は、50番目のフィボナッチ数を計算する前に400億回以上呼び出されたことを思い出してください。メモリを実装することにより、この数値は99に低下します。

function fibonacci(n) {
  if (n === 0 || n === 1)
    return n;
  else
    return fibonacci(n - 1) + fibonacci(n - 2);
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

複数のパラメーターの処理

前の例では、関数は単一のパラメーターを受け入れます。これにより、キャッシュの実装が非常に簡単になります。残念ながら、ほとんどの関数には複数のパラメーターが必要であり、キャッシュされたインデックスを複雑にします。複数のパラメーターを使用して関数を記憶するには、キャッシュを多次元にする必要があります。または、すべてのパラメーターを組み合わせて単一のインデックスを形成する必要があります。

多次元法では、キャッシュは単一のオブジェクトではなく、オブジェクトの階層になります。次に、各ディメンションは単一のパラメーターによってインデックス付けされます。次の例では、フィボナッチ関数の多次元キャッシュを実装しています。この例では、関数は追加のパラメーター「x」を受け入れ、何もしません。関数が呼び出されるたびに、コードは「x」ディメンションが存在するかどうかをチェックし、存在しない場合に初期化します。それ以来、「x」ディメンションを使用して「n」値をキャッシュします。その結果、関数はFibonacci( "foo"、3)とFibonacci( "bar"、3)を呼び出しても同じ結果とは見なされません。

var fibonacci = (function() {
  var memo = {};

  function f(n) {
    var value;

    if (n in memo) {
      value = memo[n];
    } else {
      if (n === 0 || n === 1)
        value = n;
      else
        value = f(n - 1) + f(n - 2);

      memo[n] = value;
    }

    return value;
  }

  return f;
})();
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

多次元キャッシュに代わるものは単一のキャッシュオブジェクトで、関数のすべてのパラメーターの組み合わせによってインデックス付けされます。この方法では、パラメーターは配列に変換され、インデックスキャッシュに使用されます。各関数には、パラメーターに合格した「引数」という名前の組み込みオブジェクトがあります。 「引数」は、クラスの配列オブジェクトと呼ばれるタイプのオブジェクトです。アレイに似ていますが、インデックスキャッシュには使用できません。したがって、最初に実際の配列に変換する必要があります。これは、配列スライス()メソッドを使用して実行できます。以前に示すように、キャッシュを配列表現を使用してインデックス化できます。次の例は、これを達成する方法を示しています。追加の変数「スライス」は、配列スライス()メソッドへの参照として定義されていることに注意してください。このリファレンスを保存することにより、array.prototype.slice()の繰り返し計算を避けることができます。次に、call()メソッドを使用して、slice()を「引数」に適用します。

var fibonacci = (function() {
  var memo = {};

  function f(x, n) {
    var value;

    memo[x] = memo[x] || {};

    if (x in memo && n in memo[x]) {
      value = memo[x][n];
    } else {
      if (n === 0 || n === 1)
        value = n;
      else
        value = f(x, n - 1) + f(x, n - 2);

      memo[x][n] = value;
    }

    return value;
  }

  return f;
})();
ログイン後にコピー
ログイン後にコピー

オブジェクトパラメーターをキャッシュ

ここに導入されたメモリスキームは、オブジェクトパラメーターをうまく処理しません。オブジェクトがインデックスとして使用される場合、最初に「[オブジェクトオブジェクト]」などの文字列表現に変換されます。これにより、複数のオブジェクトが同じキャッシュの場所に誤ってマッピングされます。この動作は、インデックス作成の前にオブジェクトパラメーターを描画することで修正できます。残念ながら、これはメモリプロセスも遅くなります。次の例では、オブジェクトを引数として取る一般的なメモリ関数を作成します。オブジェクトパラメーターは、json.stringify()を使用してstringifiedを使用してキャッシュされたインデックスを作成することに注意してください。

function fibonacci(n) {
  if (n === 0 || n === 1)
    return n;
  else
    return fibonacci(n - 1) + fibonacci(n - 2);
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

自動メモリ

以前のすべての例では、メモリを追加するように関数が明示的に変更されます。暗記されたインフラストラクチャも、機能を変更せずに実装できます。これは、関数ロジックを記憶されたロジックとは別に実装できるようにするため有用です。これは、関数を入力として使用し、それを適用して記憶するために適用するユーティリティ関数を作成することによって行われます。次のMemoize()関数は、関数「FUNC」を入力として使用します。 Memoize()は、「FUNC」にキャッシュメカニズムをラップする新しい関数を返します。この関数はオブジェクトパラメーターを処理しないことに注意してください。オブジェクトを処理するには、各パラメーターを個別にチェックし、必要に応じてStringifyを確認するループが必要です。

var fibonacci = (function() {
  var memo = {};

  function f(n) {
    var value;

    if (n in memo) {
      value = memo[n];
    } else {
      if (n === 0 || n === 1)
        value = n;
      else
        value = f(n - 1) + f(n - 2);

      memo[n] = value;
    }

    return value;
  }

  return f;
})();
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

制限

メモリを実現する場合、次のポイントを記憶する必要があります。まず、古い結果を保存することにより、記憶関数は余分なメモリを消費します。フィボナッチの例では、余分なメモリ消費量は無制限です。メモリの使用が問題の場合、固定サイズのキャッシュを使用する必要があります。メモリに関連付けられているオーバーヘッドは、非常に迅速に実行したり、頻度が低い関数に適していない場合もあります。

メモリの最大の制限は、関数にのみ自動的に適用できることです。関数の出力がその入力のみに依存し、副作用を生成しない場合、関数は参照透過と見なされます。参照透過関数は、プログラムのセマンティクスを変更せずにリターン値に置き換えることができます。フィボナッチ関数は、「n」の値に完全に依存するため、透明性が参照されます。次の例では、関数foo()は、グローバル変数「bar」を使用するため、透過的ではありません。 「bar」はfoo()の外側で変更できるため、入力値ごとに返品値が変更されないという保証はありません。この例では、2つの呼び出しに渡されたパラメーターが同じであっても、2つの呼び出しはfoo()を返す値2と3を返します。

var fibonacci = (function() {
  var memo = {};

  function f(x, n) {
    var value;

    memo[x] = memo[x] || {};

    if (x in memo && n in memo[x]) {
      value = memo[x][n];
    } else {
      if (n === 0 || n === 1)
        value = n;
      else
        value = f(x, n - 1) + f(x, n - 2);

      memo[x][n] = value;
    }

    return value;
  }

  return f;
})();
ログイン後にコピー
ログイン後にコピー
覚えておくべきこと<
  • 暗記は、以前の関数呼び出しの結果をキャッシュすることにより、パフォーマンスを改善する可能性があります。
  • メモリ関数は、入力パラメーターによってインデックス付けされたキャッシュを保存します。パラメーターがキャッシュに存在する場合、キャッシュ値が返されます。それ以外の場合は、関数を実行して、新しく計算された値をキャッシュに追加します。
  • オブジェクトパラメーターは、インデックスとして使用する前にストリング化する必要があります。
  • 記憶を自動的に適用して、透過機能を参照することができます。
  • 暗記は、頻繁に呼ばれたり、迅速に実行されたりしない機能には理想的ではない場合があります。
JavaScript(FAQ)

に記憶する

FAQ

JavaScriptでメモリを使用する主な目的は何ですか?

暗記は、JavaScriptやその他の言語のコンピュータープログラムを最適化するために使用されるプログラミング手法です。この手法では、同じ入力が再び表示されたときに、高価な関数呼び出しの結果を保存し、それらを再利用することが含まれます。これにより、不必要な計算を回避することにより、プログラムのパフォーマンスが大幅に向上する可能性があります。同じパラメーターで繰り返し呼び出される再帰関数または関数がある状況で特に役立ちます。

JavaScriptでメモリはどのように機能しますか?

JavaScriptでは、メモリは、関数呼び出しの結果を保存するキャッシュを作成することで動作します。関数を呼び出すとき、関数は最初に指定された入力の結果がすでにキャッシュにあるかどうかを確認します。その場合、関数は計算を再度実行する代わりにキャッシュ結果を返します。結果がキャッシュにない場合、関数は計算を実行し、結果をキャッシュに保存し、結果を返します。

JavaScriptで記憶された機能の例を提供できますか?

もちろん、数値の要因を計算する単純な関数の例を考えてみましょう。メモリがなければ、関数は次のようになります:

function fibonacci(n) {
  if (n === 0 || n === 1)
    return n;
  else
    return fibonacci(n - 1) + fibonacci(n - 2);
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
メモリを使用して、以前の関数呼び出しの結果を保存することにより、この関数を最適化できます。

JavaScriptでメモリを使用することに制限や短所はありますか?
var fibonacci = (function() {
  var memo = {};

  function f(n) {
    var value;

    if (n in memo) {
      value = memo[n];
    } else {
      if (n === 0 || n === 1)
        value = n;
      else
        value = f(n - 1) + f(n - 2);

      memo[n] = value;
    }

    return value;
  }

  return f;
})();
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

暗記はJavaScriptプログラムのパフォーマンスを大幅に改善できますが、制限がないわけではありません。メモリの主な欠点は、特に多くのデータをキャッシュに保存する場合、多くのメモリを消費できることです。適切に管理されていない場合、これはパフォーマンスの問題につながる可能性があります。さらに、メモリは、関数が同じパラメーターで複数回呼び出される場合にのみ有効です。関数入力が常に異なる場合、暗記はパフォーマンスの利点を提供しません。

JavaScriptのあらゆる種類の機能に暗記を使用できますか?

純粋な機能で使用すると、暗記が最も効果的です。純粋な関数とは、常に同じ入力と同じ結果を返す関数であり、副作用を生成しません。関数を記憶すると、外部状態に依存している場合、または副作用がある場合、予期しない結果につながる可能性があります。したがって、JavaScriptのあらゆる種類の機能に技術的に暗記を使用できますが、純粋な機能に最適です。

javascriptの複数のパラメーターで関数を暗記する方法は?

複数のパラメーターを使用した関数のメモリの実装は少し複雑になる可能性がありますが、これは間違いなく可能です。 1つの方法は、引数をキャッシュのキーとして使用できる文字列に変換することです。例は次のとおりです。

function fibonacci(n) {
  if (n === 0 || n === 1)
    return n;
  else
    return fibonacci(n - 1) + fibonacci(n - 2);
}
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

JavaScriptで記憶するのに役立つライブラリやツールはありますか?

はい、JavaScriptで記憶するのに役立つライブラリとツールがあります。たとえば、人気のあるJavaScriptユーティリティライブラリLodashは、機能を簡単に記憶できる_.memoize関数を提供します。同様に、RAMDAライブラリは、カスタムキャッシュキー関数を指定できるR.memoizeWith関数を提供します。

メモリ機能でキャッシュをクリアする方法は?

メモリ関数のキャッシュは、キャッシュオブジェクトをリセットするだけでクリアできます。例は次のとおりです。

var fibonacci = (function() {
  var memo = {};

  function f(n) {
    var value;

    if (n in memo) {
      value = memo[n];
    } else {
      if (n === 0 || n === 1)
        value = n;
      else
        value = f(n - 1) + f(n - 2);

      memo[n] = value;
    }

    return value;
  }

  return f;
})();
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー
ログイン後にコピー

反応などのJavaScriptフレームワークでメモリを使用できますか?

はい、メモリはReactなどのJavaScriptフレームワークで非常に役立ちます。たとえば、Reactはコンポーネントを記憶するために使用できるReact.memo関数を提供します。これは、コンポーネントの不必要に再レンダリングを防ぐことにより、パフォーマンスを改善するのに役立ちます。

他の最適化技術と比較して、JavaScriptのメモリはどうですか?

記憶は強力な最適化手法ですが、必ずしも最良の解決策ではありません。場合によっては、dejitterやスロットリングなどの他の最適化技術がより適切になる場合があります。重要なのは、プログラムの特定のニーズと制約を理解し、適切な最適化テクノロジーを選択することです。

以上がJavaScriptにメモを実装しますの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。

このウェブサイトの声明
この記事の内容はネチズンが自主的に寄稿したものであり、著作権は原著者に帰属します。このサイトは、それに相当する法的責任を負いません。盗作または侵害の疑いのあるコンテンツを見つけた場合は、admin@php.cn までご連絡ください。
著者別の最新記事
人気のチュートリアル
詳細>
最新のダウンロード
詳細>
ウェブエフェクト
公式サイト
サイト素材
フロントエンドテンプレート