Javascriptで多次元配列内の1要素を合算する:メモ

ある程度の作り方はイメージできても実際に書く際に迷う部分が多かったので、勉強のためにメモを残します。

実現したいこと


const array = [
  [ 1, "北海道", 10 ],
  [ 2, "本州", 20 ],
  [ 3, "九州", 30 ],
  [ 1, "北海道", 100 ],
  [ 1, "北海道", 200 ],
  [ 2, "本州", 300 ]
]

上記のようなインデックス0番目がidで、1番目はidに紐づく文字列が入っている多次元配列が対象です。

特徴としては0番目と1番目の組み合わせは固定であり、2番目の数値だけ違う点が挙げられます。

この多次元配列を以下のように作り替え、2番目の数値を合算してまとめるのが目的です。


const array = [
  [ 1, "北海道", 310 ],
  [ 2, "本州", 320 ],
  [ 3, "九州", 30 ]
]

参考

上記の記事内にある回答を参考にしました。
2015年の記事ですし当時でも古い書き方だとは思いますが、参考ページの方の以下の言葉にはうなずくばかりです。

この状況で躓いてるようでしたら、Array#map 等の関数を使わず、for 文でコードを書いてみる事をお勧めします。

サンプルコード


const array = [[ 1, "北海道", 10 ],
  [ 2, "本州", 20 ],
  [ 3, "九州", 30 ],
  [ 1, "北海道", 100 ],
  [ 1, "北海道", 200 ],
  [ 2, "本州", 300 ]];

const create_noduplicates_array = (data) => {
  const keys = [],
      values1 = [],
      values2 = [],
      results = [];

  for (let i = 0, l = data.length, element, index; i < l; ++i) {
    element = data[i];
    index = keys.indexOf(element[0]);
    if (index !== -1) {
      values2[index] += element[2];
    } else {
      keys.push(element[0]);
      values1.push(element[1]);
      values2.push(element[2]);
    }
  }
  for (let i = 0, l = keys.length; i < l; ++i) {
    results.push([keys[i],values1[i],values2[i]]);
  }
  return results;
}

console.log(create_noduplicates_array(array));

動作の概要

自分用にコメントで動作を説明したバージョンを記載します。


const array = [[ 1, "北海道", 10 ],
  [ 2, "本州", 20 ],
  [ 3, "九州", 30 ],
  [ 1, "北海道", 100 ],
  [ 1, "北海道", 200 ],
  [ 2, "本州", 300 ]];

//dataには上記の配列(array)を入れる想定
const create_noduplicates_array = (data) => {
  // kyesにはキーとなるidを入れる
  // values1には都道府県名を入れる
  // values2には数値を入れる
  // このようにそれぞれの値を配列にいれることで、各配列のインデック番号と配列が以下のような関係性を持つことができる
  // [keys[0],values1[0],values2[0]] === [1,"北海道",10] 
  const keys = [],
      values1 = [],
      values2 = [],
      results = [];

  for (let i = 0, l = data.length, element, index; i < l; ++i) {
    
    // elementに配列のi番目を入れる
    element = data[i];

    // keysの中で、idの位置を確認する
    index = keys.indexOf(element[0]);

    if (index !== -1) {
      // keysの中に同じidがあれば、数値を入れているvalues2のidと同じインデックス番号の位置に数値を追加する
      values2[index] += element[2];

    } else {
      // elementの中に同じidがなければ、都道府県と数値用の配列に値を追加する
      keys.push(element[0]);
      values1.push(element[1]);
      values2.push(element[2]);

    }

    /*
    初回のループではkeysに値は入っていないので、必ずこちらが実行され、keysは[1]になる。
    2回目と3回目のループも同様になり、3回目の時点でkeysとvalues1と2は以下のようになる
    keys→ [1,2,3]
    values1→ ["北海道","本州","九州"]
    values2→ [10,20,30]

    4回目のループではidが1なので keys.indexOf(element[0]) の値は0(配列の先頭のindex番号は0なので)になり、index !== -1 がtrueになる。
    結果として、values2のインデックス番号0番にelement[2](=数値)が追加される。

    そのため4回目のループ後には以下のようになる。
    keys→ [1,2,3]
    values1→ ["北海道","本州","九州"]
    values2→ [110,20,30]

    あとはこれを繰り返し、kyesのインデックス番号に基づいてvalues2に追加するか、keysとvalues1とvalues2に値を追加するかが実行される。
    */

  }
  // keysの要素には重複はないので、keysの要素数の数だけforを回す
  for (let i = 0, l = keys.length; i < l; ++i) {

    // idと都道府県と数値が、keysとvalues1とvalues2に分かれているため、kesyに基づいて一つの多次元配列に戻していく
    results.push([keys[i],values1[i],values2[i]]);

  }
  return results;
}

console.log(create_noduplicates_array(array));

大まかに書くと以下のようになります。

  • それぞれ別の配列を作る
  • 基準となるidに基づいて、数値を足すだけか、それぞれの配列に新たに要素を追加するかをifで実行する
  • 最後に一つの多次元配列に戻す

結び

実装イメージはあったのですが細かい部分が形にできず、参考コードを基に書いて各部の動作を確認してやっとすっきりできました。

補足

多次元連想配列で要素が3種以上の場合は、以下記事のコメントにあるワンライナーが利用可能でした。


Object.values(arr.reduce((a, {id, name, money}) => ({...a, [id]:{id, name, money:(a[id] ? a[id].money : 0) + money}}), {}))

2人がこの記事を評価

役に立ったよという方は上の「記事を評価する」ボタンをクリックしてもらえると嬉しいです。

連投防止のためにCookie使用。SNSへの投稿など他サービスとの連動は一切ありません。

コメント欄