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

JavaScriptで配列を出現頻度の昇順に並べ替える方法

問題

数値の配列 arr を唯一の引数として受け取るJavaScript関数を作成する必要があります。配列 arr には重複した要素が含まれている可能性があります。

この関数では、出現回数が少ない要素から順に配列を並べ替えます。つまり、出現頻度の低い要素を先頭に配置し、頻度の昇順に沿って残りの要素を並べていきます。

なお、出現回数が同じ要素が複数存在する場合は、それらを値の昇順(小さい順)に配置する必要があります。

入力例

const arr = [5, 4, 5, 4, 2, 1, 12];

出力例

[1, 2, 12, 4, 4, 5, 5]

出力の解説

数値「1」「2」「12」はそれぞれ1回しか出現しないため、値の昇順に並んでいます。その後ろには、2回ずつ出現する「4」と「5」が続きます。

サンプルコード

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

const arr = [5, 4, 5, 4, 2, 1, 12];
const sortByAppearance = (arr = []) => {
    // まず値の昇順にソートしておく
    arr.sort((a, b) => a - b);
    const res = [];
    const searched = {};

    // 対象の値が出現する回数をカウントする関数
    const countAppearance = (list, target) => {
        searched[target] = true;
        let count = 0;
        let index = list.indexOf(target);
        while(index !== -1){
            count++;
            list.splice(index, 1);
            index = list.indexOf(target);
        };
        return count;
    };

    const map = [];
    arr.forEach(el => {
        if(!searched.hasOwnProperty(el)){
            map.push([el, countAppearance(arr.slice(), el)]);
        };
    });

    // 出現回数の昇順に並べ替え
    map.sort((a, b) => a[1] - b[1]);

    map.forEach(([num, freq]) => {
        while(freq){
            res.push(num);
            freq--;
        }
    });
    return res;
};
console.log(sortByAppearance(arr));

実行結果

[1, 2, 12, 4, 4, 5, 5]

コードの解説

この実装の流れは次のとおりです。

  1. まず sort() で配列全体を値の昇順に並べ替えておきます。
  2. countAppearance() 関数が、まだ調査していない要素について出現回数をカウントし、[要素, 回数] のペアとして map 配列に格納します。
  3. map を出現回数の昇順にソートします。
  4. 最後に、各要素を出現回数の分だけ結果配列 res に追加していきます。

元の配列はあらかじめ値の昇順にソートされているため、ユニークな要素は小さい順に map へ登録されます。そのため、出現回数によるソートで同頻度の要素が並んでも、安定ソートによって値の昇順が保たれます。

より効率的な実装例(Mapを使った方法)

上記のコードは indexOf()splice() を繰り返し呼び出すため、計算量が O(n²) 程度になり、大きな配列では処理速度が低下します。事前に Map オブジェクトで各要素の出現回数をカウントしておけば、よりシンプルかつ高速に実装できます。

const sortByAppearance = (arr = []) => {
    // 各要素の出現回数をカウント
    const countMap = new Map();
    for (const num of arr) {
        countMap.set(num, (countMap.get(num) || 0) + 1);
    }

    // 頻度の昇順、同一昇順、同一頻度なら値の昇順でソートして返す
    return [...arr].sort((a, b) => {
        const diff = countMap.get(a) - countMap.get(b);
        return diff !== 0 ? diff : a - b;
    });
};

const arr = [5, 4, 5, 4, 2, 1, 12];
console.log(sortByAppearance(arr)); // [1, 2, 12, 4, 4, 5, 5]

この方法では、比較関数の中でまず「出現回数の差」を評価し、差がゼロ(=同頻度)の場合にのみ値の大小を比較することで、すべての要件を1回のソートで満たせます。計算量は O(n log n) に収まり、コードの可読性も大幅に向上します。

まとめ

本記事では、配列を出現頻度の昇順(同頻度の場合は値の昇順)に並べ替えるJavaScript関数の実装方法を紹介しました。indexOf() ベースの素朴な実装と、Map を活用した効率的な実装の両方を理解しておくと、実際の開発業務やコーディングテストで役立つでしょう。

  1. JavaScriptで複数の文字列に共通する文字を重複込みで抽出する方法

    問題文字列の配列 arr を第一引数(かつ唯一の引数)として受け取るJavaScript関数を作成することを考えます。この関数の役割は、配列内のすべての文字列に出現する文字を抽出し、それらを配列として返すことです。重複も含めて扱う必要がある点に注意してください。たとえば、ある文字がすべての文字列の中で2回ずつ出現している場合(3回ではない場合)、最終的な結果にもその文字を2回だけ含めます。入力と出力の例たとえば、関数への入力が次のような場合を考えてみましょう。const arr = [door, floor, crook];r は3つの文字列すべてに1回ずつ現れ、o は door に2回、fl

  2. JavaScriptで配列を昇順(増加列)に変換できるか判定する方法

    本記事では、整数型の配列を引数として受け取り、「要素を最大1つだけ変更することで配列を昇順(増加列)にできるか」を判定するJavaScript関数の実装方法を解説します。 増加列(Increasing Sequence)とは 配列が増加列であるとは、すべてのインデックス i(0 ≤ i ≤ n − 2)に対して、次の条件が成り立つことを指します。 arr[i] <= arr[i + 1] つまり、隣り合う要素を左から右へ見たときに値が減少することが一度もない(単調非減少=広義の昇順)状態のことです。等しい値が並んでいても問題ありません。 問題の定義 整数の配列 arr を第一引数(唯一の