[Javascript] 配列操作小ネタ集 最大、最小、平均、合計、分散、標準偏差、重複削除、割り込みなど

記事
IT・テクノロジー

はじめに

Javascriptには便利な配列用のメソッドがたくさんありますが、いざ、イメージ通りの処理を行おうとすると、自分で実装しなくてはならないことが多いです。よく使いそうなものからニッチなものまでいくつかのロジックサンプルを紹介します。

前提として、共通の引数として使用する一次元配列は以下とします。
const array01 = [1,6,2,5,8,7,3,4,9,2,6,8,1,3,5];
const array02 = [10,15,20,5,35,25,30];
なお、二次元配列やその他多次元配列の場合、別途処理内容が異なりますので、今回は考慮していません。

■比較的よく使いそうな処理

1. 配列の要素をランダムに並べる
Array.reduceを使っている例も見受けられますが、無駄な配列を生成しない分、Array.forEachを使うほうがシンプルかと思います。実際の処理内容も、先頭から順次、Math.randomによって生成されたインデックス番号の要素と入替えているだけです。
const arrayRandom = arr => {
    arr.forEach((_, i) => {
        const j = Math.floor(Math.random() * (i + 1));
        [arr[i], arr[j]] = [arr[j], arr[i]];
    });
    return arr;
};
console.log(arrayRandom(array01));
// 出力例 [6,8,9,8,4,3,1,5,2,6,7,2,1,3,5];

2. 配列中の特定値の数を取得
エクセルなどではCOUNTIF関数に該当しますが、その機能限定版といったところです。処理内容は単純で、条件式に合致する要素をArray.filterで絞り込み、その長さを返しているだけです。lengthが取得できないときは0を返します。

なお、「val === target」が条件式になりますので、適宜修正すれば任意の条件に合致する要素数を取得することもできます。
const getTargetNum = (arr, target) => arr.filter(val => val === target).length || 0;
console.log(getTargetNum(array01, 5));
// 2

console.log(getTargetNum(array01, '5'));
// 0

3. 配列中の要素重複をなくす
GoogleスプレッドシートのUNIQUE関数に該当するものです。ES2015(ES6)環境以降であれば、Setオブジェクトを生成して終了です。
const uniqueArr = [...new Set(array)];
console.log(uniqueArr);
// [1,6,2,5,8,7,3,4,9]
以下のようにArray.filterを利用する方法もあります。
const unique = arr => arr.filter((val, i, arr) => arr.indexOf(val) === i);
console.log(unique(array01));
// [1,6,2,5,8,7,3,4,9]

■若干ニッチな処理

4. 配列中の任意の要素を任意の位置に割り込ませる(入れ替えではない)
とある案件の仕様上、必要なロジックでしたが、需要があるのかは不明ですけれども紹介しておきます。処理内容としては、移動させる要素を取り出し、元配列を詰めた後、指定の位置に割り込ませています。

Array.spliceは破壊的メソッドのため、元の配列を変更します。そのため、2回目のconsole.logでは1回目の処理で変更された順番を元に、割り込み処理が行われていることにご注意ください。

※汎用化のため、引数に指定したインデックス値が有効でない場合、強制的に配列の最後尾に置き換えるようにしています。もし実装する場合は、冒頭の2行を削除の上、関数を当てる前に引数のチェックをしていただくほうが良いと思います。
const intoArr = (arr, current, into) => {
    current = current >= arr.length? arr.length-1: current;
    into = into >= arr.length? arr.length-1: into;
    const target = arr[current];
    arr.splice(current, 1);
    arr.splice(into, 0, target);
    return arr;
};
console.log(intoArr(array01, 3, 0));
// [5,1,6,2,8,7,3,4,9,2,6,8,1,3,5]

console.log(intoArr(array01, 5, 2));
// [5,1,7,6,2,8,3,4,9,2,6,8,1,3,5]

5. 一次元配列をn個ずつに分割して新しい二次元配列を返す
元記事執筆当初はArray.reduceを用いたロジックを紹介していましたが、頂いたコメントより、処理負荷の面からも結果が配列ならArray.flatMapを使用するほうがより適切であると判断しました。(結果がオブジェクトなら、Array.reduceで良いでしょう)
const chunkedArr = (arr, num) => arr.flatMap((_, i, arr) => i % num? []: [arr.slice(i, i+num)]);

console.log(chunkedArr(array01, 3));
// [[1,6,2],[5,8,7],[3,4,9],[2,6,8],[1,3,5]]

