LoginSignup
18
7

More than 1 year has passed since last update.

【JavaScript】連想配列、Map、配列のキー検索速度を比較する

Last updated at Posted at 2022-05-15

はじめに

JavaScript の 連想配列(Object)、Map オブジェクト、配列(Array)において、キーで検索して値を取得する場合の速度を比較してみました(Map オブジェクトについては、@juner さんからのご指摘で追加しました)。
併せて、値で検索する場合についても比較しておきました。

結論(キーで検索した場合の速度比較)

条件によりますが、結論から言うと、キーで検索して値を取得する場合は、Map オブジェクト連想配列(Object)が圧倒的に高速です。
配列(Array)は、ループ処理が必要なためパフォーマンスは良くありません。

以下は、条件として要素数 5,000,000 個、キーはランダムな文字列、値は 0 〜 4,999,999 まで順に格納した場合で検索を実行した結果です。
検索対象となる要素は、配列のちょうど中央としています。

配列形式 検索方式 処理時間(8byteキー) 処理時間(20byteキー)
配列(Array) find() メソッド 35.6ms 216.6ms
配列(Array) for...of 文 25.3ms 385.2ms
配列(Array) for 文 19.0ms 195.7ms
Map オブジェクト get() メソッド 0.000011ms 0.000012ms
連想配列(Object) プロパティ値取得 0.000013ms 0.000013ms

キー検索の速度比較詳細はこちら

なお、逆に、値からキーを検索する場合は、配列(Array)の方が早いです。
Map オブジェクトもある程度早く、連想配列(Object) は極端にスピードが落ちてしまいます。
そのため、どの方法で実装するかはケースバイケースとなると思います(値検索による処理速度比較はこちら)。

以下、詳細を残しておきます。
コードは TypeScript で記述していますが、JavaScript でも結果は変わりません。

1. 配列と連想配列の基本構文

備忘的に、配列(Array)、Map オブジェクト、連想配列(Object)の基本構文を書いておきます。
以下、都道府県名と人口をキーと値に設定する場合のサンプルコードです。

1-1. 配列(Array)の基本的なサンプル

配列(Array)の場合、Key に対応する値を取得するには、find() メソッド、for...of 文、for 文などを使用します。

sample.ts
// 配列(Array)のサンプル
const sampleArray = [
  { key: 'ibaraki', value: 2840403 },
  { key: 'kanagawa', value: 9221129 },
  { key: 'gunma', value: 1915035 },
  { key: 'saitama', value: 7331256 },
  { key: 'chiba', value: 6267579 },
  { key: 'tokyo', value: 13995469 },
  { key: 'tochigi', value: 1910502 }
];

// tokyo に対応する値を取得する(find() メソッドを使用)
const arrayResult1 = sampleArray.find(e => e.key === 'tokyo')?.value;
console.log(arrayResult1); // 出力値: 13995469

// tokyo に対応する値を取得する(for...of を使用)
let arrayResult2;
for (const e of sampleArray) {
  if (e.key === 'tokyo') {
    arrayResult2 = e.value;
    break;
  }
}
console.log(arrayResult2); // 出力値: 13995469

// tokyo に対応する値を取得する(for文 を使用)
let arrayResult3;
for (let i = 0; i < sampleArray.length; i++) {
  if (sampleArray[i].key === 'tokyo') {
    arrayResult3 = sampleArray[i].value;
    break;
  }
}
console.log(arrayResult3); // 出力値: 13995469

1-2. Map オブジェクトの基本的なサンプル

Map オブジェクトは、get() メソッドを使用することで、インデックスから値を取得することができます。

sample.ts
// Map オブジェクトのサンプル
const sampleMap = new Map<string, number>();
sampleMap.set('ibaraki', 2840403);
sampleMap.set('kanagawa', 9221129);
sampleMap.set('gunma', 1915035);
sampleMap.set('saitama', 7331256);
sampleMap.set('chiba', 6267579);
sampleMap.set('tokyo', 13995469);
sampleMap.set('tochigi', 1910502);

// tokyo に対応する値を取得する(get() メソッドを使用)
const mapResult = sampleMap.get('tokyo');
console.log(mapResult); // 出力値: 13995469

