JavaScript
 Computer >> コンピューター >  >> プログラミング >> JavaScript

JavaScriptで2つの配列の共通要素(積集合)を求めるintersection関数の実装方法

はじめに

JavaScriptには、2つの配列が与えられたときに、それらの共通部分(積集合)を計算して返す組み込みのメソッドは用意されていません。そこで本記事では、共通する要素を任意の順序で含む配列を返す関数 intersection() を自前で実装する方法を解説します。重要なポイントとして、結果に含まれる各要素は、両方の配列に現れる回数だけ繰り返して含める必要があります。

具体例

たとえば、入力が以下の2つの配列だったとします。

arr1 = ['hello', 'world', 'how', 'are', 'you'];
arr2 = ['hey', 'world', 'can', 'you', 'rotate'];

この場合、期待される出力は次のようになります。

['world', 'you'];

アプローチの検討

もし配列があらかじめソート済みであれば、「双ポインタ(ツーポインター)」方式が有効です。両方のポインタをそれぞれの配列の先頭(0番目)に置き、値を比較しながら対応するポインタを進めていくことで、時間計算量 O(m+n)(m と n は各配列のサイズ)で処理できます。

しかし今回扱うのは未ソートの配列です。わざわざソートを実行すると O(m log m + n log n) のコストがかかるため、このアプローチを採るメリットはありません。そこで、シンプルに最初の配列の各値をもう一方の配列と順番に照合し、一致した要素を共通要素の配列に追加していく方法を採用します。

この素朴な照合方式では、時間計算量は O(n²) になります。ただし、片方の短い配列を基準にループを回すことで、無駄な比較を減らす工夫をしています。

実装コード

以下が実際のコードです。

arr1 = ['hello', 'world', 'how', 'are', 'you'];
arr2 = ['hey', 'world', 'can', 'you', 'rotate'];
const intersectElements = (arr1, arr2) => {
    const res = [];
    const { length: len1 } = arr1;
    const { length: len2 } = arr2;
    // 短い方の配列を基準にループすることで効率化
    const smaller = (len1 < len2 ? arr1 : arr2).slice();
    const bigger = (len1 >= len2 ? arr1 : arr2).slice();
    for(let i = 0; i < smaller.length; i++) {
        if(bigger.indexOf(smaller[i]) !== -1){
            res.push(smaller[i]);
            // 一致した要素をundefinedで置き換え、重複カウントを防止
            bigger.splice(bigger.indexOf(smaller[i]), 1, undefined);
        }
    };
    return res;
};
console.log(intersectElements(arr1, arr2));

このコードのポイントは、一致が見つかった際に splice() を使って元の配列側の要素を undefined に置き換えているところです。これにより、同じ要素が複数回マッチしても、正しく重複を排除できる仕組みになっています。

出力結果

コンソールには次のように出力されます。

[ 'world', 'you' ]

まとめ

未ソートの配列同士の共通要素を求める場合、ソートしてから双ポインタを使うよりも、短い配列を基準に線形探索する方がシンプルで効率的です。データ量が小規模な場合は O(n²) のアプローチでも十分実用的ですが、大規模なデータを扱う場合は SetMap を活用して O(m+n) に最適化することも検討するとよいでしょう。

  1. JavaScriptのテンプレート文字列(テンプレートリテラル)とは?基本の使い方を解説

    テンプレート文字列(テンプレートリテラル)とは テンプレート文字列は、ES6(ECMAScript 2015)で導入された機能で、文字列の中に式を直接埋め込むことを可能にしました。従来のシングルクォート()やダブルクォート()の代わりに、バッククォート(``)を使用する点が特徴です。 テンプレート文字列を使うと、文字列の連結や補間(インターポレーション)がはるかに直感的に行えます。例えば ${a + b} のように記述することで、文字列の中に任意の式をそのまま埋め込めます。従来のように + 演算子で文字列をつなぎ合わせる方法と比べると、はるかに読みやすく美しい構文です。 JavaScriptに

  2. JavaScriptでテンプレート文字列をネストする方法

    JavaScriptのES6で導入されたテンプレート文字列(テンプレートリテラル)は、バッククォート(`)で囲むことで文字列の中に変数や式を埋め込める便利な機能です。実は、この${}(プレースホルダー)の中に、さらに別のテンプレート文字列を入れ子(ネスト)として記述することもできます。テンプレート文字列のネストとは${}の中には任意のJavaScript式を書けるため、その中で再度バッククォートを使ったテンプレート文字列を評価することが可能です。これにより、関数呼び出しの引数として動的に生成した文字列を渡すなど、柔軟な文字列組み立てが行えます。コード例以下は、ボタンをクリックすると、ネストされ