TypeScript 実践アルゴリズムシリーズ (XII): Map と HashMap の実装

TypeScript 実践アルゴリズムシリーズ (XII): Map と HashMap の実装

この記事では、辞書とハッシュテーブルの実装のアイデアを詳しく説明し、TypeScript を使用して実装します。興味のあるフロントエンド開発者は、ぜひこの記事を読んでください。

[[342361]]

実装のアイデア
辞書とハッシュ テーブルはキーと値のペアの形式でデータを保存するため、JavaScript のオブジェクトを使用して実装できます。

辞書の実装
辞書は、キーと値のペアの形式でデータを格納します。そのキーは文字列型であり、呼び出し元が渡すキーになります。

完全な辞書クラスには、キーが辞書内にあるかどうかを判断する、辞書に要素を追加する、キーに従って辞書に格納されている要素を削除する、キーに従って辞書内の要素を検索する、辞書に格納されているすべての要素を取得するなどのメソッドが必要です。次に、これらのメソッドの実装アイデアを分析しましょう。

辞書(セット)に要素を追加する

setメソッドは、キーと値の2つのパラメータを受け入れます。
パラメータの有効性を判断します。キーと値が null | undefined でない場合は、要素を辞書に追加し、それ以外の場合は直接 false を返します。
パラメータが有効な場合、パラメータキーを文字列に変換し、文字列に変換されたキーを辞書のキーとして使用し、キーと値をオブジェクトに格納し、文字列に変換されたキーにオブジェクトを格納します。
上記の操作の後、辞書に要素が正常に追加され、true が返されました。
キーが辞書内に存在するかどうかを判定する (hasKey)

hasKeyメソッドは1つのパラメータを受け入れます: key
辞書内のデータはオブジェクトの形式で保存されているため、キーを直接文字列に変換し、辞書オブジェクトの属性として渡すことができます。返された結果が undefined | null であるかどうかを判断することで、キーが辞書内にあるかどうかを知ることができます。
キーに応じて辞書に格納されている値を取得する (get)

getメソッドは1つのパラメータを受け入れます: key
キーを文字列に変換し、それを辞書オブジェクトの属性として渡し、変数を使用してその戻り値を受け取ります。
戻り値が null かどうかを判定します | undefined
戻り値が null | undefined でない場合はオブジェクト内の値を返し、それ以外の場合は undefined を返します。
キーに基づいて辞書から要素を削除します (削除)

削除メソッドは1つのパラメータを受け入れます: key
対象パラメータが辞書オブジェクトに存在するかどうかを判断し(hasKeyメソッドを呼び出し)、存在しない場合は直接falseを返します。
辞書オブジェクト内に対象要素が存在する場合は、キーを文字列に変換し、辞書オブジェクトにパラメータとして渡します。最後に、オブジェクトの delete メソッドを呼び出して対象キーを削除し、true を返します。
辞書に保存されているすべてのオブジェクトを取得します (keyValues)

keyValues メソッドはパラメータを一切受け取らず、オブジェクトの配列を返します。まず、取得したオブジェクトを格納するための配列変数 (valuePairs) を宣言します。辞書オブジェクト内のすべてのキーを取得します。
取得したキーを走査し、走査したキーをパラメーターとして辞書オブジェクトに渡します。
辞書オブジェクトから返された値を valuePairs に入れて返します。
辞書に格納されているすべてのキー(keys)を取得する & 辞書に格納されているすべての値(value)を取得する

keys メソッドは、取得したキーを格納するための配列変数 (keys) を宣言するための任意のパラメータを受け入れます | 取得した値を格納するための配列変数 (values) を宣言します
辞書に格納されているすべてのオブジェクトを取得します(keyValuesメソッドを呼び出します)
トラバースして取得したオブジェクト配列のキーを取得したい場合は、現在トラバースしている要素のキー値を keys 配列に格納し、それ以外の場合は values 配列に値の値を格納します。
戻りキー | 値
辞書内のデータの反復処理 (forEach)

forEachメソッドはコールバック関数をパラメータとして受け取り、そのコールバック関数にはキーと値の2つのパラメータがあります。
辞書内のすべてのデータを取得します。取得したデータをトラバースし、コールバック関数のパラメータを呼び出して、現在トラバースしているオブジェクトのキーと値をコールバック関数に渡し、変数 (result) を使用して結果を保存します。 result が false の場合、辞書内の要素がトラバースされたことを意味します。ループを終了して辞書のサイズ (size) を取得し、keyValues メソッドを呼び出して、その配列の長さを返します。

辞書が空かどうか (isEmpty) を判断し、size メソッドを呼び出して 0 かどうかを判断して、結果を返します。

辞書をクリア(クリア)し、辞書オブジェクトを空のオブジェクトに初期化するだけです

辞書内のデータを文字列に変換する (toString)

toString メソッドはパラメータを受け取りません。辞書が空の場合は、空の文字列を直接返します。
辞書が空でない場合は、辞書内のすべてのデータを取得します。
辞書内の各オブジェクトを格納する変数 (objString) を宣言します。その初期値は、辞書オブジェクト配列のトラバース番号 0 で取得したオブジェクトです。トラバースしたデータと objString を連結し、objString を返します。
ハッシュテーブルの実装
ハッシュ テーブル (ハッシュ テーブルとも呼ばれる) は、辞書の別の実装です。ハッシュ テーブルと辞書の違いは、キー値の格納方法です。

辞書は要素を格納するときに、要素を格納する前にキーを文字列に変換します。

ハッシュ テーブルに要素を格納する場合、キーをハッシュし、ハッシュ値を取得した後に要素を格納します。

要素を検索する場合、辞書はデータ構造全体を反復処理して対象要素を見つける必要がありますが、ハッシュ テーブルはハッシュ値によって格納されます。対象要素の場所をすばやく見つけるには、対象要素のハッシュ値を計算するだけで済みます。したがって、ハッシュ テーブルは辞書よりも効率的です。