1-3. 連想配列(Object)の基本的なサンプル

連想配列(Object)の場合は、次のように、検索するキーを指定するだけで目的の値が取得できます。

sample.ts
// 連想配列(Object)のサンプル
const sampleObject = {
  ibaraki: 2840403,
  kanagawa: 9221129,
  gunma: 1915035,
  saitama: 7331256,
  chiba: 6267579,
  tokyo: 13995469,
  tochigi: 1910502
};

// tokyo に対応する値を取得する
const objectResult = sampleObject.tokyo;
console.log(objectResult); // 出力値: 13995469

上記の例だと、書き方が簡素なため、表見上は処理速度において有利に見えてしまいます。
そのため、ここでの検証では、あえて以下の形式を使用することにします。

sample.ts
// 連想配列(Object)のサンプル
const sampleObject = {
  ibaraki: { key: 'ibaraki', value: 2840403 },
  kanagawa: { key: 'kanagawa', value: 9221129 },
  gunma: { key: 'gunma', value: 1915035 },
  saitama: { key: 'saitama', value: 7331256 },
  chiba: { key: 'chiba', value: 6267579 },
  tokyo: { key: 'tokyo', value: 13995469 },
  tochigi: { key: 'tochigi', value: 1910502 }
};

// tokyo に対応する値を取得する
const objectResult = sampleObject.tokyo.value;
console.log(objectResult); // 出力値: 13995469

この形式にしたとしても、実際、処理速度は変わりません。
データ構造上、{ key: 'ibaraki', value: 2840403 } の部分には、値ではなくて参照が格納されているためです。

2. キーから検索する処理速度の比較

処理速度の差がわかるように、PC のメモリで実行できる範囲で、なるべく要素数を多くして計測を行います。
JavaScript は、node.js で実行しています。

2-1. 比較を行う配列の内容

以下の内容で処理速度の計測を行います。
使用する PC は Mac Book Proです(3年くらい前に購入したマシン)。

要素数は、5,000,000 個。
キーは、ランダムな文字列。8文字(8byte)と 20文字(20byte)でそれぞれ実行
は、0 〜 4,999,999 まで順に格納(値は何でも良い)。

なお、検索対象となる要素は、配列のちょうど中央にあるものとします(つまり、2,500,000 番目の要素)。
検索対象が、配列の最初の方にある場合と、最後の方にある場合では、当然ながら処理時間は異なってきます。

2-2. キーから値取得の速度比較

先に結果を書いておきます(実行コードは後述)。
今回のように、500万個の要素があると、結果に如実な差が生じてきます。

配列形式 検索方式 処理時間(8byteキー) 処理時間(20byteキー)
配列(Array) find() メソッド 35.6ms 216.6ms
配列(Array) for...of 文 25.3ms 385.2ms
配列(Array) for 文 19.0ms 195.7ms
Map オブジェクト get() メソッド 0.000011ms 0.000012ms
連想配列(Object) プロパティ値取得 0.000013ms 0.000013ms

連想配列(Object)Map オブジェクトが圧倒的に処理が早いことが分かります(10万倍から100万倍ほどのスピード差)。
これは、キーに紐づけられたインデックスから値を取得しているためです。
そのため、キーの文字数(byte 数)に関係なく処理速度は一定です。
なお、両者の差は誤差程度で、何度か実行するどちらが早いということもありませんでした。

配列(Array)はループ処理をする必要があるため、どうしても処理時間が掛かってしまいます。
また、キー照合のために単純に文字列の比較を行うため、キーのバイト数によっても処理速度に差が生じています。

なお、配列(Array)でも、キーから値を取得するのではなくて、インデックス(配列番号)から値を取得するならば、高速に値を取得することができます(実測で 0.000004ms)。

2-3. 実行コード

実際に計測を行ったコードを貼っておきます。

2-3-1. 配列(Array)の実行速度計測

2-3-1-1. find() メソッドで値を取得

