JavaScriptの一般的なアルゴリズムとデータ構造
連想配列
JavaScript objects are an example of an associative array. Unlike regular arrays, associative arrays do not have indexes, but rather keys (usually strings). Otherwise, there is almost no difference – the keys are unique and each corresponds to a certain value. Associative arrays are also called dictionaries or maps (from the English map). They allow you to conveniently represent complex data of various types (for example, user information) and are very popular in JavaScript programming.
効率的には連想配列は他のデータ構造より優れている.たとえば、単純な配列の真ん中に新しい要素を追加するには、再インデックスを付けなければなりません(最初の部分ではこの話をしました).この演算の複雑さはo(n)である.連想配列では、単に値が関連付けられている新しいキーを追加します.
ハッシュテーブル
しかし、連想配列は独自の弱さを持っています.通常のインデックス配列とは異なり、コンピュータのメモリに格納できません.連想配列を格納するために、特別な構造が使われます--ハッシュテーブル(ハッシュマップ).
連想配列はシンタックスシュガー、ハッシュテーブルへのより便利なアドオンです.
ハッシュテーブル操作の概略図
ハッシュ
連想配列のキーを通常のインデックスのインデックスにするには、2つの操作を実行する必要があります.
ハッシュの計算
ハッシュ関数は入力データを受け取り、それを固定長のハッシュ文字列または数値に変換します.おそらく、いくつかのハッシュアルゴリズムについて聞いたことがあります.キーは、ハッシュ関数が扱うことができるどんなデータ型でも表すことができます.
gitにおけるコミットのハッシュIDの例.変更を保存するとき、それらはハッシュされます、そして、あなたは
0481e0692e2501192d67d7da506c6e70ba41e913
のような何かを得ます.これはあなたの変更のために計算されたハッシュです.ハッシュ関数の実装は非常に異なることができます.たとえば、最も単純なID関数を使用することができます.この関数は、入力パラメータを受け取り、そのまま変更します.
const hash = key => key;
キーが文字列の場合、すべての文字のコードの合計を計算できます.const hash = string => {
let result = 0;
for (let i = 0; i < string.length; i++) {
result += string.charCodeAt(i);
}
return result;
};
例えば、キーのハッシュ値は417で、キーの年齢のハッシュ値は301です.これらのすべては、ハッシュ関数の非常に良い例ではありません、彼らは通常実生活でより複雑です、しかし、我々が一般原則を理解することは重要です.ハッシュテーブルがどのようなデータを扱うのかを知っていれば、一般的な場合よりも特定のハッシュ関数を選択できます.
重要:同じ入力値の場合、ハッシュ関数は常に同じ結果を返します.
インデックスへのキャスティング
通常、結果の配列のサイズはすぐに決定されるので、インデックスは指定した範囲内になければなりません.ハッシュは通常インデックスより大きいので、さらに変換する必要があります.
インデックスを計算するには、配列のサイズでハッシュを分割する剰余を使用できます.
const index = Math.abs(hash) % 5;
配列が長いほど、メモリにかかるスペースが増えることを覚えておくことが重要です.ハッシュ関数を使用し、連想配列を通常の配列に変換します.
// associative array
const user = {
name: 'John',
age: 23
};
// default array, length = 5
[
undefined,
['age', 23],
['name', 'John'],
undefined,
undefined
]
キー名はインデックス2に対応し、キーの年齢はインデックス1に対応する.結果の配列だけでなく、元のキーに値を格納します.これがなぜ必要なのか、すぐにわかる.
今、キー名を持つ配列要素を取得したい場合は、このキーをハッシュして、連想配列の要素がどのインデックスにあるかを確認する必要があります.
衝突
あなたは既にそのような変換の弱点を見ますか?
The key in an associative array can be absolutely any string of any length – the number of options is infinite. And the number of indexes in the array is limited. In other words, there are not enough indexes for all keys, and for some input data, the hash function will return the same result. This is called a collision.
衝突を解決する2つの一般的な方法があります.
オープンアドレッシング
ハッシュ関数を連想配列(Key 1)のキーの一部として渡し、このキーに対応する正規配列の2つのインデックスから受け取ります.
[ undefined, undefined, [key1, value1], undefined, undefined, undefined, undefined ]
それから、我々はもう一つのキー- Key 2を通過します、そして、再び、我々は2を得ます-衝突がありました.私たちは同じインデックスの下で新しいデータを書くことができません.これを線形プロービングと呼ぶ.2 - 3以降の次のインデックスは無料です.[ undefined, undefined, [key1, value1], [key2, value2], undefined, undefined, undefined ]
第3のキーKit 3のために、ハッシュ関数はインデックス3を返します、しかし、それはすでにキーKey 2によって占領されます.[ undefined, undefined, [key1, value1], [key2, value2], [key3,value3], undefined, undefined ]
レコードは明確ですが、キーのようなハッシュテーブル、例えばKit 3で希望のキーを見つけることができますか?同様に、まずハッシュ関数を実行し、3を取得します.このインデックスで配列要素をチェックし、これが我々が探しているキーではないことを確認します.ですから、ソースキーをハッシュテーブルに格納しているので、見つかった要素が正確に必要なものであることを確認できます.我々は、配列を通してさらに動き始めます.そして、各々の要素を繰り返して、我々が探しているキーと比較します.ハッシュテーブルがより密に設定されている場合、より多くの反復を行う必要があります.
チェーン法
このアプローチでは、単一のインデックスに対応する値がリンクリストとして格納されます.配列の各インデックスは1つの要素ではなく、ハッシュ関数が1つのインデックスを計算した要素のリストに対応します.衝突が起こるならば、新しい要素は単にリストの終わりに加えられます.
そのようなハッシュテーブルに特定のキーを持つ要素を検索するとき、まず、ハッシュを計算し、目的の配列インデックスを決定し、希望するキーを見つけるまでリスト全体を調べます.
この実装では、テーブルから項目を削除することができます.リンクリストでは、削除操作は一定時間かかります.
JavaScriptのハッシュテーブルの実装
ハッシュテーブルは連想配列インターフェースを実装しなければなりません.
新しいキー値組を加えることキーによる値のための
衝突を解決するために、チェーンメソッドを使用します.これを行うには、リンクリストクラスLinkedListが必要です.
const hashTableSize = 32;
class HashTable {
constructor() {
this.buckets = Array(hashTableSize).fill(null);
}
hash(key) {
let hash = Array.from(key).reduce((sum, key) => {
return sum + key.charCodeAt(0);
}, 0);
return hash % hashTableSize;
}
set(key, value) {
// calculating the hash for the key
let index = this.hash(key);
// create if there is no list for this hash yet
if (!this.buckets[index]) {
this.buckets[index] = new LinkedList();
}
let list = this.buckets[index];
// check if the key was added earlier
let node = list.find((nodeValue) => {
nodeValue.key === key;
});
if (node) {
node.value.value = value; // updating the value for the key
} else {
list.append({ key, value }); // adding a new item to the end of the list
}
}
get(key) {
// calculating the hash for the key
let index = this.hash(key);
// we find the corresponding list in the array
let list = this.buckets[index];
if (!list) return undefined;
// we are looking for an item with the desired key in the list
let node = list.find((nodeValue) => {
return nodeValue.key === key;
});
if (node) return node.value.value;
return undefined;
}
delete(key) {
let index = this.hash(key);
let list = this.buckets[index];
if (!list) return;
let node = list.find((nodeValue) => nodeValue.key === key);
if (!node) return;
list.delete(node.value);
}
}
ハッシュ表における基本演算の効率
ハッシュテーブルの主な操作は次の2つの段階からなります.
ハッシュテーブルの有効性は、次の3つの主要な要因に依存します.
ハッシュテーブルの使用
Hash tables are widely used in programming, for example, for authorization mechanisms, indexing large amounts of information (databases), caching, or searching. Another common case is the implementation of unordered sets, which we will discuss in the next part of the cycle.
JavaScriptでは、純粋な形式のハッシュテーブルはほとんど使用されません.通常、すべての作業は正常なオブジェクト(連想配列)またはより複雑なマップによって正常に実行されます.同時に、より低いレベル(プログラム解釈)で、ハッシュテーブルはオブジェクトを表すのに用いられます.
様々な動作を最適化するとき、オブジェクトとハッシュテーブルはしばしば補助構造として使われます.たとえば、文字列内のさまざまな文字の出現回数をカウントします.
function countSymbols(string) {
const hash = {};
[...string].forEach(s => {
let symbol = s.toLowerCase();
if (!(symbol in hash)) hash[symbol] = 0;
hash[symbol]++;
});
return hash;
}
countSymbols('Hello, world!');
/*
{ " ": 1, "!": 1, ",": 1, d: 1, e: 1, h: 1, l: 3, o: 2, r: 1, w: 1 }
*/
ハッシュ、符号化、暗号化
Hashing is an algorithm that works only in one direction. It is impossible to get the original value from the hash, and there is no practical need for this, because the main task of hashing is to distinguish input data, not to save it.
場合によっては、双方向の変換が必要です.たとえば、他の誰も読むことができない友人に秘密のメッセージを残したい.これは、暗号化アルゴリズムが救助に来るところです.
You convert the source text to some other sequence of characters using a cipher. Such a sequence is either completely unreadable (just a set of letters), or it has a completely different meaning. If someone intercepts this email, they simply won't understand what you were trying to say. Your friend knows that the message is encrypted and knows how to decrypt it. Thus, the main purpose of encryption is to hide information from unauthorized persons. To do this, use a secret key or even two keys – one for encryption, the second for decryption.
暗号化に加えて、エンコーディングもあります.それは本質的に暗号化に近いですが、目的の異なる.符号化は、情報の伝送を簡素化するために使用され、例えば、電気通信回線を超えている.あなたのメッセージは、一連のビットに変換されて、ワイヤーの上に受取人に届けられて、もう一方の端で再び回復しました.この場合、キーは使用されません.このようなコードは、通信の問題を解決するだけでなく、送信中に可能な干渉に対処しようとする場合もある.最も有名なコードの一つは、モールス符号です.
結論
ハッシュテーブルを扱っている間、私たちはもう一度プログラミングのほとんどすべてが行われることを確認しました.配列.したがって、フードの下の連想オブジェクトはまた、ハッシュ関数を使用して各キーのインデックスを計算し、それらを使用します.
Reference
この問題について(JavaScriptの一般的なアルゴリズムとデータ構造), 我々は、より多くの情報をここで見つけました https://dev.to/ra1nbow1/common-algorithms-and-data-structures-in-javascript-objects-and-hashing-1kdjテキストは自由に共有またはコピーできます。ただし、このドキュメントのURLは参考URLとして残しておいてください。
Collection and Share based on the CC Protocol