ハッシュテーブルはハッシュテーブルと多くの類似点がありますが、異なるキーにデータを格納するため、使用するメソッドをインターフェースに記述し、それぞれの特性に応じてこのインターフェースを実装する必要があります。次に、辞書とは異なるハッシュテーブルでのメソッドの実装を分析しましょう。

ハッシュテーブルに要素を追加する (put)

辞書の実装と同様に、有効かどうかを判断するための 2 つのパラメーターを受け取ります。キーがパラメーターとして使用され、hashCode 関数が呼び出され (独自に実装)、そのハッシュ値が計算されます。取得されたハッシュ値はハッシュ テーブルにキーとして保存され、その値は辞書と一致します。
ハッシュ値を計算する(hashCode)

ハッシュ値を生成するためのソリューションは多数あります。この記事では、最も一般的に使用される 2 つのソリューションのみを紹介します。
使用したいハッシュ関数を呼び出します (loseloseHashCode | djb2HashCode)
loseloseHashCodeはハッシュ値を計算します

まず、キーが数字かどうかを判断します。数字の場合は、実行せずにそのまま返します。キーを文字列に変換するためのハッシュ演算は行いません。ハッシュ値のトラバーサルから文字列に変換されたキーを格納する変数 (ハッシュ) を宣言します。js charCodeAt 関数を呼び出して、各文字の Unicode エンコーディングを見つけます。トラバーサルが完了すると、取得した Unicode コードがハッシュに追加されます。値が大きくなりすぎないように、ハッシュ値を 37 で割って余りを取り、ハッシュを返します。
djb2HashCodeはハッシュ値を計算します

loseloseHashCode メソッドと同じように、キーが数値かどうかをチェックし、それを文字列に変換します。違いは、ハッシュの初期値が 5381 であることです。
キーを文字列に変換してからトラバースし、トラバースした各文字の Unicode コードを取得し、ハッシュ値に 33 を掛けて、取得した Unicode コードを加算します。トラバースが完了したら、ハッシュ値を 1013 で割って余りを取り、ハッシュを返します。
キーに従ってハッシュテーブル内の要素を取得する (get)

キーをハッシュして結果を取得し、それをハッシュ テーブル オブジェクトにパラメータとして渡し、ハッシュ テーブル内の対象キーの要素を取得して、結果が null | undefined かどうかを判断します。そうであれば undefined を返し、そうでない場合はその値を返します。
キーに基づいてハッシュテーブルから要素を削除します (削除)

キーをハッシュして、そのハッシュ値がハッシュ内にあるかどうかを判断します。ない場合は false を返します。
キーはハッシュテーブルにあり、計算されたハッシュ値はハッシュテーブルに属性として渡され、deleteメソッドが呼び出されて対象要素のキーが削除され、trueが返されます。
他のメソッドは辞書と同じ方法で実装されますが、唯一の違いはキーの処理方法です。

ハッシュ テーブルでのハッシュ値の競合の処理<br /> HashMap を使用する場合、loseloseHashCode メソッドを呼び出してハッシュ値を計算すると、競合率が非常に高くなります。ここでは、ハッシュ衝突の問題に対処するためによく使用される 2 つの方法を紹介します。

別リンク
個別リンク方式では、ハッシュ テーブル内の各位置に対してリンク リストが作成され、その中に要素が格納されます。これは競合を解決する最も簡単な方法ですが、追加のストレージスペースを占有します。

分離リンク方式はHashMapのストレージ構造を変更するだけなので、HashMapを継承してHashMapと異なるメソッドを書き換えることができます。

プライベート属性テーブルの変数名を変更します。分離リンク方式の値はリンクリスト型であり、HashMapはValuePair型を使用しているため、jsには実際のプライベート属性はなく、そのテーブル属性の型は継承中に変更できないため、変数名(tableLink)を変更する必要があります。

putメソッドをオーバーライドする

HashMap と同様に、キーと値の有効性を判断し、キーのハッシュ値を計算し、それを変数 (位置) に格納し、位置をパラメーターとして tableLink に渡して、null | undefined かどうかを判断します。
null | undefined でない場合は、tableLink の位置にリンク リストを作成し、Key & value オブジェクトを tableLink の位置のリンク リストに追加し、追加が成功した場合は true を返します。
get メソッドを書き直す (リンクリストから要素を取得する必要があります)

キーのハッシュ値を計算し、変数(位置)に格納します。位置に格納されているリンクリスト構造の要素を取得します。リンクリストが空でない場合は、リンクリストの先頭から、現在トラバースされているリンクリスト要素のキーがターゲットパラメータのキーと同じになるまでトラバースし、対応する値を返します。リンクリストが空の場合は、undefined を返します。
削除メソッドを書き直す(リンクリストから要素を削除する必要があります)

キーのハッシュ値を計算し、変数(位置)に格納します。その位置にあるリンクリスト構造の要素を取得します。リンクリストが空でない場合は、リンクリストの先頭から走査を開始します。
現在走査されているリンク リスト要素がターゲット パラメーター キーと同じ場合、現在のリンク リスト内の要素はリンク リストから削除されます。
削除後、リンクリストが空の場合は、tableLinkの位置要素を直接削除します。リンクリストが空の場合は、undefinedを返します。
clearメソッドを書き直し、tableLinkを空のオブジェクトにポイントします。

keyValuesメソッドを書き換えます。HashMapはリンクリストを格納しており、格納されたオブジェクト(valuePair)をリンクリストから取得する必要があります。