sample.ts
// 使用する変数・定数
const char = '0123456789abcdefghijklmnopqrstuvwxyz'; // キー作成用文字列
interface TypeArray {key: string; value: number; }
const array: TypeArray[] = []; // 連想配列用の空オブジェクト
const loop = 5000000; // ループ回数
const halfLoop = loop / 2 | 0; // ループ回数の中間点(ここで検索対象となるキーと値を取得)
const keys = new Set<string>(); // 一意のキーかを確認するためのSetオブジェクト
let targetKey: string = ''; // 検索対象のキーを格納

for (let i = 0; i < loop; i++) {
  // 20文字の一意のキーを生成(0〜35までのランダムな整数値から1文字ずつ取得)
  let key = '';
  while (true) {
    for (let j = 0; j < 20; j++) key = key + char[(Math.random() * 36) | 0];
    if (!keys.has(key)) {
      keys.add(key);
      break; // 文字列が重複しなければループ終了
    }
  }
  // 配列にキーと値のセットを追加(iを値として格納)
  array.push({ key: key, value: i });
  // 検索対象とするキーと値を取得(ループの中間点)
  if (i === halfLoop) targetKey = key;
}
console.log(`検索キー: ${targetKey}`); // 出力値: 検索キー: f1ixelezynspkzxfl4x1

// 検索処理実行
let startTime = new Date().valueOf();
let result;
for (let i = 0; i < 10; i++) {
  // 検索キーから[キーと値のセット]を取得(find()を使用, 10回実行)
  result = array.find(e => e.key === targetKey);
}
console.log(result); // 出力値: { key: 'f1ixelezynspkzxfl4x1', value: 2500000 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 216.6ms/回

2-3-1-2. for...of 文で値を取得

sample.ts
// 配列作成部分は共通のため省略

// 検索処理実行
let startTime = new Date().valueOf();
let result;
for (let i = 0; i < 10; i++) {
  // 検索キーから[キーと値のセット]を取得(for...ofを使用, 10回実行)
  for (let e of array) {
    if (e.key === targetKey) {
      result = e;
      break;
    }
  }
}
console.log(result); // 出力値: { key: 'fuc5bpgnvq6fbtmci7hz', value: 2500000 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 385.2ms/回

2-3-1-3. for 文で値を取得

sample.ts
// 配列作成部分は共通のため省略

// 検索処理実行
let startTime = new Date().valueOf();
let result;
for (let i = 0; i < 10; i++) {
  // 検索キーから[キーと値のセット]を取得(for文を使用, 10回実行)
  for (let j = 0; j < array.length; j++) {
    if (array[j].key === targetKey) {
      result = array[j];
      break;
    }
  }
}
console.log(result); // 出力値: { key: 'secl8tphikv4mr7e6sbl', value: 2500000 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 195.7ms/回

2-3-2. Map オブジェクトの実行速度計測

sample.ts
// 使用する変数・定数
const char = '0123456789abcdefghijklmnopqrstuvwxyz'; // キー作成用文字列
const mapArray = new Map<string, { key: string; value: number; }>();
const loop = 5000000; // ループ回数
const halfLoop = loop / 2 | 0; // ループ回数の中間点(ここで検索対象となるキーと値を取得)
let targetKey = ''; // 検索対象のキーを格納

// Mapオブジェクトを生成
let startTime = new Date().valueOf(); // 計測開始時間
for (let i = 0; i < loop; i++) {
  // 20文字の一意のキーを生成(0〜35までのランダムな整数値から1文字ずつ取得)
  let key = '';
  while (true) {
    for (let j = 0; j < 20; j++) key = key + char[(Math.random() * 36) | 0];
    if (!mapArray.has(key)) break; // 文字列が重複しなければループ終了
  }
  // Mapオブジェクトにキーと値のセットを追加(iを値として格納)
  mapArray.set(key, { key: key, value: i });
  // 検索対象とするキーと値を取得(ループの中間点)
  if (i === halfLoop)  targetKey = key;
}
console.log(`検索キー: ${targetKey}`); // 出力値: 検索キー: r6078ezpgkxkutnl8d87
console.log(`配列作成終了: ${new Date().valueOf() - startTime}`); // 出力値: 配列作成終了: 12261