console.log(chunkedArr(array02, 3));
// [[10,15,20],[5,35,25],[30]]

■統計・集計に関する必要最低限の処理

統計及び集計関連の案件はスプレッドシート+GAS、もしくは数式設定で対応することがほとんどです。以前にJavascriptメインの統計に関する案件依頼があり、レアな内容でしたので誰かのお役に立てばと、その際に使用した必要最低限の統計解析セットを紹介します。

なお、分散及び標準偏差に関しましては、公式の解説は割愛します。あくまでもJavascriptのコードの紹介となります。

また、当然ですが統計及び集計での利用前提ですので、配列の値も数値前提となります。数値以外が配列内に入る可能性がある場合、実装時には配列内の事前チェックをしましょう。

6. 最大値と最小値
コードレビューした際、よくfor文やforEachで順次確認するロジックを見かけます。配列操作という観点からあえてArray.sortを使用したものを先に紹介しておきますと、降順にソートしたものの先頭が最大値になります。逆に昇順にソートしたものの先頭が最小値となります。
const max = arr => arr.sort((a,b) => b-a)[0];
console.log(max(array01));
// 9

const min = arr => arr.sort((a,b) => a-b)[0];
console.log(min(array01));
// 1
[参考] Mathオブジェクトを使用する
Mathオブジェクトを使用すれば、もっとシンプルに記述できますので参考に併記しておきます。
const max = Math.max(...array02);
console.log(max);
// 35

const min = Math.min(...array02);
console.log(min);
// 5
※補足
Mathオブジェクト使用時には以下の注意事項があります。
スプレッド構文の (...) と apply のどちらも、配列に膨大な要素があった場合は、配列の要素を関数の引数として渡そうとするため、失敗したり、誤った結果を返したりすることがあります。
MDNより引用
また、Array.sortを使用する場合においても、元配列を直接並び替えるため(破壊的)、元配列を変更したくない場合には、Array.map もしくは Array.reduce を使用するほうが安全です。

7. 合計
Array.reduceは配列のそれぞれの要素に対して、順次ユーザー定義のコールバック関数を実行し、その処理によって配列の各値を一つの値にまとめます。この説明では分かりづらいですが、配列要素の合計を求めるのはこのメソッドの基本形です。
const sum = arr => arr.reduce((a,b) => a+b);
console.log(sum(array02));
// 140

8. 平均値
説明するまでもないのですが、合計を要素数で除算すれば平均値を算出できます。
const avr = arr => arr.reduce((a,b) => a+b)/arr.length;
console.log(avr(array02));
// 20

9. 分散
ネット上で散見されるロジックでは、Array.mapを使って(値-平均値)の2乗の配列を生成した後、Array.redduceで合計を出し、さらに配列の要素数で除算するというものが多い気がします。以下のようにArray.reduceのみでも計算できます。
const variance = arr => {
    const avr = arr.reduce((a,b) => a+b)/arr.length;
    return arr.reduce((a,b) => (a + ((c - avr) ** 2)),0)/arr.length;
};
console.log(variance(array02));
// 100.0

10. 標準偏差(母集団標準偏差)
分散の平方根ですから、Math.sqrtを使用します。
const stdev = arr => {
   const avr = arr.reduce((a,b) => a+b)/arr.length;
   const Var = arr.reduce((a,c) => (a + ((c - avr) ** 2)),0)/arr.length;
   return Math.sqrt(Var);
};
console.log(stdev(array02));
// 10.0

// 先述の分散処理(variance)が既に定義されていれば、以下のようにワンライナーで記述できます。
const stdev = arr => Math.sqrt(variance(array02));

最後に

配列処理は様々な方法が存在します。Javascriptに限らず、配列操作の様々なバリエーションに触れることは、スキルアップや知見を広める意味でも大切なことかと思います。今回紹介した方法が最適解ということではございませんが、誰かのお役に立てば幸いです。

CSSを含めたフロントエンドの表示・動作不具合の解析も行います
以下サービスよりご相談受付けております。
[追記]2022.06.25
「配列中の任意の要素を任意の位置に割り込ませる(入れ替えではない)」の出力結果サンプルに誤りがありましたので修正しました。
また、エラー処理が考慮されていなかったため補足しました。

[追記]2022.12.14
タイトル及び記事全体の構成と一部文言の修正を行いました。
サービス数40万件のスキルマーケット、あなたにぴったりのサービスを探す