取得したValuePairオブジェクトを格納するための配列変数(valuePairs)を宣言します。tableLink内のすべてのキーを取得し、int型に変換して変数(keys)に格納します。
キーをトラバースし、現在トラバースしているリンク リスト構造内の要素のデータを取得し、変数 (linkedList) に格納します。linkedList が空でない場合は、リンク リストの先頭からリンク リスト内のデータをトラバースし、現在トラバースしているリンク リスト内の要素を取得し、valuePairs に格納します。
戻り値ペア
リニアプローブ
競合を解決するもう 1 つの方法は線形探索です。これは、別のデータ構造ではなくテーブルに要素を直接格納することで競合を処理するため、線形と呼ばれます。

テーブル内の特定の位置に新しい要素を追加する場合、インデックス位置がすでに占有されているときは、位置 + 1 を試します。位置 + 1 も占有されている場合は、位置 + 2 を試し、ハッシュ テーブルで空いている位置が見つかるまでこれを繰り返します。

次に、線形プローブを使用して競合を解決するためにどのメソッドを書き直す必要があるかを見てみましょう。

putメソッドをオーバーライドする

HashMapと同様に、パラメータの有効性と渡されたパラメータの数を判断してキーのハッシュ値を計算し、それを変数(位置)に格納する必要があります。
テーブルの位置が占有されているかどうかを判断します。占有されていない場合は、その位置に新しいオブジェクトを作成し、キーと値をその中に格納します。占有されている場合は、変数 (インデックス) を使用して、位置 + 1 の値を受け取ります。テーブルのインデックス位置の値をトラバースします。空でない場合、インデックスは増加し続けます。
テーブル内に空の位置が見つかった場合、テーブルのインデックス位置にキーと値を格納するための新しいオブジェクトが作成され、true が返されます。
getメソッドをオーバーライドする

キーのハッシュ値を計算し、変数(位置)に格納します。
テーブルの位置にある要素が null | undefined かどうかを判定し、そうでない場合は undefined を返します。
テーブルの位置にある要素のキーがターゲット パラメータのキーと等しいかどうかを判断します。等しい場合は、位置にある値を直接返します。等しくない場合は、変数 (インデックス) を使用して位置 + 1 にある値を格納します。テーブルのインデックスにある値を走査します。インデックスにある値が空でなく、インデックスにあるキーがターゲット パラメータのキーと等しくない場合は、インデックスが増分されます。ループが終了したら、現在のテーブルのインデックスにある要素のキーがターゲット パラメータのキーと等しいかどうかを判断します。等しい場合は、インデックスにある値を返します。
削除メソッドをオーバーライドする

キーのハッシュ値を計算し、変数(位置)に格納します。
テーブルの位置が null かどうかを判断します。null の場合は、直接 false を返します。
テーブルの位置のキーがターゲットパラメータのキーと等しい場合は、その位置の要素を削除し、この削除に副作用があるかどうかを確認し、要素の位置を調整してtrueを返します。
等しくない場合は、位置の後の値を見つけるための変数 (インデックス) を宣言する必要があります。デフォルト値は位置 + 1 です。
テーブルのインデックス位置の要素をトラバースします。それが null でなく、そのキーがターゲット キーと等しくない場合、インデックスが増分されます。トラバース手法の後、インデックス位置の要素が null でなく、インデックス位置の要素のキーがターゲット パラメータのキーと等しい場合、テーブルのインデックス位置の要素を削除し、この削除に副作用があるかどうかを確認し、要素の位置を調整して、true を返します。
削除操作に副作用があるかどうかを検証するための新しいメソッド (verifyRemoveSideEffect) が追加されました。要素の削除後に競合が発生した場合は、空の位置が生成されないように、競合する要素を前の位置に移動する必要があります。

verifyRemoveSideEffect メソッドは、削除するキーとテーブル内の削除されたキーの位置 (removedPosition) というパラメータを受け取ります。
キーのハッシュ値を計算し、変数(ハッシュ)に格納する
削除されたキー位置の次の位置(インデックス)を受け取る変数を使用します。デフォルトは removedPosition+1 です。
テーブルを走査し、インデックス位置の要素が null でない場合は、現在のインデックス位置のキーのハッシュ値を取得し、それを変数 (posHash) に格納します。
posHash が hash 以下、または posHash が removedPosition 以下である場合、テーブル内のインデックスの要素を removedPosition に割り当てます。テーブル内のインデックスの要素を削除し、インデックスを removedPosition に割り当てます。
次のサイクル判定に進む
実装コード
分析後、実装のアイデアが得られました。次に、上記のアイデアをコードに変換します。

マップインターフェースの作成
辞書とハッシュ テーブルには多くの共通メソッドがあるため、共通メソッドをインターフェイスに分離し、さまざまなニーズに応じて異なるインターフェイスを実装する必要があります。

新しいMap.tsファイルを作成します。新しいdictionary-list-models.tsファイルを作成します。dictionary-list-modelsにValuePairクラスを追加してエクスポートします。このクラスは辞書に値を格納するために使用されます。

  1. // オブジェクトを生成する
  2. エクスポートクラスValuePair<K,V>{
  3. コンストラクタ(パブリック キー: K、公開値: V) {}
  4.  
  5. 文字列(){
  6. `[#${this.key } : ${this.value}]`を返します
  7. }
  8. }

ValuePairクラスをMapにインポートし、後で必要になるメソッドを追加して定義し、戻り値の型を指定します。

  1. {ValuePair}インポート  "../../utils/dictionary-list-models.ts" ;
  2.  
  3. デフォルトインターフェースMap<K,V>をエクスポートします。
  4. hasKey(キー: K ): ブール値;
  5. 設定?(キー:K、値:V):ブール値;
  6. put?(キー: K、値: V ): ブール値;
  7. ハッシュコード?(キー: K ): 数値;
  8. 削除(キー:K):ブール値;
  9. get(キー:K):V|未定義;
  10. キー値(): ValuePair<K, V>[];
  11. キー():K[];
  12. ():V[];
  13. forEach(callbackFn: (キー: K、値: V) => any ): void;
  14. サイズ(): 数値;
  15. 空かどうかをチェックする
  16. クリア():void;
  17. toString():文字列;
  18. }