// 検索処理実行
startTime = new Date().valueOf(); // 検索開始時間
let result;
for (let i = 0; i < 1000000; i++) {
  // 検索キーから[キーと値のセット]を取得(1000000回実行)
  result = mapArray.get(targetKey);
}

console.log(result); // 出力値: { key: 'r6078ezpgkxkutnl8d87', value: 2500000 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 1000000}ms/回`);
// 出力値: 検索速度: 0.000012ms/回

2-3-3. 連想配列(Object)の実行速度計測

sample.ts
// 配列作成部分は共通のため省略

// 使用する変数・定数
const char = '0123456789abcdefghijklmnopqrstuvwxyz'; // キー作成用文字列
const objectArray: { [key: string]: { key: string; value: number; }; } = {}; // 連想配列用のオブジェクト
const loop = 5000000; // ループ回数
const halfLoop = loop / 2 | 0; // ループ回数の中間点(ここで検索対象となるキーと値を取得)
let targetKey = ''; // 検索対象のキーを格納

// 連想配列を生成
for (let i = 0; i < loop; i++) {
  // 20文字の一意のキーを生成(0〜35までのランダムな整数値から1文字ずつ取得)
  let key = '';
  while (true) {
    for (let j = 0; j < 20; j++) key = key + char[(Math.random() * 36) | 0];
    if (!(key in objectArray)) break; // 文字列が重複しなければループ終了
  }
  // 連想配列にキーと値のセットを追加(iを値として格納)
  objectArray[key] = { key: key, value: i};
  // 検索対象とするキーと値を取得(ループの中間点)
  if (i === halfLoop)  targetKey = key;
}
console.log(`検索キー: ${targetKey}`); // 出力値: 検索キー: nx018cv7see645fy18vs

// 検索処理実行
let startTime = new Date().valueOf(); // 計測開始時間
let result;
for (let i = 0; i < 1000000; i++) {
  // 検索キーから[キーと値のセット]を取得(1000000回実行)
  result = objectArray[targetKey];
}
console.log(result); // 出力値: { key: 'nx018cv7see645fy18vs', value: 2500000 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 1000000}ms/回`);
// 出力値: 検索速度: 0.000013ms/回

3. 値から検索する処理速度の比較

値からキーを取得する場合は、処理速度が変わってきます。
連想配列(Object)、Map オブジェクトでもインデックス使用ができないため、ループ処理をする必要が生じます。

3-1. 比較を行う配列の内容

以下の内容で処理速度の計測を行います。
キー検索の場合と異なるのは、値をランダムな数値に変更している点です。

要素数は、5,000,000 個。
キーは、ランダムな文字列。8文字(8byte)と 20文字(20byte)でそれぞれ実行
は、ランダムな数値(0 〜 49,999,999 の範囲)。

ここでも、検索対象となる要素は、配列のちょうど中央にあるものとします(つまり、2,500,000 番目の要素)。

3-2. 値からキー取得の速度比較

先に結果を書いておきます(実行コードは後述)。

3-2-1. 基本的な方法で比較

配列形式 検索方式 処理時間
配列(Array) find() メソッド 27.9ms
配列(Array) for...of 文 15.1ms
配列(Array) for 文 9.9ms
Map オブジェクト forEach 文 55.8ms
Map オブジェクト for...of 文 41.3ms
連想配列(Object) for...in 文 2042.4ms

値からキーを取得する場合は、配列(Array)の方が圧倒的に早いです。
処理内容は、キーから値を取得する場合と全く同じです。
キーから値を取得するよりも速度が早くなるのは、値が一致しているかの照合をするコストが少ないためと思われます(最大 8 桁の数値の比較のため)。

Map オブジェクトも、ある程度の速度は確保されます。
連想配列(Object) だと致命的に処理時間がかかる(後述)ので、選択肢としては、Map オブジェクトが一番無難なように思います。

連想配列(Object)で for...in 文使用の場合はかなりの時間要します。
オブジェクトのまま各要素をループで参照するには、for...in 文くらいしかなさそうですが、大量のデータを扱う場合には難がありそうです。

なお、Object.keys() メソッドや Object.entries() メソッドを使用して、連想配列(Object)を配列(Array)に変換して検索する方法もあります(以下のとおり)。

3-2-2. 連想配列(Object)を配列化して検索する場合の速度

連想配列(Object)は、Object.keys() メソッドや Object.entries() メソッドを使用することで配列(Array)に変換することができます。
これを使用して検索を行う場合も多いと思います。

しかし、要素数が膨大にある場合、配列変換をするだけで結構な処理時間を要してしまいます。
今回の条件での処理速度は、以下のとおりでした。

配列変換方式 検索方式 配列変換時間 検索処理速度 合計速度
Object.keys() find() メソッド 1925ms 333.1ms 2258ms
Object.keys() for...of 文 1843ms 190.7ms 2034ms
Object.entries() find() メソッド 12491ms 41.7ms 12533ms
Object.entries() for...of 文 12347ms 49.6ms 12397ms
Object.entries() for 文 12188ms 29.6ms 12218ms

要素数が少なければ、配列への変換時間は極小になるので、この方法で十分だと思いますが、要素数が膨大な場合はパフォーマンスが低下してしまします。
キー以外の値で配列を検索することが必要な場合は、最初から配列(Array)を選択した方が無難かもしれません(ケースによる)。

3-3. 実行コード

値からキーを取得する場合も実行コードを貼っておきます。

3-3-1. 配列(Array)の実行速度計測

3-3-1-1. find() メソッドで値からキーを取得

sample.ts
// 使用する変数・定数
const char = '0123456789abcdefghijklmnopqrstuvwxyz'; // キー作成用文字列
interface TypeArray { key: string; value: number; }
const array: TypeArray[] = []; // 連想配列用の空オブジェクト
const loop = 5000000; // ループ回数
const halfLoop = loop / 2 | 0; // ループ回数の中間点(ここで検索対象となるキーと値を取得)
const keys = new Set<string>(); // 一意のキーかを確認するためのSetオブジェクト
const values = new Set<number>(); // 一意の値かを確認するためのSetオブジェクト
let targetKey: string = ''; // 検索対象のキーを格納
let targetValue = 0; // 検索対象の値を格納

// 連想配列を生成
let startTime = new Date().valueOf();
for (let i = 0; i < loop; i++) {
  // 20文字の一意のキーを生成(0〜35までのランダムな整数値から1文字ずつ取得)
  let key = '';
  while (true) {
    for (let j = 0; j < 20; j++) key = key + char[(Math.random() * 36) | 0];
    if (!keys.has(key)) {
      keys.add(key);
      break; // 文字列が重複しなければループ終了
    }
  }
  // 格納する一意の値を生成
  let value = (Math.random() * loop * 10) | 0;
  while (values.has(value)) value++; // 値が重複した場合は1を加算
  values.add(value);
  // 配列にキーと値のセットを追加
  array.push({ key: key, value: value });
  // 検索対象とするキーと値を取得(ループの中間点)
  if (i === (halfLoop)) {
    targetKey = key;
    targetValue = value;
  };
}
console.log(`検索キー: ${targetKey}, 検索値: ${targetValue}`); // 出力値: 検索キー: 52iuvej8gnp870x47ft3, 検索値: 30326905
console.log(`配列作成終了: ${new Date().valueOf() - startTime}`); // 出力値: 配列作成終了: 16192

// 検索処理実行
startTime = new Date().valueOf();
let result;
for (let i = 0; i < 10; i++) {
  // 検索値から[キーと値のセット]を取得(find()を使用, 10回実行)
  result = array.find(e => e.value === targetValue);
}

console.log(result); // 出力値: { key: '52iuvej8gnp870x47ft3', value: 30326905 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 27.9ms/回

3-3-1-2. for...of 文で値からキーを取得

sample.ts
// 配列作成部分は共通のため省略

// 検索処理実行
startTime = new Date().valueOf();
let result;
for (let i = 0; i < 10; i++) {
  // 検索値から[キーと値のセット]を取得(for...ofを使用, 10回実行)
  for (let e of array) {
    if (e.value === targetValue) {
      result = e;
      break;
    }
  }
}

console.log(result); // 出力値: { key: '9zi03ivf3pfjjrr98kzq', value: 16362488 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 15.1ms/回