辞書クラスの実装
新しいDictionary.tsファイルを作成し、Mapインターフェースを実装するためのDictionaryクラスを追加します。

  1. デフォルトのクラスDictionary<K, V>をエクスポートし、 Map<K, V>を実装します。
  2.  
  3. }

辞書保存用のプライベート属性テーブルを宣言する

  1. プライベートテーブル: { [キー: 文字列]: ValuePair<K, V> };

コンストラクタでテーブルを初期化し、値から文字列への関数を宣言してデフォルト値を割り当てます。

  1. // toStrFn は値を文字列に変換するために使用されます。このロジックを自分で実装し、インスタンス化時に渡すことができます。
  2. コンストラクター(プライベートtoStrFn: (キー:K) =>文字列=defaultToString) {
  3. テーブルを次のように変更します。
  4. }

setメソッドの実装

  1. // 辞書に要素を追加する
  2. キー:K、値:V)を設定します
  3. if (キー!= null && 値 != null ) {
  4. //キーを文字列に変換します。辞書に必要なキーは文字列形式です。
  5. const tableKey = this.toStrFn(キー);
  6. this.table [tableKey] = 新しい ValuePair(キー、 値 );
  7. 戻る 真実;
  8. }
  9. 戻る 間違い;
  10. }

hasKeyメソッドの実装

  1. hasKey(キー:K) {
  2. this.table [ this.toStrFn( key )]!= nullを返します
  3. }

getメソッドの実装

  1. get(キー:K) {
  2. const valuePair = this.table [this.toStrFn(キー)];
  3. 戻り値 valuePair == null ? 未定義: valuePair.value;
  4. }

削除メソッドの実装

  1. 削除(キー:K) {
  2. if (this.hasKey(キー)) {
  3. this.table [ this.toStrFn( key )]を削除します
  4. 戻る 真実;
  5. }
  6. 戻る 間違い;
  7. }

keyValuesメソッドの実装

  1. キー値(): 値のペア<K, V>[] {
  2. /* ES2017で導入されたObject.valuesメソッドを使用して、オブジェクトに格納されている対応するすべてのキーの値を直接取得し、配列に格納します */
  3. 定数値のペア = [];
  4. const keys = Object.keys( this.table );
  5. ( i = 0 とします; i < keys.length; i++){
  6. valuePairs.push( this.table [keys[i]])
  7. }
  8. valuePairsを返します
  9. }

キーメソッドの実装

  1. キー(){
  2. // マップを直接使用してオブジェクトのキーを取得できます 
  3. // this.keyValues().map(valuePair=> valuePair.key )返します
  4. 定数キー = [];
  5. 定数 valuePairs = this.keyValues();
  6. ( i = 0 とします; i < valuePairs.length; i++) {
  7. キーをプッシュします(valuePairs[i] .key );
  8. }
  9. リターンキー。
  10. }

値メソッドの実装

  1. (){
  2. 定数値= [];
  3. 定数 valuePairs = this.keyValues();
  4. ( i = 0 とします; i < valuePairs.length; i++) {
  5. ​​.push(valuePairs[i].value);
  6. }
  7. 戻る ​​;
  8. }

forEach メソッドの実装

  1. forEach(コールバックFn: (キー: K, 値: V) =>任意) {
  2. 定数 valuePairs = this.keyValues();
  3. ( i = 0 とします; i < valuePairs.length; i++) {
  4. const result = callbackFn(valuePairs[i] .key , valuePairs[i].value);
  5. 結果が偽の場合
  6. 壊す;
  7. }
  8. }
  9. }

size、isEmpty、clearメソッドを実装する

  1. サイズ() {
  2. this.keyValues().lengthを返します
  3. }
  4.  
  5. 空です() {
  6. this.size () === 0を返します
  7. }
  8.  
  9. クリア() {
  10. テーブルを次のように変更します。
  11. }

toString メソッドの実装

  1. 文字列を渡す
  2. 空の場合(){
  3. 戻る  '' ;
  4. }
  5.  
  6. 定数 valuePairs = this.keyValues();
  7. objString = `${valuePairs[0].toString()}` とします。
  8. ( i = 1 とします; i < valuePairs.length; i++) {
  9. objString = `${objString},${valuePairs[i].toString()}`;
  10. }
  11. objStringを返します
  12. }

完全なコードについては、Dictionary.ts をご覧ください。

テストコードを書く
上記では辞書クラスを実装しました。次に、上記のコードが正常に実行されるかどうかをテストしてみましょう。

  1. 定数辞書 = 新しい辞書();
  2. dictionary.set ( "name" , "张三" );
  3. dictionary.set ( "年齢" ,20);
  4. dictionary.set ( "id" ,198);
  5. console.log( "名前が辞書にあるかどうかを判断します" , dictionary.hasKey( "name" ));
  6. // idという名前のキーを削除します 
  7. dictionary.remove( "id" );
  8. console.log( "idが辞書内にあるかどうかを判断します" , dictionary.hasKey( "id" ));
  9. console.log( "辞書に保存されているデータを文字列に変換します" , dictionary.toString())
  10. // 辞書内のnameという名前のを取得します
  11. console.log( "辞書内の name という名前の値" 、 dictionary.get( "name" ));
  12. // 辞書に格納されているすべての値を取得します
  13. console.log( "辞書に保存されているすべての値" , dictionary.keyValues());
  14. // 辞書内のすべてのキーを取得します
  15. console.log( "辞書に保存されているすべてのキー" , dictionary.keys());
  16. // 辞書内のすべての値を取得する
  17. console.log("辞書に保存されているすべての" , dictionary.values ());
  18. // 辞書内の各キーと値のペアを反復処理します
  19. 定数obj = {};
  20. dictionary.forEach(関数(キー, 値) {
  21. obj[キー] = 値;
  22. })
  23. コンソールログ(obj)