3-3-1-3. for 文で値からキーを取得

sample.ts
// 配列作成部分は共通のため省略

// 検索処理実行
startTime = new Date().valueOf();
let result;
for (let i = 0; i < 10; i++) {
  // 検索値から[キーと値のセット]を取得(for文を使用, 10回実行)
  for (let j = 0; j < array.length; j++) {
    if (array[j].value === targetValue) {
      result = array[j];
      break;
    }
  }
}

console.log(result); // 出力値: { key: 'obuxp7q8bsjyswgor1xt', value: 32159701 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 9.9ms/回

3-3-2. Map オブジェクトの実行速度計測

3-3-2-1. forEach 文で値からキーを取得

sample.ts
// 使用する変数・定数
const char = '0123456789abcdefghijklmnopqrstuvwxyz'; // キー作成用文字列
const mapArray = new Map<string, { key: string; value: number; }>();
const loop = 5000000; // ループ回数
const halfLoop = loop / 2 | 0; // ループ回数の中間点(ここで検索対象となるキーと値を取得)
const values = new Set<number>(); // 一意の値かを確認するためのSetオブジェクト
let targetKey = ''; // 検索対象のキーを格納
let targetValue = 0; // 検索対象の値を格納

// Mapオブジェクトを生成
let startTime = new Date().valueOf(); // 計測開始時間
for (let i = 0; i < loop; i++) {
  // 20文字の一意のキーを生成(0〜35までのランダムな整数値から1文字ずつ取得)
  let key = '';
  while (true) {
    for (let j = 0; j < 20; j++) key = key + char[(Math.random() * 36) | 0];
    if (!mapArray.has(key)) break; // 文字列が重複しなければループ終了
  }
  // 格納する一意の値を生成
  let value = (Math.random() * loop * 10) | 0;
  while (values.has(value)) value++; // 値が重複した場合は1を加算
  values.add(value);
  // Mapオブジェクトにキーと値のセットを追加(iを値として格納)
  mapArray.set(key, { key: key, value: value });
  // 検索対象とするキーと値を取得(ループの中間点)
  if (i === halfLoop)  {
    targetKey = key;
    targetValue = value;
  };
}
console.log(`検索キー: ${targetKey}, 検索値: ${targetValue}`); // 出力値: 検索キー: qms6ecczsnt56ixb0f9m, 検索値: 30788458
console.log(`配列作成終了: ${new Date().valueOf() - startTime}`); // 出力値: 配列作成終了: 16605

// 検索処理実行
startTime = new Date().valueOf(); // 検索開始時間
let result;
for (let i = 0; i < 10; i++) {
  // 検索値から[キーと値のセット]を取得(10回実行)
  mapArray.forEach((value, key) => {
    if (value.value === halfLoop) {
      result = value;
    }
  });
}

console.log(result); // 出力値: { key: 'qms6ecczsnt56ixb0f9m', value: 30788458 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 55.8ms/回

3-3-2-2. for...of 文で値からキーを取得

sample.ts
// 配列作成部分は共通のため省略

// 検索処理実行
startTime = new Date().valueOf(); // 検索開始時間
let result;
for (let i = 0; i < 10; i++) {
  // 検索値から[キーと値のセット]を取得(10回実行)
  for (let [key, value] of mapArray) {
    if (value.value === targetValue) {
      result = value;
      break;
    }
  }
}

console.log(result); // 出力値: { key: '1j8joaprmn8aav3vrtm9', value: 3830659 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 41.3ms/回

3-3-3. 連想配列(Object)の実行速度計測

3-3-3-1. 連想配列(Object)の実行速度計測(for...in で検索)

sample.ts
// 使用する変数・定数
const char = '0123456789abcdefghijklmnopqrstuvwxyz'; // キー作成用文字列
interface ObjectArray { [key: string]: { key: string; value: number; }; }
const objectArray: ObjectArray = {}; // 連想配列用の空オブジェクト
const loop = 5000000; // ループ回数
const halfLoop = loop / 2 | 0; // ループ回数の中間点(ここで検索対象となるキーと値を取得)
const values = new Set<number>(); // 一意の値かを確認するためのSetオブジェクト
let targetKey = ''; // 検索対象のキーを格納
let targetValue = 0; // 検索対象の値を格納

// 連想配列を生成
let startTime = new Date().valueOf(); // 計測開始時間
for (let i = 0; i < loop; i++) {
  // 20文字の一意のキーを生成(0〜35までのランダムな整数値から1文字ずつ取得)
  let key = '';
  while (true) {
    for (let j = 0; j < 20; j++) key = key + char[(Math.random() * 36) | 0];
    if (!(key in objectArray)) break; // 文字列が重複しなければループ終了
  }
  // 格納する一意の値を生成
  let value = (Math.random() * loop * 10) | 0;
  while (values.has(value)) value++; // 値が重複した場合は1を加算
  values.add(value);
  // 連想配列にキーと値のセットを追加
  objectArray[key] = { key: key, value: value};
  // 検索対象とするキーと値を取得(ループの中間点)
  if (i === (halfLoop)) {
    targetKey = key;
    targetValue = objectArray[key].value;
  };
}
console.log(`検索キー: ${targetKey}, 検索値: ${targetValue}`); // 出力値: lcygoqzajmjh9rooj32d, 検索値: 49123064
console.log(`配列作成終了: ${new Date().valueOf() - startTime}`); // 出力値: 11211

// 検索処理実行
startTime = new Date().valueOf();
let result;
for (let i = 0; i < 10; i++) {
  // 検索値から[キーと値のセット]を取得(for...in を使用)
  for (let key in objectArray) {
    if (objectArray[key].value === targetValue) {
      result = objectArray[key];
      break;
    };
  }
}
console.log(result); // 出力値: { key: 'lcygoqzajmjh9rooj32d', value: 49123064 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 2042.4ms/回

3-3-3-2. 連想配列(Object)の実行速度計測(配列変換して実行)

3-3-3-3-1. Object.keys() メソッドで配列変換後に find() メソッドで値からキーを取得
sample.ts
// 使用する変数・定数
const char = '0123456789abcdefghijklmnopqrstuvwxyz'; // キー作成用文字列
interface ObjectArray { [key: string]: { key: string; value: number; }; }
const objectArray: ObjectArray = {}; // 連想配列用の空オブジェクト
const loop = 5000000; // ループ回数
const halfLoop = loop / 2 | 0; // ループ回数の中間点(ここで検索対象となるキーと値を取得)
const values = new Set<number>(); // 一意の値かを確認するためのSetオブジェクト
let targetKey = ''; // 検索対象のキーを格納
let targetValue = 0; // 検索対象の値を格納

// 連想配列を生成
let startTime = new Date().valueOf(); // 計測開始時間
for (let i = 0; i < loop; i++) {
  // 20文字の一意のキーを生成(0〜35までのランダムな整数値から1文字ずつ取得)
  let key = '';
  while (true) {
    for (let j = 0; j < 20; j++) key = key + char[(Math.random() * 36) | 0];
    if (!(key in objectArray)) break; // 文字列が重複しなければループ終了
  }
  // 格納する一意の値を生成
  let value = (Math.random() * loop * 10) | 0;
  while (values.has(value)) value++; // 値が重複した場合は1を加算
  values.add(value);
  // 連想配列にキーと値のセットを追加
  objectArray[key] = { key: key, value: value};
  // 検索対象とするキーと値を取得(ループの中間点)
  if (i === (halfLoop)) {
    targetKey = key;
    targetValue = objectArray[key].value;
  };
}
console.log(`検索キー: ${targetKey}, 検索値: ${targetValue}`); // 出力値: 検索キー: jdkh37n394vlh8v9ikyk, 検索値: 29458260
console.log(`配列作成終了: ${new Date().valueOf() - startTime}`); // 出力値: 配列作成終了: 11623

// 検索処理実行
startTime = new Date().valueOf();
let result;
const keys = Object.keys(objectArray); // オブジェクトキーを配列に変換
console.log(`配列化終了: ${new Date().valueOf() - startTime}ms`); // 出力値: 配列化終了: 1925ms
startTime = new Date().valueOf();
for (let i = 0; i < 10; i++) {
  // 検索値から[キーと値のセット]を取得(Object.keys() および find() を使用)
  const resultKey = keys.find(key => objectArray[key].value === targetValue);
  if (resultKey) result = objectArray[resultKey];
}