完全なコードについては、DictionaryTest.js を参照してください。

ハッシュテーブルの実装
新しい HashMap クラスを作成し、Map インターフェースを実装します。

  1. HashMap<K,V>クラスをエクスポートし、Map<K, V>を実装します{
  2.  
  3. }

テーブルを宣言し、そのタイプを指定する

  1. 保護されたテーブル:{[キー:数値]:ValuePair<K, V>};

コンストラクタでテーブルを初期化し、値から文字列への関数を指定して、呼び出し元が値文字列関数を渡せるようにします。

  1. コンストラクター(保護されたtoStrFn: (キー: K) => 文字列 = defaultToString) {
  2. テーブルを次のように変更します。
  3. }

hashCode、loseLoseHashCode、djb2HashCodeメソッドを実装する

  1. // ハッシュコードを生成する
  2. ハッシュコード(キー:K):数値{
  3. this.loseloseHashCode(キー)を返します
  4. }
  5.  
  6. // loseloseはハッシュ関数を実装します
  7. loseloseHashCode(キー: K ): 数値 {
  8. if (typeof key === "number" ){
  9. 戻る ;
  10. }
  11. const tableKey = this.toStrFn(キー);
  12. ハッシュを 0 にします。
  13. ( i = 0 とします; i < tableKey.length; i++){
  14. // 各文字のASCIIコードを取得し、それをハッシュに連結します
  15. ハッシュ += tableKey.charCodeAt(i);
  16. }
  17. ハッシュ%37を返します
  18. }
  19.  
  20. // djb2 はハッシュ関数を実装します
  21. djb2HashCode(キー: K ): 数値 {
  22. if (typeof key === "number" ){
  23. 戻る ;
  24. }
  25.  
  26. // パラメータを文字列に変換する
  27. const tableKey = this.toStrFn(キー);
  28. ハッシュ = 5381;
  29. ( i = 0 とします; i < tableKey.length; i++){
  30. ハッシュ = (ハッシュ * 33) + tableKey.charCodeAt(i);
  31. }
  32. ハッシュ%1013を返します
  33. }

putメソッドの実装

  1. put(キー:K、値:V):ブール値{
  2. if (キー!= null && 値 != null ) {
  3. const 位置 = this.hashCode(キー);
  4. this.table [位置] = new ValuePair(キー、値 );
  5. 戻る 真実;
  6. }
  7. 戻る 間違い;
  8. }

getメソッドの実装

  1. get(キー:K):V|未定義{
  2. const valuePair = this.table [this.hashCode(キー)];
  3. 戻り値 valuePair == null ? 未定義: valuePair.value;
  4. }

hasKeyメソッドの実装

  1. hasKey(キー:K):ブール値{
  2. this.table [ this.hashCode( key )]!= nullを返します
  3. }

削除メソッドの実装

  1. 削除(キー:K):ブール値{
  2. if (this.hasKey (キー)) {
  3. this.table [ this.hashCode( key )]を削除します
  4. 戻る 真実;
  5. }
  6. 戻る 間違い;
  7. }

keyValues、キー、値のメソッドを実装する

  1. キー値(): 値のペア<K, V>[] {
  2. 定数値のペア = [];
  3. // オブジェクト内のすべてのキーを取得し、 int型の配列に変換します
  4. const keys = Object.keys( this.table ).map(item => parseInt(item));
  5. ( i = 0 とします; i < keys.length; i++){
  6. valuePairs.push( this.table [keys[i]]);
  7. }
  8. valuePairsを返します
  9. }
  10.  
  11. キー():K[] {
  12. 定数キー = [];
  13. 定数 valuePairs = this.keyValues();
  14. ( i = 0 とします; i < valuePairs.length; i++){
  15. キーをプッシュします(valuePairs[i] .key );
  16. }
  17. リターンキー。
  18. }
  19.    
  20. ():V[] {
  21. 定数値= [];
  22. 定数 valuePairs = this.keyValues();
  23. ( i = 0 とします; i < valuePairs.length; i++){
  24. ​​.push(valuePairs[i].value);
  25. }
  26. 戻る ​​;
  27. }

isEmpty、size、clearメソッドを実装する

  1. isEmpty():ブール値 {
  2. this.values ().length === 0を返します
  3. }
  4.    
  5. サイズ():数値{
  6. this.keyValues().lengthを返します
  7. }
  8.    
  9. クリア(): void {
  10. テーブルを次のように変更します。
  11. }

forEach メソッドの実装

  1. forEach(callbackFn: (キー: K, 値: V) =>任意): void {
  2. 定数 valuePairs = this.keyValues();
  3. ( i = 0 とします; i < valuePairs.length; i++){
  4. const result = callbackFn(valuePairs[i] .key ,valuePairs[i].value);
  5. 結果が偽の場合
  6. 壊す;
  7. }
  8. }
  9. }

toString メソッドの実装

  1. toString(): 文字列 {
  2. (これが空の場合){
  3. 戻り値``
  4. }
  5.  
  6. 定数 valuePairs = this.keyValues();
  7. objString = `${valuePairs[0].toString()}` とします。
  8. ( i = 1 とします; i < valuePairs.length; i++){
  9. objString = `${objString},${valuePairs[i].toString()}`;
  10. }
  11. objStringを返します
  12. }

完全なコードについては、HashMap.ts をご覧ください。

テストコードを書く

上記のコードが正常に実行されるかどうかをテストしてみましょう。

  1. 定数hashMap = 新しいHashMap();
  2. hashMap.put( "name" , "张三" );
  3. ハッシュマップにidを代入します。
  4. hashMap.put( "クラス" , "製品" );
  5. console.log( "HashMapにクラスが存在するかどうかを判断します" 、 hashMap.hasKey( "class" ));
  6. ハッシュマップを削除します( "id" );
  7. console.log( "HashMap に id が存在するかどうかを確認します" 、 hashMap.hasKey( "id" ))
  8. console.log(hashMap.get( "名前" ));
  9. hashMap.forEach(((キー、値) => {
  10. console.log(キー+ "=" + 値);
  11. }))
  12. console.log( "HashMap 内のデータが空かどうかを判断します" , hashMap.isEmpty());
  13. console.log( "HashMap内のすべてのキーに対応する値を出力します" , hashMap.keyValues());
  14. console.log( "HashMap内のすべてのキー値を取得します" , hashMap.keys());
  15. console.log(" HashMap内のすべてのValueを取得します" , hashMap.values ());
  16. console.log( "HashMapのサイズを取得します" , hashMap.size ());
  17. console.log( "HashMap 内のデータを文字列出力に変換します" , hashMap.toString());
  18. console.log( "HashMap 内のデータをクリアします" );
  19. ハッシュマップをクリアします。
  20. // ハッシュ値の競合問題をテストする
  21. hashMap.put( 'Ygritte' , '[email protected]' );
  22. hashMap.put( 'Jonathan' , '[email protected]' );
  23. hashMap.put( 'Jamie' , '[email protected]' );
  24. hashMap.put( 'Jack' , '[email protected]' );
  25. hashMap.put( 'Jasmine' , '[email protected]' );
  26. hashMap.put( 'Jake' , '[email protected]' );
  27. hashMap.put( 'Nathan' , '[email protected]' );
  28. hashMap.put( 'Athelstan' , '[email protected]' );
  29. hashMap.put( 'スー' , '[email protected]' );
  30. hashMap.put( 'Aethelwulf' , '[email protected]' );
  31. hashMap.put( 'Sargeras' , '[email protected]' );
  32. コンソールにログ出力します。

完全なコードについては、HashMapTest.js を参照してください。

ハッシュ競合問題を解決するためにリンクリストを分離する方法<br /> 上記のテストコードを実行したところ、一部の値が競合して置き換えられ、データが失われていることがわかりました。

リンクリストと組み合わせて競合の問題を解決する方法を見てみましょう。

新しいHashMapSeparateChaining.tsファイルを作成し、HashMapから継承したHashMapSeparateChainingクラスを追加します。

  1. エクスポートデフォルトクラスHashMapSeparateChaining<K,V>はHashMap<K, V>を拡張します{
  2.  
  3. }

プライベート プロパティ tableLink を宣言し、テーブル ストレージの型を定義します。

  1. プライベート tableLink:{ [キー: 数値]: LinkedList<ValuePair<K, V>> };

コンストラクタでtoStrFnメソッドを宣言し、デフォルト値を割り当てて親クラスとテーブルを初期化します。

  1. コンストラクター(保護されたtoStrFn: (キー: K) => 文字列 = defaultToString) {
  2. スーパー(toStrFn);
  3. this.tableLink = {};
  4. }

putメソッドをオーバーライドする

  1. put(キー:K、値:V):ブール値{
  2. if (キー!= null && 値 != null ) {
  3. const 位置 = this.hashCode(キー);
  4. if (this.tableLink[位置] == null ){
  5. // 要素を追加する位置が空の場合は、リンクリストを作成します
  6. this.tableLink[位置] = 新しいLinkedList<ValuePair<K, V>>();
  7. }
  8. // 現在の要素を、その要素が現在追加されているリンクリストに追加します
  9. this.tableLink[位置].push(新しいValuePair(キー、値));
  10. 戻る 真実;
  11. }
  12. 戻る 間違い;
  13. }

getメソッドをオーバーライドする

  1. get(キー:K):V | 未定義{
  2. // パラメータのハッシュ値を取得する
  3. const 位置 = this.hashCode(キー);
  4. // ターゲット要素の位置に格納されているリンクリスト構造の要素を取得します
  5. const linkedList = this.tableLink[位置];
  6. リンクリストがnullの場合、linkedList.isEmpty() は空です。
  7. // リンクリストのヘッダーデータを取得する
  8. 現在のものをlinkedList.getHead()とします。
  9. while (現在値!= null ) {
  10. // リンクリストを走査し、リンクリスト内でターゲットパラメータと同じデータを検索します
  11. if (現在の.element . key === key ) {
  12. // ターゲットキーに対応する値を返す
  13. 戻る 現在の.element.value;
  14. }
  15. current =現在の. next ;
  16. }
  17. }
  18. 未定義を返します
  19. }

削除メソッドをオーバーライドする

  1. 削除(キー:K):ブール値{
  2. const 位置 = this.hashCode(キー);
  3. // ターゲット要素の位置に格納されているリンクリスト構造の要素を取得します
  4. const linkedList = this.tableLink[位置];
  5. リンクリストがnullの場合、linkedList.isEmpty() は空です。
  6. // リンクリストの先頭要素を取得する
  7. 現在のものをlinkedList.getHead()とします。
  8. while (現在値!= null ) {
  9. // リンクリストを走査し、ターゲット要素と同じデータを検索します
  10. if (現在の.element . key === key ) {
  11. // リンクリストから現在リンクリストにある要素を削除します
  12. linkedList.remove(現在の要素);
  13. リンクリストが空の場合(){
  14. // リンクリストが空の場合、ターゲット位置の要素を削除します
  15. this.tableLink[位置]を削除します
  16. }
  17. 戻る 真実;
  18. }
  19. current =現在の. next ;
  20. }
  21. }
  22. 戻る 間違い;
  23. }

clearメソッドをオーバーライドする

  1. クリア() {
  2. this.tableLink = {};
  3. }