console.log(result); // 出力値: { key: 'hnznlw5c9geymiomaouo', value: 1880654 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 333.1ms/回
3-3-3-3-2. Object.keys() メソッドで配列変換後に for...of 文で値からキーを取得
sample.ts
// 配列作成部分は共通のため省略

// 検索処理実行
startTime = new Date().valueOf();
let result;
const keys = Object.keys(objectArray); // オブジェクトキーを配列に変換
console.log(`配列化終了: ${new Date().valueOf() - startTime}ms`); // 出力値: 配列化終了: 1843ms
startTime = new Date().valueOf();
for (let i = 0; i < 10; i++) {
  for (let key of keys) {
    // 検索値から[キーと値のセット]を取得(Object.keys() および for...of を使用)
    if (objectArray[key].value === targetValue) {
      result = objectArray[key];
      break;
    };
  }
}

console.log(result); // 出力値: { key: 'jdkh37n394vlh8v9ikyk', value: 29458260 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 190.7ms/回

3-3-3-3. Object.entries() メソッドで配列変換後に find() メソッドで値からキーを取得

sample.ts
// 配列作成部分は共通のため省略

// 検索処理実行
startTime = new Date().valueOf();
let result;
const newArray = Object.entries(objectArray); // オブジェクトを配列に変換
console.log(`配列化終了: ${new Date().valueOf() - startTime}ms`); // 出力値: 12491ms
startTime = new Date().valueOf();
for (let i = 0; i < 10; i++) {
  // 検索値から[キーと値のセット]を取得(Object.entries() および find() を使用)
  const findEntity = newArray.find(e => e[1].value === targetValue);
  if (findEntity) result = objectArray[findEntity[0]];
}

console.log(result); // 出力値: { key: 'zsbdkm6s2xwa8fykard7', value: 19723261 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 41.7ms/回

3-3-3-4. Object.entries() メソッドで配列変換後に for...of 文で値からキーを取得

sample.ts
// 配列作成部分は共通のため省略

// 検索処理実行
startTime = new Date().valueOf();
let result;
const newArray = Object.entries(objectArray); // オブジェクトを配列に変換
console.log(`配列化終了: ${new Date().valueOf() - startTime}ms`); // 出力値: 配列化終了: 12347ms
startTime = new Date().valueOf();
for (let i = 0; i < 10; i++) {
  // 検索値から[キーと値のセット]を取得(Object.entries() および for...of を使用)
  let findEntity;
  for (let v of newArray) {
    if (v[1].value === targetValue) {
      findEntity = v[0];
      break;
    }
  }
  if (findEntity) result = objectArray[findEntity];
}

console.log(result); // 出力値: { key: 'm7jufdrlwjxwbr49rqxv', value: 37517996 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 49.6ms/回

3-3-3-5. Object.entries() メソッドで配列変換後に for 文で値からキーを取得

sample.ts
// 配列作成部分は共通のため省略

// 検索処理実行
startTime = new Date().valueOf();
let result;
const newArray = Object.entries(objectArray); // オブジェクトを配列に変換
console.log(`配列化終了: ${new Date().valueOf() - startTime}ms`); // 出力値: 配列化終了: 12188ms
startTime = new Date().valueOf();
for (let i = 0; i < 10; i++) {
  // 検索値から[キーと値のセット]を取得(Object.entries() および for文 を使用)
  let findEntity;
  for (let i = 0; i < newArray.length; i++) {
    if (newArray[i][1].value === targetValue) {
      findEntity = newArray[i][0];
      break;
    }
  }
  if (findEntity) result = objectArray[findEntity];
}

console.log(result); // 出力値: { key: '029qq9qpuvctua55ktcf', value: 25848464 }
console.log(`検索速度: ${(new Date().valueOf() - startTime) / 10}ms/回`);
// 出力値: 検索速度: 29.6ms/回

以上

18
7
5

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
18
7