keyValuesメソッドをオーバーライドする

  1. キー値(): 値のペア<K, V>[] {
  2. 定数値のペア = [];
  3. // tableLink 内のすべてのキーを取得し、 intに変換します
  4. const keys = Object.keys(this.tableLink).map(item=>parseInt(item));
  5. ( i = 0 とします; i < keys.length; i++){
  6. const linkedList = this.tableLink[keys[i]];
  7. リンクリストがnullの場合、linkedList.isEmpty() は空です。
  8. // リンクリスト内のデータを走査し、リンクリスト内のデータを valuePairs に格納します
  9. 現在のものをlinkedList.getHead()とします。
  10. while (現在値!= null ) {
  11. valuePairs.push(現在の.element);
  12. current =現在の. next ;
  13. }
  14. }
  15. }
  16. valuePairsを返します
  17. }

完全なコードについては、HashMapSeparateChaining.ts をご覧ください。

テストコードを書く
上記の方法が正常に実行できるかテストしてみましょう。

  1. 定数hashMapSC = 新しいHashMapSeparateChaining();
  2. hashMapSC.put( "name" , "张三" );
  3. hashMapSC.put( "id" 、11);
  4. hashMapSC.put( "年齢" ,22);
  5. hashMapSC.put( "電話番号" , "09871588" );
  6. hashMapSC.remove( "id" );
  7. console.log(hashMapSC.get( "名前" ));
  8. console.log( "hashMap 内のデータが空かどうかを判断します" , hashMapSC.isEmpty());
  9. コンソールにログ出力します。
  10. console.log( "forEach を使用して hashMap 内のデータを走査します" );
  11. hashMapSC.forEach((キー,値)=>{
  12. console.log(`${キー} = ${値}`);
  13. })
  14. console.log( "hashMap に格納されているすべてのキーを取得します" , hashMapSC.keys());
  15. console.log(" hashMapに格納されているすべてのを取得します" , hashMapSC.values ());
  16. console.log( "idがhashMap内にあるかどうかを判断します" , hashMapSC.hasKey( "id" ));
  17. console.log( "HashMap 内のデータをクリアします" );
  18. ハッシュマップSC.clear();
  19. console.log( "HashMap 内のデータが空かどうかを判断します" , hashMapSC.isEmpty());
  20. console.log( "競合テスト" )
  21. hashMapSC.put( 'Ygritte' , '[email protected]' );
  22. hashMapSC.put( 'ジョナサン' , '[email protected]' );
  23. hashMapSC.put( 'Jamie' , '[email protected]' );
  24. hashMapSC.put( 'Jack' , '[email protected]' );
  25. hashMapSC.put( 'Jasmine' , '[email protected]' );
  26. hashMapSC.put( 'Jake' , '[email protected]' );
  27. hashMapSC.put( 'Nathan' , '[email protected]' );
  28. hashMapSC.put( 'Athelstan' , '[email protected]' );
  29. hashMapSC.put( 'スー' , '[email protected]' );
  30. hashMapSC.put( 'Aethelwulf' , '[email protected]' );
  31. hashMapSC.put( 'Sargeras' , '[email protected]' );
  32. コンソールにログ出力します。

完全なコードについては、HashmapsParateChainingTest.jsにアクセスしてください

リニアプロービングは、ハッシュ衝突の問題を解決します
新しいHashmaplinearProbing.tsファイルを作成し、Hashmapから継承されたHashmaplinearProbingクラスを追加します

  1. デフォルトクラスhashmaplinearprobing <k、v> extends hashmap <k、v> {
  2.  
  3. }

コンストラクターの親クラスを初期化します

  1. コンストラクタ() {
  2. 素晴らしい();
  3. }

PUTメソッドをオーバーライドします

  1. put( key :k、value:v):boolean {
  2. if( key != null && value!= null ){
  3. const position = this.hashcode( key );
  4. //挿入される現在の位置がテーブルに占有されているかどうかを判断します
  5. if( this.table [position] == null ){
  6. //現在の位置が占有されていません。キーと値をValuePairに入れて、現在のテーブルに挿入する要素に割り当てます
  7. this.table [position] = new ValuePair( key 、value);
  8. }それ以外{
  9. //位置が占有されており、空いている位置が見つかるまでインデックスを増やします
  10. index = position + 1とします。
  11. while( this.table [ index ]!= null ){
  12. インデックス++;
  13. }
  14. //空いている位置を見つけ、キーと値をValuePairに入れて、挿入する位置の現在のテーブルの要素に割り当てます
  15. this.table [ index ] = new ValuePair( key 、value);
  16. }
  17. 戻る 真実;
  18. }
  19. 戻る 間違い;
  20. }

GETメソッドをオーバーライドします

  1. get( key :k):v |。
  2. const position = this.hashcode( key );
  3. if( this.table [position]!= null ){
  4. //現在の位置の要素のキーがターゲット要素のキーに等しい場合、現在の位置の要素の値を直接返します
  5. if( this.table [position] .key === key {
  6. this.table [ position ] .valueを返します
  7. }
  8. //探している要素を見つけるか、空の位置を見つけるまで位置が増加します
  9. index = position + 1とします。
  10. while( this.table [ index ]!= null && this.table [ index ] .key !== key ){
  11. インデックス++;
  12. }
  13. //増分が完了したら、現在のテーブルのインデックスキーがターゲットキーに等しいかどうかを判断します 
  14. if( this.table [ index ]!= null && this.table [ index ] .key == key {
  15. this.table [ index ] .valueを返します
  16. }
  17. }
  18. 未定義を返す;
  19. }

削除メソッドをオーバーライドします

  1. 削除( key :k):boolean {
  2. const position = this.hashcode( key );
  3. if( this.table [position]!= null ){
  4. if( this.table [position] .key === key {
  5. this.table [position]を削除します
  6. //削除した後、この削除に副作用があるかどうかを確認し、要素の位置を調整します
  7. this.verifyRemovesideEffect( key 、position);
  8. 戻る 真実;
  9. }
  10. index = position + 1とします。
  11. while( this.table [ index ]!= null && this.table [ index ] .key !== key ){
  12. インデックス++;
  13. }
  14. if( this.table [ index ]!= null && this.table [ index ] .key == key {
  15. this.table [ index ] ;
  16. this.verifyRemovesIdeEffect( key index );
  17. 戻る 真実;
  18. }
  19. }
  20. 戻る 間違い;
  21. }

verifyRemovesideEffectメソッドを実装します

  1. //削除操作に副作用がないことを確認します
  2. private verifyRemovesideEffect( key :k、removedposition:number){
  3. //削除されたキーのハッシュ値を計算します
  4. const hash = this.hashcode( key );
  5. //削除された要素の次の位置から開始し、空の位置が見つかるまでテーブルを横断します
  6. //空の位置が見つかった場合、それは要素が適切な位置にあり、移動する必要がないことを意味します
  7. index = removedposition + 1とします。
  8. while( this.table [ index ]!= null ){
  9. //現在トラバースされている要素キーのハッシュ値を計算します
  10. const poshash = this.hashcode( this.table [ index ] .key );
  11. console.log( `現在トラバースエレメントのハッシュ= $ {poshash}、最後に削除されたkey = $ {removedposition}`)のハッシュ
  12. if(poshash <= hash || poshash <= removedposition){
  13. //現在トラバースされている要素のハッシュ値が、削除された要素のハッシュ値以下、または最後に削除されたキーのハッシュ値以下の場合(削除ポジション)
  14. //現在の要素を削除ポジション位置に移動する必要があります
  15. this.table [removedposition] = this.table [ index ] ;
  16. //移動が完了したら、現在のインデックス位置で要素を削除します
  17. this.table [ index ] ;
  18. //削除ポジションの値をインデックスに更新します 
  19. removedposition = index ;
  20. }
  21. インデックス++;
  22. }
  23. }

完全なコードについては、hashmaplinearprobing.tsにアクセスしてください

テストコードの作成
上記の方法が正常に実行されるかどうかをテストします

  1. const hashmaplp = new hashmaplinearprobing();
  2. console.log( "競合する要素削除テスト" );
  3. hashmaplp.put( 'ygritte' '[email protected]' );
  4. Hashmaplp.put( 'Jonathan' '[email protected]' );
  5. hashmaplp.put( 'jamie' '[email protected]' );
  6. hashmaplp.put( 'jack' '[email protected]' );
  7. hashmaplp.put( 'jasmine' '[email protected]' );
  8. hashmaplp.put( 'jake' '[email protected]' );
  9. hashmaplp.put( 'nathan' '[email protected]' );
  10. hashmaplp.put( 'athelstan' '[email protected]' );
  11. hashmaplp.put( 'sue' '[email protected]' );
  12. hashmaplp.put( 'aethelwulf' '[email protected]' );
  13. hashmaplp.put( 'sargeras' '[email protected]' );
  14. // hashmaplp.remove( "ygritte" );
  15. Hashmaplp.Remove( "Jonathan" );
  16. console.log(hashmaplp.toString());

完全なコードについては、hashmaplinearprobing.tsにアクセスしてください

より良いハッシュ関数
コードでは、Hash値を生成するためにLoseloseHashCodeを使用します。そのため、競合が多くのパフォーマンスを消費するため、推奨されません。

上記のコードにDJB2HashCodeメソッドを実装しました。この方法は非常に小さく、次にHashCodeで使用されるメソッドを使用してHasHMAPの実行結果をテストする必要があります。

  1. HashCode( key :k):number {
  2. this.djb2hashcode key );
  3. }

その結果、予想されたように、重複したハッシュ値を生成せず、すべての要素が保存されました。

<<:  TikTok本社は米国に残り、ByteDanceが管理権とコアアルゴリズムを保持する

>>:  人工知能は医療現場の診断や治療の決定に役立つ

ブログ    
ブログ    
ブログ    

推薦する

人工知能は242万件の医療記録の分析を支援した

人工知能は242万件の医療記録の分析を支援した1月26日、iFLYTEKは最前線の防疫・管理を支援す...

AIがFBIに加わったとき、KGBはそれを専門家と呼んだ

「市の東にある家で爆弾が爆発しようとしています!」 「爆弾はネズミ捕り、ACデルコ社の単三電池、亜鉛...

食品サービス機器業界の主な動向

[[442813]]画像ソース: https://pixabay.com/images/id-673...

...

自律走行レースのためのマルチモーダルセンサーフュージョンとターゲット追跡

この記事は、Heart of Autonomous Driving の公開アカウントから許可を得て転...

...

スタンフォード大学の研究:スマートフォンの録画で人が酔っているかどうかを98%の精度で識別できる

11月9日、スタンフォード大学の最近の研究で、スマートフォンは音声パターンから人が酔っているかどうか...

2020年にAIアルゴリズム市場は普及するでしょうか?

2019年も残り1か月余りとなり、各種年間総括も迫ってまいりました。今年の AI の発展を振り返る...

LianjiaのFeng Yang氏:不動産業界でデータと機械学習が輝く

[51CTO.comより引用] 2017年12月1日~2日、51CTO主催のWOTDグローバルソフト...

AIの奇妙な使い方:マクドナルドはゴミ箱の監視にAIを活用

この記事は公開アカウント「Reading Core Technique」(ID: AI_Discov...

ビッグデータと人工知能の関係、総合的な分析

ビッグデータはクラウドコンピューティングを採用PaaS レイヤーの複雑な汎用アプリケーションは、ビッ...

...

...

Pythonは画像内のすべての顔を認識し、それを表示する機能を実装しています

Python3 を使用して、写真内のすべての顔を認識して表示します。コードは次のとおりです。 # -...